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

« back to all changes in this revision

Viewing changes to subversion/libsvn_fs_fs/fs_fs.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:
20
20
 * ====================================================================
21
21
 */
22
22
 
23
 
#include <stdlib.h>
24
 
#include <stdio.h>
25
 
#include <string.h>
26
 
#include <ctype.h>
27
 
#include <assert.h>
28
 
#include <errno.h>
 
23
#include "fs_fs.h"
29
24
 
30
 
#include <apr_general.h>
31
 
#include <apr_pools.h>
32
 
#include <apr_file_io.h>
33
25
#include <apr_uuid.h>
34
 
#include <apr_lib.h>
35
 
#include <apr_md5.h>
36
 
#include <apr_sha1.h>
37
 
#include <apr_strings.h>
38
 
#include <apr_thread_mutex.h>
39
 
 
40
 
#include "svn_pools.h"
41
 
#include "svn_fs.h"
42
 
#include "svn_dirent_uri.h"
43
 
#include "svn_path.h"
 
26
 
 
27
#include "svn_private_config.h"
 
28
 
 
29
#include "svn_checksum.h"
44
30
#include "svn_hash.h"
45
31
#include "svn_props.h"
 
32
#include "svn_time.h"
 
33
#include "svn_dirent_uri.h"
46
34
#include "svn_sorts.h"
47
 
#include "svn_string.h"
48
 
#include "svn_time.h"
49
 
#include "svn_mergeinfo.h"
50
 
#include "svn_config.h"
51
 
#include "svn_ctype.h"
52
35
#include "svn_version.h"
53
36
 
54
 
#include "fs.h"
55
 
#include "tree.h"
56
 
#include "lock.h"
57
 
#include "key-gen.h"
58
 
#include "fs_fs.h"
 
37
#include "cached_data.h"
59
38
#include "id.h"
 
39
#include "index.h"
60
40
#include "rep-cache.h"
61
 
#include "temp_serializer.h"
 
41
#include "revprops.h"
 
42
#include "transaction.h"
 
43
#include "tree.h"
 
44
#include "util.h"
62
45
 
 
46
#include "private/svn_fs_util.h"
 
47
#include "private/svn_io_private.h"
63
48
#include "private/svn_string_private.h"
64
 
#include "private/svn_fs_util.h"
65
49
#include "private/svn_subr_private.h"
66
 
#include "private/svn_delta_private.h"
67
50
#include "../libsvn_fs/fs-loader.h"
68
51
 
69
 
#include "svn_private_config.h"
70
 
#include "temp_serializer.h"
71
 
 
72
 
/* An arbitrary maximum path length, so clients can't run us out of memory
73
 
 * by giving us arbitrarily large paths. */
74
 
#define FSFS_MAX_PATH_LEN 4096
75
 
 
76
52
/* The default maximum number of files per directory to store in the
77
53
   rev and revprops directory.  The number below is somewhat arbitrary,
78
54
   and can be overridden by defining the macro while compiling; the
96
72
   Values < 1 disable deltification. */
97
73
#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
98
74
 
99
 
/* Give writing processes 10 seconds to replace an existing revprop
100
 
   file with a new one. After that time, we assume that the writing
101
 
   process got aborted and that we have re-read revprops. */
102
 
#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
103
 
 
104
 
/* The following are names of atomics that will be used to communicate
105
 
 * revprop updates across all processes on this machine. */
106
 
#define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
107
 
#define ATOMIC_REVPROP_TIMEOUT    "rev-prop-timeout"
108
 
#define ATOMIC_REVPROP_NAMESPACE  "rev-prop-atomics"
109
 
 
110
 
/* Following are defines that specify the textual elements of the
111
 
   native filesystem directories and revision files. */
112
 
 
113
 
/* Headers used to describe node-revision in the revision file. */
114
 
#define HEADER_ID          "id"
115
 
#define HEADER_TYPE        "type"
116
 
#define HEADER_COUNT       "count"
117
 
#define HEADER_PROPS       "props"
118
 
#define HEADER_TEXT        "text"
119
 
#define HEADER_CPATH       "cpath"
120
 
#define HEADER_PRED        "pred"
121
 
#define HEADER_COPYFROM    "copyfrom"
122
 
#define HEADER_COPYROOT    "copyroot"
123
 
#define HEADER_FRESHTXNRT  "is-fresh-txn-root"
124
 
#define HEADER_MINFO_HERE  "minfo-here"
125
 
#define HEADER_MINFO_CNT   "minfo-cnt"
126
 
 
127
 
/* Kinds that a change can be. */
128
 
#define ACTION_MODIFY      "modify"
129
 
#define ACTION_ADD         "add"
130
 
#define ACTION_DELETE      "delete"
131
 
#define ACTION_REPLACE     "replace"
132
 
#define ACTION_RESET       "reset"
133
 
 
134
 
/* True and False flags. */
135
 
#define FLAG_TRUE          "true"
136
 
#define FLAG_FALSE         "false"
137
 
 
138
 
/* Kinds that a node-rev can be. */
139
 
#define KIND_FILE          "file"
140
 
#define KIND_DIR           "dir"
141
 
 
142
 
/* Kinds of representation. */
143
 
#define REP_PLAIN          "PLAIN"
144
 
#define REP_DELTA          "DELTA"
145
 
 
146
75
/* Notes:
147
76
 
148
77
To avoid opening and closing the rev-files all the time, it would
155
84
 
156
85
*/
157
86
 
158
 
/* The vtable associated with an open transaction object. */
159
 
static txn_vtable_t txn_vtable = {
160
 
  svn_fs_fs__commit_txn,
161
 
  svn_fs_fs__abort_txn,
162
 
  svn_fs_fs__txn_prop,
163
 
  svn_fs_fs__txn_proplist,
164
 
  svn_fs_fs__change_txn_prop,
165
 
  svn_fs_fs__txn_root,
166
 
  svn_fs_fs__change_txn_props
167
 
};
168
 
 
169
87
/* Declarations. */
170
88
 
171
89
static svn_error_t *
172
 
read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
173
 
                      const char *path,
174
 
                      apr_pool_t *pool);
175
 
 
176
 
static svn_error_t *
177
 
update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
178
 
 
179
 
static svn_error_t *
180
 
get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
181
 
 
182
 
static svn_error_t *
183
 
verify_walker(representation_t *rep,
184
 
              void *baton,
185
 
              svn_fs_t *fs,
186
 
              apr_pool_t *scratch_pool);
 
90
get_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool);
187
91
 
188
92
/* Pathname helper functions */
189
93
 
190
 
/* Return TRUE is REV is packed in FS, FALSE otherwise. */
191
 
static svn_boolean_t
192
 
is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
193
 
{
194
 
  fs_fs_data_t *ffd = fs->fsap_data;
195
 
 
196
 
  return (rev < ffd->min_unpacked_rev);
197
 
}
198
 
 
199
 
/* Return TRUE is REV is packed in FS, FALSE otherwise. */
200
 
static svn_boolean_t
201
 
is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
202
 
{
203
 
  fs_fs_data_t *ffd = fs->fsap_data;
204
 
 
205
 
  /* rev 0 will not be packed */
206
 
  return (rev < ffd->min_unpacked_rev)
207
 
      && (rev != 0)
208
 
      && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
209
 
}
210
 
 
211
94
static const char *
212
95
path_format(svn_fs_t *fs, apr_pool_t *pool)
213
96
{
226
109
  return svn_dirent_join(fs->path, PATH_CURRENT, pool);
227
110
}
228
111
 
229
 
static APR_INLINE const char *
230
 
path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
231
 
{
232
 
  return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
233
 
}
234
 
 
235
 
static APR_INLINE const char *
236
 
path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
237
 
{
238
 
  return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
239
 
}
240
 
 
241
 
static APR_INLINE const char *
242
 
path_lock(svn_fs_t *fs, apr_pool_t *pool)
243
 
{
244
 
  return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
245
 
}
246
 
 
247
 
static const char *
248
 
path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
249
 
{
250
 
  return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
251
 
}
252
 
 
253
 
static const char *
254
 
path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
255
 
                apr_pool_t *pool)
256
 
{
257
 
  fs_fs_data_t *ffd = fs->fsap_data;
258
 
 
259
 
  assert(ffd->max_files_per_dir);
260
 
  assert(is_packed_rev(fs, rev));
261
 
 
262
 
  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
263
 
                              apr_psprintf(pool,
264
 
                                           "%ld" PATH_EXT_PACKED_SHARD,
265
 
                                           rev / ffd->max_files_per_dir),
266
 
                              kind, NULL);
267
 
}
268
 
 
269
 
static const char *
270
 
path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
271
 
{
272
 
  fs_fs_data_t *ffd = fs->fsap_data;
273
 
 
274
 
  assert(ffd->max_files_per_dir);
275
 
  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
276
 
                              apr_psprintf(pool, "%ld",
277
 
                                                 rev / ffd->max_files_per_dir),
278
 
                              NULL);
279
 
}
280
 
 
281
 
static const char *
282
 
path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
283
 
{
284
 
  fs_fs_data_t *ffd = fs->fsap_data;
285
 
 
286
 
  assert(! is_packed_rev(fs, rev));
287
 
 
288
 
  if (ffd->max_files_per_dir)
289
 
    {
290
 
      return svn_dirent_join(path_rev_shard(fs, rev, pool),
291
 
                             apr_psprintf(pool, "%ld", rev),
292
 
                             pool);
293
 
    }
294
 
 
295
 
  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
296
 
                              apr_psprintf(pool, "%ld", rev), NULL);
297
 
}
298
 
 
299
 
svn_error_t *
300
 
svn_fs_fs__path_rev_absolute(const char **path,
301
 
                             svn_fs_t *fs,
302
 
                             svn_revnum_t rev,
303
 
                             apr_pool_t *pool)
304
 
{
305
 
  fs_fs_data_t *ffd = fs->fsap_data;
306
 
 
307
 
  if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
308
 
      || ! is_packed_rev(fs, rev))
309
 
    {
310
 
      *path = path_rev(fs, rev, pool);
311
 
    }
312
 
  else
313
 
    {
314
 
      *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
315
 
    }
316
 
 
317
 
  return SVN_NO_ERROR;
318
 
}
319
 
 
320
 
static const char *
321
 
path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
322
 
{
323
 
  fs_fs_data_t *ffd = fs->fsap_data;
324
 
 
325
 
  assert(ffd->max_files_per_dir);
326
 
  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
327
 
                              apr_psprintf(pool, "%ld",
328
 
                                           rev / ffd->max_files_per_dir),
329
 
                              NULL);
330
 
}
331
 
 
332
 
static const char *
333
 
path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
334
 
{
335
 
  fs_fs_data_t *ffd = fs->fsap_data;
336
 
 
337
 
  assert(ffd->max_files_per_dir);
338
 
  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
339
 
                              apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
340
 
                                           rev / ffd->max_files_per_dir),
341
 
                              NULL);
342
 
}
343
 
 
344
 
static const char *
345
 
path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
346
 
{
347
 
  fs_fs_data_t *ffd = fs->fsap_data;
348
 
 
349
 
  if (ffd->max_files_per_dir)
350
 
    {
351
 
      return svn_dirent_join(path_revprops_shard(fs, rev, pool),
352
 
                             apr_psprintf(pool, "%ld", rev),
353
 
                             pool);
354
 
    }
355
 
 
356
 
  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
357
 
                              apr_psprintf(pool, "%ld", rev), NULL);
358
 
}
359
 
 
360
 
static APR_INLINE const char *
361
 
path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
362
 
{
363
 
  SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
364
 
  return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
365
 
                              apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
366
 
                                          (char *)NULL),
367
 
                              NULL);
368
 
}
369
 
 
370
 
/* Return the name of the sha1->rep mapping file in transaction TXN_ID
371
 
 * within FS for the given SHA1 checksum.  Use POOL for allocations.
372
 
 */
373
 
static APR_INLINE const char *
374
 
path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
375
 
              apr_pool_t *pool)
376
 
{
377
 
  return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
378
 
                         svn_checksum_to_cstring(sha1, pool),
379
 
                         pool);
380
 
}
381
 
 
382
 
static APR_INLINE const char *
383
 
path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
384
 
{
385
 
  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
386
 
}
387
 
 
388
 
static APR_INLINE const char *
389
 
path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
390
 
{
391
 
  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
392
 
}
393
 
 
394
 
static APR_INLINE const char *
395
 
path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
396
 
{
397
 
  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
398
 
}
399
 
 
400
 
static APR_INLINE const char *
401
 
path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
402
 
{
403
 
  return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
404
 
}
405
 
 
406
 
 
407
 
static APR_INLINE const char *
408
 
path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
409
 
{
410
 
  fs_fs_data_t *ffd = fs->fsap_data;
411
 
  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
412
 
    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
413
 
                                apr_pstrcat(pool, txn_id, PATH_EXT_REV,
414
 
                                            (char *)NULL),
415
 
                                NULL);
416
 
  else
417
 
    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
418
 
}
419
 
 
420
 
static APR_INLINE const char *
421
 
path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
422
 
{
423
 
  fs_fs_data_t *ffd = fs->fsap_data;
424
 
  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
425
 
    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
426
 
                                apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
427
 
                                            (char *)NULL),
428
 
                                NULL);
429
 
  else
430
 
    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
431
 
                           pool);
432
 
}
433
 
 
434
 
static const char *
435
 
path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
436
 
{
437
 
  const char *txn_id = svn_fs_fs__id_txn_id(id);
438
 
  const char *node_id = svn_fs_fs__id_node_id(id);
439
 
  const char *copy_id = svn_fs_fs__id_copy_id(id);
440
 
  const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
441
 
                                  node_id, copy_id);
442
 
 
443
 
  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
444
 
}
445
 
 
446
 
static APR_INLINE const char *
447
 
path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
448
 
{
449
 
  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
450
 
                     (char *)NULL);
451
 
}
452
 
 
453
 
static APR_INLINE const char *
454
 
path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
455
 
{
456
 
  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
457
 
                     PATH_EXT_CHILDREN, (char *)NULL);
458
 
}
459
 
 
460
 
static APR_INLINE const char *
461
 
path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
462
 
{
463
 
  size_t len = strlen(node_id);
464
 
  const char *node_id_minus_last_char =
465
 
    (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
466
 
  return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
467
 
                              node_id_minus_last_char, NULL);
468
 
}
469
 
 
470
 
static APR_INLINE const char *
471
 
path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
472
 
{
473
 
  const char *path;
474
 
  apr_off_t offset = 0;
475
 
 
476
 
  if (apr_file_name_get(&path, file) != APR_SUCCESS)
477
 
    path = "(unknown)";
478
 
 
479
 
  if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
480
 
    offset = -1;
481
 
 
482
 
  return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
483
 
}
484
 
 
485
 
 
486
 
 
487
 
/* Functions for working with shared transaction data. */
488
 
 
489
 
/* Return the transaction object for transaction TXN_ID from the
490
 
   transaction list of filesystem FS (which must already be locked via the
491
 
   txn_list_lock mutex).  If the transaction does not exist in the list,
492
 
   then create a new transaction object and return it (if CREATE_NEW is
493
 
   true) or return NULL (otherwise). */
494
 
static fs_fs_shared_txn_data_t *
495
 
get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
496
 
{
497
 
  fs_fs_data_t *ffd = fs->fsap_data;
498
 
  fs_fs_shared_data_t *ffsd = ffd->shared;
499
 
  fs_fs_shared_txn_data_t *txn;
500
 
 
501
 
  for (txn = ffsd->txns; txn; txn = txn->next)
502
 
    if (strcmp(txn->txn_id, txn_id) == 0)
503
 
      break;
504
 
 
505
 
  if (txn || !create_new)
506
 
    return txn;
507
 
 
508
 
  /* Use the transaction object from the (single-object) freelist,
509
 
     if one is available, or otherwise create a new object. */
510
 
  if (ffsd->free_txn)
511
 
    {
512
 
      txn = ffsd->free_txn;
513
 
      ffsd->free_txn = NULL;
514
 
    }
515
 
  else
516
 
    {
517
 
      apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
518
 
      txn = apr_palloc(subpool, sizeof(*txn));
519
 
      txn->pool = subpool;
520
 
    }
521
 
 
522
 
  assert(strlen(txn_id) < sizeof(txn->txn_id));
523
 
  apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
524
 
  txn->being_written = FALSE;
525
 
 
526
 
  /* Link this transaction into the head of the list.  We will typically
527
 
     be dealing with only one active transaction at a time, so it makes
528
 
     sense for searches through the transaction list to look at the
529
 
     newest transactions first.  */
530
 
  txn->next = ffsd->txns;
531
 
  ffsd->txns = txn;
532
 
 
533
 
  return txn;
534
 
}
535
 
 
536
 
/* Free the transaction object for transaction TXN_ID, and remove it
537
 
   from the transaction list of filesystem FS (which must already be
538
 
   locked via the txn_list_lock mutex).  Do nothing if the transaction
539
 
   does not exist. */
540
 
static void
541
 
free_shared_txn(svn_fs_t *fs, const char *txn_id)
542
 
{
543
 
  fs_fs_data_t *ffd = fs->fsap_data;
544
 
  fs_fs_shared_data_t *ffsd = ffd->shared;
545
 
  fs_fs_shared_txn_data_t *txn, *prev = NULL;
546
 
 
547
 
  for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
548
 
    if (strcmp(txn->txn_id, txn_id) == 0)
549
 
      break;
550
 
 
551
 
  if (!txn)
552
 
    return;
553
 
 
554
 
  if (prev)
555
 
    prev->next = txn->next;
556
 
  else
557
 
    ffsd->txns = txn->next;
558
 
 
559
 
  /* As we typically will be dealing with one transaction after another,
560
 
     we will maintain a single-object free list so that we can hopefully
561
 
     keep reusing the same transaction object. */
562
 
  if (!ffsd->free_txn)
563
 
    ffsd->free_txn = txn;
564
 
  else
565
 
    svn_pool_destroy(txn->pool);
566
 
}
567
 
 
568
 
 
569
 
/* Obtain a lock on the transaction list of filesystem FS, call BODY
570
 
   with FS, BATON, and POOL, and then unlock the transaction list.
571
 
   Return what BODY returned. */
572
 
static svn_error_t *
573
 
with_txnlist_lock(svn_fs_t *fs,
574
 
                  svn_error_t *(*body)(svn_fs_t *fs,
575
 
                                       const void *baton,
576
 
                                       apr_pool_t *pool),
577
 
                  const void *baton,
578
 
                  apr_pool_t *pool)
579
 
{
580
 
  fs_fs_data_t *ffd = fs->fsap_data;
581
 
  fs_fs_shared_data_t *ffsd = ffd->shared;
582
 
 
583
 
  SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
584
 
                       body(fs, baton, pool));
585
 
 
586
 
  return SVN_NO_ERROR;
587
 
}
588
 
 
589
 
 
 
112
 
 
113
 
590
114
/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
591
115
static svn_error_t *
592
116
get_lock_on_filesystem(const char *lock_filename,
593
117
                       apr_pool_t *pool)
594
118
{
595
 
  svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
596
 
 
597
 
  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
598
 
    {
599
 
      /* No lock file?  No big deal; these are just empty files
600
 
         anyway.  Create it and try again. */
601
 
      svn_error_clear(err);
602
 
      err = NULL;
603
 
 
604
 
      SVN_ERR(svn_io_file_create(lock_filename, "", pool));
605
 
      SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
606
 
    }
607
 
 
608
 
  return svn_error_trace(err);
 
119
  return svn_error_trace(svn_io__file_lock_autocreate(lock_filename, pool));
609
120
}
610
121
 
611
122
/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
619
130
  return APR_SUCCESS;
620
131
}
621
132
 
622
 
/* Obtain a write lock on the file LOCK_FILENAME (protecting with
623
 
   LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
624
 
   BATON and that subpool, destroy the subpool (releasing the write
625
 
   lock) and return what BODY returned.  If IS_GLOBAL_LOCK is set,
626
 
   set the HAS_WRITE_LOCK flag while we keep the write lock. */
 
133
/* Structure defining a file system lock to be acquired and the function
 
134
   to be executed while the lock is held.
 
135
 
 
136
   Instances of this structure may be nested to allow for multiple locks to
 
137
   be taken out before executing the user-provided body.  In that case, BODY
 
138
   and BATON of the outer instances will be with_lock and a with_lock_baton_t
 
139
   instance (transparently, no special treatment is required.).  It is
 
140
   illegal to attempt to acquire the same lock twice within the same lock
 
141
   chain or via nesting calls using separate lock chains.
 
142
 
 
143
   All instances along the chain share the same LOCK_POOL such that only one
 
144
   pool needs to be created and cleared for all locks.  We also allocate as
 
145
   much data from that lock pool as possible to minimize memory usage in
 
146
   caller pools. */
 
147
typedef struct with_lock_baton_t
 
148
{
 
149
  /* The filesystem we operate on.  Same for all instances along the chain. */
 
150
  svn_fs_t *fs;
 
151
 
 
152
  /* Mutex to complement the lock file in an APR threaded process.
 
153
     No-op object for non-threaded processes but never NULL. */
 
154
  svn_mutex__t *mutex;
 
155
 
 
156
  /* Path to the file to lock. */
 
157
  const char *lock_path;
 
158
 
 
159
  /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
 
160
  svn_boolean_t is_global_lock;
 
161
 
 
162
  /* Function body to execute after we acquired the lock.
 
163
     This may be user-provided or a nested call to with_lock(). */
 
164
  svn_error_t *(*body)(void *baton,
 
165
                       apr_pool_t *pool);
 
166
 
 
167
  /* Baton to pass to BODY; possibly NULL.
 
168
     This may be user-provided or a nested lock baton instance. */
 
169
  void *baton;
 
170
 
 
171
  /* Pool for all allocations along the lock chain and BODY.  Will hold the
 
172
     file locks and gets destroyed after the outermost BODY returned,
 
173
     releasing all file locks.
 
174
     Same for all instances along the chain. */
 
175
  apr_pool_t *lock_pool;
 
176
 
 
177
  /* TRUE, iff BODY is the user-provided body. */
 
178
  svn_boolean_t is_inner_most_lock;
 
179
 
 
180
  /* TRUE, iff this is not a nested lock.
 
181
     Then responsible for destroying LOCK_POOL. */
 
182
  svn_boolean_t is_outer_most_lock;
 
183
} with_lock_baton_t;
 
184
 
 
185
/* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY
 
186
   with BATON->BATON.  If this is the outermost lock call, release all file
 
187
   locks after the body returned.  If BATON->IS_GLOBAL_LOCK is set, set the
 
188
   HAS_WRITE_LOCK flag while we keep the write lock. */
627
189
static svn_error_t *
628
 
with_some_lock_file(svn_fs_t *fs,
629
 
                    svn_error_t *(*body)(void *baton,
630
 
                                         apr_pool_t *pool),
631
 
                    void *baton,
632
 
                    const char *lock_filename,
633
 
                    svn_boolean_t is_global_lock,
634
 
                    apr_pool_t *pool)
 
190
with_some_lock_file(with_lock_baton_t *baton)
635
191
{
636
 
  apr_pool_t *subpool = svn_pool_create(pool);
637
 
  svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
 
192
  apr_pool_t *pool = baton->lock_pool;
 
193
  svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
638
194
 
639
195
  if (!err)
640
196
    {
 
197
      svn_fs_t *fs = baton->fs;
641
198
      fs_fs_data_t *ffd = fs->fsap_data;
642
199
 
643
 
      if (is_global_lock)
 
200
      if (baton->is_global_lock)
644
201
        {
645
202
          /* set the "got the lock" flag and register reset function */
646
 
          apr_pool_cleanup_register(subpool,
 
203
          apr_pool_cleanup_register(pool,
647
204
                                    ffd,
648
205
                                    reset_lock_flag,
649
206
                                    apr_pool_cleanup_null);
652
209
 
653
210
      /* nobody else will modify the repo state
654
211
         => read HEAD & pack info once */
655
 
      if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
656
 
        SVN_ERR(update_min_unpacked_rev(fs, pool));
657
 
      SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
658
 
                           pool));
659
 
      err = body(baton, subpool);
 
212
      if (baton->is_inner_most_lock)
 
213
        {
 
214
          if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
 
215
            err = svn_fs_fs__update_min_unpacked_rev(fs, pool);
 
216
          if (!err)
 
217
            err = get_youngest(&ffd->youngest_rev_cache, fs, pool);
 
218
        }
 
219
 
 
220
      if (!err)
 
221
        err = baton->body(baton->baton, pool);
660
222
    }
661
223
 
662
 
  svn_pool_destroy(subpool);
 
224
  if (baton->is_outer_most_lock)
 
225
    svn_pool_destroy(pool);
663
226
 
664
227
  return svn_error_trace(err);
665
228
}
666
229
 
 
230
/* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
 
231
 
 
232
   POOL is unused here and only provided for signature compatibility with
 
233
   WITH_LOCK_BATON_T.BODY. */
 
234
static svn_error_t *
 
235
with_lock(void *baton,
 
236
          apr_pool_t *pool)
 
237
{
 
238
  with_lock_baton_t *lock_baton = baton;
 
239
  SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
 
240
 
 
241
  return SVN_NO_ERROR;
 
242
}
 
243
 
 
244
/* Enum identifying a filesystem lock. */
 
245
typedef enum lock_id_t
 
246
{
 
247
  write_lock,
 
248
  txn_lock,
 
249
  pack_lock
 
250
} lock_id_t;
 
251
 
 
252
/* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK
 
253
   according to the LOCK_ID.  All other members of BATON must already be
 
254
   valid. */
 
255
static void
 
256
init_lock_baton(with_lock_baton_t *baton,
 
257
                lock_id_t lock_id)
 
258
{
 
259
  fs_fs_data_t *ffd = baton->fs->fsap_data;
 
260
  fs_fs_shared_data_t *ffsd = ffd->shared;
 
261
 
 
262
  switch (lock_id)
 
263
    {
 
264
    case write_lock:
 
265
      baton->mutex = ffsd->fs_write_lock;
 
266
      baton->lock_path = svn_fs_fs__path_lock(baton->fs, baton->lock_pool);
 
267
      baton->is_global_lock = TRUE;
 
268
      break;
 
269
 
 
270
    case txn_lock:
 
271
      baton->mutex = ffsd->txn_current_lock;
 
272
      baton->lock_path = svn_fs_fs__path_txn_current_lock(baton->fs,
 
273
                                                          baton->lock_pool);
 
274
      baton->is_global_lock = FALSE;
 
275
      break;
 
276
 
 
277
    case pack_lock:
 
278
      baton->mutex = ffsd->fs_pack_lock;
 
279
      baton->lock_path = svn_fs_fs__path_pack_lock(baton->fs,
 
280
                                                   baton->lock_pool);
 
281
      baton->is_global_lock = FALSE;
 
282
      break;
 
283
    }
 
284
}
 
285
 
 
286
/* Return the  baton for the innermost lock of a (potential) lock chain.
 
287
   The baton shall take out LOCK_ID from FS and execute BODY with BATON
 
288
   while the lock is being held.  Allocate the result in a sub-pool of POOL.
 
289
 */
 
290
static with_lock_baton_t *
 
291
create_lock_baton(svn_fs_t *fs,
 
292
                  lock_id_t lock_id,
 
293
                  svn_error_t *(*body)(void *baton,
 
294
                                       apr_pool_t *pool),
 
295
                  void *baton,
 
296
                  apr_pool_t *pool)
 
297
{
 
298
  /* Allocate everything along the lock chain into a single sub-pool.
 
299
     This minimizes memory usage and cleanup overhead. */
 
300
  apr_pool_t *lock_pool = svn_pool_create(pool);
 
301
  with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
 
302
 
 
303
  /* Store parameters. */
 
304
  result->fs = fs;
 
305
  result->body = body;
 
306
  result->baton = baton;
 
307
 
 
308
  /* File locks etc. will use this pool as well for easy cleanup. */
 
309
  result->lock_pool = lock_pool;
 
310
 
 
311
  /* Right now, we are the first, (only, ) and last struct in the chain. */
 
312
  result->is_inner_most_lock = TRUE;
 
313
  result->is_outer_most_lock = TRUE;
 
314
 
 
315
  /* Select mutex and lock file path depending on LOCK_ID.
 
316
     Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
 
317
  init_lock_baton(result, lock_id);
 
318
 
 
319
  return result;
 
320
}
 
321
 
 
322
/* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
 
323
 *
 
324
 * That means, when you create a lock chain, start with the last / innermost
 
325
 * lock to take out and add the first / outermost lock last.
 
326
 */
 
327
static with_lock_baton_t *
 
328
chain_lock_baton(lock_id_t lock_id,
 
329
                 with_lock_baton_t *nested)
 
330
{
 
331
  /* Use the same pool for batons along the lock chain. */
 
332
  apr_pool_t *lock_pool = nested->lock_pool;
 
333
  with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
 
334
 
 
335
  /* All locks along the chain operate on the same FS. */
 
336
  result->fs = nested->fs;
 
337
 
 
338
  /* Execution of this baton means acquiring the nested lock and its
 
339
     execution. */
 
340
  result->body = with_lock;
 
341
  result->baton = nested;
 
342
 
 
343
  /* Shared among all locks along the chain. */
 
344
  result->lock_pool = lock_pool;
 
345
 
 
346
  /* We are the new outermost lock but surely not the innermost lock. */
 
347
  result->is_inner_most_lock = FALSE;
 
348
  result->is_outer_most_lock = TRUE;
 
349
  nested->is_outer_most_lock = FALSE;
 
350
 
 
351
  /* Select mutex and lock file path depending on LOCK_ID.
 
352
     Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
 
353
  init_lock_baton(result, lock_id);
 
354
 
 
355
  return result;
 
356
}
 
357
 
667
358
svn_error_t *
668
359
svn_fs_fs__with_write_lock(svn_fs_t *fs,
669
360
                           svn_error_t *(*body)(void *baton,
671
362
                           void *baton,
672
363
                           apr_pool_t *pool)
673
364
{
674
 
  fs_fs_data_t *ffd = fs->fsap_data;
675
 
  fs_fs_shared_data_t *ffsd = ffd->shared;
676
 
 
677
 
  SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
678
 
                       with_some_lock_file(fs, body, baton,
679
 
                                           path_lock(fs, pool),
680
 
                                           TRUE,
681
 
                                           pool));
682
 
 
683
 
  return SVN_NO_ERROR;
684
 
}
685
 
 
686
 
/* Run BODY (with BATON and POOL) while the txn-current file
687
 
   of FS is locked. */
688
 
static svn_error_t *
689
 
with_txn_current_lock(svn_fs_t *fs,
690
 
                      svn_error_t *(*body)(void *baton,
691
 
                                           apr_pool_t *pool),
692
 
                      void *baton,
693
 
                      apr_pool_t *pool)
694
 
{
695
 
  fs_fs_data_t *ffd = fs->fsap_data;
696
 
  fs_fs_shared_data_t *ffsd = ffd->shared;
697
 
 
698
 
  SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
699
 
                       with_some_lock_file(fs, body, baton,
700
 
                                           path_txn_current_lock(fs, pool),
701
 
                                           FALSE,
702
 
                                           pool));
703
 
 
704
 
  return SVN_NO_ERROR;
705
 
}
706
 
 
707
 
/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
708
 
   which see. */
709
 
struct unlock_proto_rev_baton
710
 
{
711
 
  const char *txn_id;
712
 
  void *lockcookie;
713
 
};
714
 
 
715
 
/* Callback used in the implementation of unlock_proto_rev(). */
716
 
static svn_error_t *
717
 
unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
718
 
{
719
 
  const struct unlock_proto_rev_baton *b = baton;
720
 
  const char *txn_id = b->txn_id;
721
 
  apr_file_t *lockfile = b->lockcookie;
722
 
  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
723
 
  apr_status_t apr_err;
724
 
 
725
 
  if (!txn)
726
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
727
 
                             _("Can't unlock unknown transaction '%s'"),
728
 
                             txn_id);
729
 
  if (!txn->being_written)
730
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
731
 
                             _("Can't unlock nonlocked transaction '%s'"),
732
 
                             txn_id);
733
 
 
734
 
  apr_err = apr_file_unlock(lockfile);
735
 
  if (apr_err)
736
 
    return svn_error_wrap_apr
737
 
      (apr_err,
738
 
       _("Can't unlock prototype revision lockfile for transaction '%s'"),
739
 
       txn_id);
740
 
  apr_err = apr_file_close(lockfile);
741
 
  if (apr_err)
742
 
    return svn_error_wrap_apr
743
 
      (apr_err,
744
 
       _("Can't close prototype revision lockfile for transaction '%s'"),
745
 
       txn_id);
746
 
 
747
 
  txn->being_written = FALSE;
748
 
 
749
 
  return SVN_NO_ERROR;
750
 
}
751
 
 
752
 
/* Unlock the prototype revision file for transaction TXN_ID in filesystem
753
 
   FS using cookie LOCKCOOKIE.  The original prototype revision file must
754
 
   have been closed _before_ calling this function.
755
 
 
756
 
   Perform temporary allocations in POOL. */
757
 
static svn_error_t *
758
 
unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
759
 
                 apr_pool_t *pool)
760
 
{
761
 
  struct unlock_proto_rev_baton b;
762
 
 
763
 
  b.txn_id = txn_id;
764
 
  b.lockcookie = lockcookie;
765
 
  return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
766
 
}
767
 
 
768
 
/* Same as unlock_proto_rev(), but requires that the transaction list
769
 
   lock is already held. */
770
 
static svn_error_t *
771
 
unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
772
 
                             void *lockcookie,
773
 
                             apr_pool_t *pool)
774
 
{
775
 
  struct unlock_proto_rev_baton b;
776
 
 
777
 
  b.txn_id = txn_id;
778
 
  b.lockcookie = lockcookie;
779
 
  return unlock_proto_rev_body(fs, &b, pool);
780
 
}
781
 
 
782
 
/* A structure used by get_writable_proto_rev() and
783
 
   get_writable_proto_rev_body(), which see. */
784
 
struct get_writable_proto_rev_baton
785
 
{
786
 
  apr_file_t **file;
787
 
  void **lockcookie;
788
 
  const char *txn_id;
789
 
};
790
 
 
791
 
/* Callback used in the implementation of get_writable_proto_rev(). */
792
 
static svn_error_t *
793
 
get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
794
 
{
795
 
  const struct get_writable_proto_rev_baton *b = baton;
796
 
  apr_file_t **file = b->file;
797
 
  void **lockcookie = b->lockcookie;
798
 
  const char *txn_id = b->txn_id;
799
 
  svn_error_t *err;
800
 
  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
801
 
 
802
 
  /* First, ensure that no thread in this process (including this one)
803
 
     is currently writing to this transaction's proto-rev file. */
804
 
  if (txn->being_written)
805
 
    return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
806
 
                             _("Cannot write to the prototype revision file "
807
 
                               "of transaction '%s' because a previous "
808
 
                               "representation is currently being written by "
809
 
                               "this process"),
810
 
                             txn_id);
811
 
 
812
 
 
813
 
  /* We know that no thread in this process is writing to the proto-rev
814
 
     file, and by extension, that no thread in this process is holding a
815
 
     lock on the prototype revision lock file.  It is therefore safe
816
 
     for us to attempt to lock this file, to see if any other process
817
 
     is holding a lock. */
818
 
 
819
 
  {
820
 
    apr_file_t *lockfile;
821
 
    apr_status_t apr_err;
822
 
    const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
823
 
 
824
 
    /* Open the proto-rev lockfile, creating it if necessary, as it may
825
 
       not exist if the transaction dates from before the lockfiles were
826
 
       introduced.
827
 
 
828
 
       ### We'd also like to use something like svn_io_file_lock2(), but
829
 
           that forces us to create a subpool just to be able to unlock
830
 
           the file, which seems a waste. */
831
 
    SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
832
 
                             APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
833
 
 
834
 
    apr_err = apr_file_lock(lockfile,
835
 
                            APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
836
 
    if (apr_err)
837
 
      {
838
 
        svn_error_clear(svn_io_file_close(lockfile, pool));
839
 
 
840
 
        if (APR_STATUS_IS_EAGAIN(apr_err))
841
 
          return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
842
 
                                   _("Cannot write to the prototype revision "
843
 
                                     "file of transaction '%s' because a "
844
 
                                     "previous representation is currently "
845
 
                                     "being written by another process"),
846
 
                                   txn_id);
847
 
 
848
 
        return svn_error_wrap_apr(apr_err,
849
 
                                  _("Can't get exclusive lock on file '%s'"),
850
 
                                  svn_dirent_local_style(lockfile_path, pool));
851
 
      }
852
 
 
853
 
    *lockcookie = lockfile;
854
 
  }
855
 
 
856
 
  /* We've successfully locked the transaction; mark it as such. */
857
 
  txn->being_written = TRUE;
858
 
 
859
 
 
860
 
  /* Now open the prototype revision file and seek to the end. */
861
 
  err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
862
 
                         APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
863
 
 
864
 
  /* You might expect that we could dispense with the following seek
865
 
     and achieve the same thing by opening the file using APR_APPEND.
866
 
     Unfortunately, APR's buffered file implementation unconditionally
867
 
     places its initial file pointer at the start of the file (even for
868
 
     files opened with APR_APPEND), so we need this seek to reconcile
869
 
     the APR file pointer to the OS file pointer (since we need to be
870
 
     able to read the current file position later). */
871
 
  if (!err)
872
 
    {
873
 
      apr_off_t offset = 0;
874
 
      err = svn_io_file_seek(*file, APR_END, &offset, pool);
875
 
    }
876
 
 
877
 
  if (err)
878
 
    {
879
 
      err = svn_error_compose_create(
880
 
              err,
881
 
              unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
882
 
 
883
 
      *lockcookie = NULL;
884
 
    }
885
 
 
886
 
  return svn_error_trace(err);
887
 
}
888
 
 
889
 
/* Get a handle to the prototype revision file for transaction TXN_ID in
890
 
   filesystem FS, and lock it for writing.  Return FILE, a file handle
891
 
   positioned at the end of the file, and LOCKCOOKIE, a cookie that
892
 
   should be passed to unlock_proto_rev() to unlock the file once FILE
893
 
   has been closed.
894
 
 
895
 
   If the prototype revision file is already locked, return error
896
 
   SVN_ERR_FS_REP_BEING_WRITTEN.
897
 
 
898
 
   Perform all allocations in POOL. */
899
 
static svn_error_t *
900
 
get_writable_proto_rev(apr_file_t **file,
901
 
                       void **lockcookie,
902
 
                       svn_fs_t *fs, const char *txn_id,
903
 
                       apr_pool_t *pool)
904
 
{
905
 
  struct get_writable_proto_rev_baton b;
906
 
 
907
 
  b.file = file;
908
 
  b.lockcookie = lockcookie;
909
 
  b.txn_id = txn_id;
910
 
 
911
 
  return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
912
 
}
913
 
 
914
 
/* Callback used in the implementation of purge_shared_txn(). */
915
 
static svn_error_t *
916
 
purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
917
 
{
918
 
  const char *txn_id = baton;
919
 
 
920
 
  free_shared_txn(fs, txn_id);
921
 
  svn_fs_fs__reset_txn_caches(fs);
922
 
 
923
 
  return SVN_NO_ERROR;
924
 
}
925
 
 
926
 
/* Purge the shared data for transaction TXN_ID in filesystem FS.
927
 
   Perform all allocations in POOL. */
928
 
static svn_error_t *
929
 
purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
930
 
{
931
 
  return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
932
 
}
 
365
  return svn_error_trace(
 
366
           with_lock(create_lock_baton(fs, write_lock, body, baton, pool),
 
367
                     pool));
 
368
}
 
369
 
 
370
svn_error_t *
 
371
svn_fs_fs__with_pack_lock(svn_fs_t *fs,
 
372
                          svn_error_t *(*body)(void *baton,
 
373
                                               apr_pool_t *pool),
 
374
                          void *baton,
 
375
                          apr_pool_t *pool)
 
376
{
 
377
  return svn_error_trace(
 
378
           with_lock(create_lock_baton(fs, pack_lock, body, baton, pool),
 
379
                     pool));
 
380
}
 
381
 
 
382
svn_error_t *
 
383
svn_fs_fs__with_txn_current_lock(svn_fs_t *fs,
 
384
                                 svn_error_t *(*body)(void *baton,
 
385
                                                      apr_pool_t *pool),
 
386
                                 void *baton,
 
387
                                 apr_pool_t *pool)
 
388
{
 
389
  return svn_error_trace(
 
390
           with_lock(create_lock_baton(fs, txn_lock, body, baton, pool),
 
391
                     pool));
 
392
}
 
393
 
 
394
svn_error_t *
 
395
svn_fs_fs__with_all_locks(svn_fs_t *fs,
 
396
                          svn_error_t *(*body)(void *baton,
 
397
                                               apr_pool_t *pool),
 
398
                          void *baton,
 
399
                          apr_pool_t *pool)
 
400
{
 
401
  fs_fs_data_t *ffd = fs->fsap_data;
 
402
 
 
403
  /* Be sure to use the correct lock ordering as documented in
 
404
     fs_fs_shared_data_t.  The lock chain is being created in
 
405
     innermost (last to acquire) -> outermost (first to acquire) order. */
 
406
  with_lock_baton_t *lock_baton
 
407
    = create_lock_baton(fs, write_lock, body, baton, pool);
 
408
 
 
409
  if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
 
410
    lock_baton = chain_lock_baton(pack_lock, lock_baton);
 
411
 
 
412
  if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
 
413
    lock_baton = chain_lock_baton(txn_lock, lock_baton);
 
414
 
 
415
  return svn_error_trace(with_lock(lock_baton, pool));
 
416
}
 
417
 
 
418
 
933
419
 
934
420
 
935
421
 
936
 
/* Fetch the current offset of FILE into *OFFSET_P. */
937
 
static svn_error_t *
938
 
get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
939
 
{
940
 
  apr_off_t offset;
941
 
 
942
 
  /* Note that, for buffered files, one (possibly surprising) side-effect
943
 
     of this call is to flush any unwritten data to disk. */
944
 
  offset = 0;
945
 
  SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
946
 
  *offset_p = offset;
947
 
 
948
 
  return SVN_NO_ERROR;
949
 
}
950
 
 
951
 
 
952
 
/* Check that BUF, a nul-terminated buffer of text from file PATH,
953
 
   contains only digits at OFFSET and beyond, raising an error if not.
954
 
   TITLE contains a user-visible description of the file, usually the
955
 
   short file name.
956
 
 
957
 
   Uses POOL for temporary allocation. */
958
 
static svn_error_t *
959
 
check_file_buffer_numeric(const char *buf, apr_off_t offset,
960
 
                          const char *path, const char *title,
961
 
                          apr_pool_t *pool)
962
 
{
963
 
  const char *p;
964
 
 
965
 
  for (p = buf + offset; *p; p++)
966
 
    if (!svn_ctype_isdigit(*p))
967
 
      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
968
 
        _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
969
 
        title, svn_dirent_local_style(path, pool), *p, buf);
970
 
 
971
 
  return SVN_NO_ERROR;
972
 
}
973
 
 
974
422
/* Check that BUF, a nul-terminated buffer of text from format file PATH,
975
423
   contains only digits at OFFSET and beyond, raising an error if not.
976
424
 
979
427
check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
980
428
                                 const char *path, apr_pool_t *pool)
981
429
{
982
 
  return check_file_buffer_numeric(buf, offset, path, "Format", pool);
 
430
  return svn_fs_fs__check_file_buffer_numeric(buf, offset, path, "Format",
 
431
                                              pool);
983
432
}
984
433
 
985
434
/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
1008
457
}
1009
458
 
1010
459
/* Read the format number and maximum number of files per directory
1011
 
   from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
1012
 
   respectively.
 
460
   from PATH and return them in *PFORMAT, *MAX_FILES_PER_DIR and
 
461
   USE_LOG_ADDRESSIONG respectively.
1013
462
 
1014
463
   *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
1015
464
   will be set to zero if a linear scheme should be used.
 
465
   *USE_LOG_ADDRESSIONG is obtained from the 'addressing' format option,
 
466
   and will be set to FALSE for physical addressing.
1016
467
 
1017
468
   Use POOL for temporary allocation. */
1018
469
static svn_error_t *
1019
 
read_format(int *pformat, int *max_files_per_dir,
1020
 
            const char *path, apr_pool_t *pool)
 
470
read_format(int *pformat,
 
471
            int *max_files_per_dir,
 
472
            svn_boolean_t *use_log_addressing,
 
473
            const char *path,
 
474
            apr_pool_t *pool)
1021
475
{
1022
476
  svn_error_t *err;
1023
477
  svn_stream_t *stream;
1062
516
 
1063
517
  /* Set the default values for anything that can be set via an option. */
1064
518
  *max_files_per_dir = 0;
 
519
  *use_log_addressing = FALSE;
1065
520
 
1066
521
  /* Read any options. */
1067
522
  while (!eos)
1088
543
            }
1089
544
        }
1090
545
 
 
546
      if (*pformat >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT &&
 
547
          strncmp(buf->data, "addressing ", 11) == 0)
 
548
        {
 
549
          if (strcmp(buf->data + 11, "physical") == 0)
 
550
            {
 
551
              *use_log_addressing = FALSE;
 
552
              continue;
 
553
            }
 
554
 
 
555
          if (strcmp(buf->data + 11, "logical") == 0)
 
556
            {
 
557
              *use_log_addressing = TRUE;
 
558
              continue;
 
559
            }
 
560
        }
 
561
 
1091
562
      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1092
563
         _("'%s' contains invalid filesystem format option '%s'"),
1093
564
         svn_dirent_local_style(path, pool), buf->data);
1094
565
    }
1095
566
 
 
567
  /* Non-sharded repositories never use logical addressing.
 
568
   * If the format file is inconsistent in that respect, something
 
569
   * probably went wrong.
 
570
   */
 
571
  if (*use_log_addressing && !*max_files_per_dir)
 
572
    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
 
573
       _("'%s' specifies logical addressing for a non-sharded repository"),
 
574
       svn_dirent_local_style(path, pool));
 
575
 
1096
576
  return SVN_NO_ERROR;
1097
577
}
1098
578
 
1099
 
/* Write the format number and maximum number of files per directory
1100
 
   to a new format file in PATH, possibly expecting to overwrite a
1101
 
   previously existing file.
 
579
/* Write the format number, maximum number of files per directory and
 
580
   the addressing scheme to a new format file in PATH, possibly expecting
 
581
   to overwrite a previously existing file.
1102
582
 
1103
583
   Use POOL for temporary allocation. */
1104
 
static svn_error_t *
1105
 
write_format(const char *path, int format, int max_files_per_dir,
1106
 
             svn_boolean_t overwrite, apr_pool_t *pool)
 
584
svn_error_t *
 
585
svn_fs_fs__write_format(svn_fs_t *fs,
 
586
                        svn_boolean_t overwrite,
 
587
                        apr_pool_t *pool)
1107
588
{
1108
589
  svn_stringbuf_t *sb;
1109
 
 
1110
 
  SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
1111
 
 
1112
 
  sb = svn_stringbuf_createf(pool, "%d\n", format);
1113
 
 
1114
 
  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
 
590
  fs_fs_data_t *ffd = fs->fsap_data;
 
591
  const char *path = path_format(fs, pool);
 
592
 
 
593
  SVN_ERR_ASSERT(1 <= ffd->format
 
594
                 && ffd->format <= SVN_FS_FS__FORMAT_NUMBER);
 
595
 
 
596
  sb = svn_stringbuf_createf(pool, "%d\n", ffd->format);
 
597
 
 
598
  if (ffd->format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1115
599
    {
1116
 
      if (max_files_per_dir)
 
600
      if (ffd->max_files_per_dir)
1117
601
        svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
1118
 
                                                  max_files_per_dir));
 
602
                                                  ffd->max_files_per_dir));
1119
603
      else
1120
604
        svn_stringbuf_appendcstr(sb, "layout linear\n");
1121
605
    }
1122
606
 
 
607
  if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
 
608
    {
 
609
      if (ffd->use_log_addressing)
 
610
        svn_stringbuf_appendcstr(sb, "addressing logical\n");
 
611
      else
 
612
        svn_stringbuf_appendcstr(sb, "addressing physical\n");
 
613
    }
 
614
 
1123
615
  /* svn_io_write_version_file() does a load of magic to allow it to
1124
616
     replace version files that already exist.  We only need to do
1125
617
     that when we're allowed to overwrite an existing file. */
1130
622
    }
1131
623
  else
1132
624
    {
1133
 
      const char *path_tmp;
1134
 
 
1135
 
      SVN_ERR(svn_io_write_unique(&path_tmp,
1136
 
                                  svn_dirent_dirname(path, pool),
1137
 
                                  sb->data, sb->len,
1138
 
                                  svn_io_file_del_none, pool));
1139
 
 
1140
 
      /* rename the temp file as the real destination */
1141
 
      SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
 
625
      SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len,
 
626
                                  NULL /* copy_perms_path */, pool));
1142
627
    }
1143
628
 
1144
629
  /* And set the perms to make it read only */
1152
637
  return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
1153
638
}
1154
639
 
 
640
/* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
 
641
 * the range of what the current system may address in RAM and it is a
 
642
 * power of 2.  Assume that the element size within the block is ITEM_SIZE.
 
643
 * Use SCRATCH_POOL for temporary allocations.
 
644
 */
 
645
static svn_error_t *
 
646
verify_block_size(apr_int64_t block_size,
 
647
                  apr_size_t item_size,
 
648
                  const char *name,
 
649
                  apr_pool_t *scratch_pool
 
650
                 )
 
651
{
 
652
  /* Limit range. */
 
653
  if (block_size <= 0)
 
654
    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
 
655
                             _("%s is too small for fsfs.conf setting '%s'."),
 
656
                             apr_psprintf(scratch_pool,
 
657
                                          "%" APR_INT64_T_FMT,
 
658
                                          block_size),
 
659
                             name);
 
660
 
 
661
  if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
 
662
    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
 
663
                             _("%s is too large for fsfs.conf setting '%s'."),
 
664
                             apr_psprintf(scratch_pool,
 
665
                                          "%" APR_INT64_T_FMT,
 
666
                                          block_size),
 
667
                             name);
 
668
 
 
669
  /* Ensure it is a power of two.
 
670
   * For positive X,  X & (X-1) will reset the lowest bit set.
 
671
   * If the result is 0, at most one bit has been set. */
 
672
  if (0 != (block_size & (block_size - 1)))
 
673
    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
 
674
                             _("%s is invalid for fsfs.conf setting '%s' "
 
675
                               "because it is not a power of 2."),
 
676
                             apr_psprintf(scratch_pool,
 
677
                                          "%" APR_INT64_T_FMT,
 
678
                                          block_size),
 
679
                             name);
 
680
 
 
681
  return SVN_NO_ERROR;
 
682
}
 
683
 
1155
684
/* Read the configuration information of the file system at FS_PATH
1156
 
 * and set the respective values in FFD.  Use POOL for allocations.
 
685
 * and set the respective values in FFD.  Use pools as usual.
1157
686
 */
1158
687
static svn_error_t *
1159
688
read_config(fs_fs_data_t *ffd,
1160
689
            const char *fs_path,
1161
 
            apr_pool_t *pool)
 
690
            apr_pool_t *result_pool,
 
691
            apr_pool_t *scratch_pool)
1162
692
{
1163
 
  SVN_ERR(svn_config_read3(&ffd->config,
1164
 
                           svn_dirent_join(fs_path, PATH_CONFIG, pool),
1165
 
                           FALSE, FALSE, FALSE, pool));
 
693
  svn_config_t *config;
 
694
 
 
695
  SVN_ERR(svn_config_read3(&config,
 
696
                           svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
 
697
                           FALSE, FALSE, FALSE, scratch_pool));
1166
698
 
1167
699
  /* Initialize ffd->rep_sharing_allowed. */
1168
700
  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1169
 
    SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed,
 
701
    SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
1170
702
                                CONFIG_SECTION_REP_SHARING,
1171
703
                                CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
1172
704
  else
1175
707
  /* Initialize deltification settings in ffd. */
1176
708
  if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
1177
709
    {
1178
 
      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories,
 
710
      apr_int64_t compression_level;
 
711
 
 
712
      SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories,
1179
713
                                  CONFIG_SECTION_DELTIFICATION,
1180
714
                                  CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
1181
 
                                  FALSE));
1182
 
      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties,
 
715
                                  TRUE));
 
716
      SVN_ERR(svn_config_get_bool(config, &ffd->deltify_properties,
1183
717
                                  CONFIG_SECTION_DELTIFICATION,
1184
718
                                  CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
1185
 
                                  FALSE));
1186
 
      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk,
 
719
                                  TRUE));
 
720
      SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
1187
721
                                   CONFIG_SECTION_DELTIFICATION,
1188
722
                                   CONFIG_OPTION_MAX_DELTIFICATION_WALK,
1189
723
                                   SVN_FS_FS_MAX_DELTIFICATION_WALK));
1190
 
      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification,
 
724
      SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
1191
725
                                   CONFIG_SECTION_DELTIFICATION,
1192
726
                                   CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
1193
727
                                   SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
 
728
 
 
729
      SVN_ERR(svn_config_get_int64(config, &compression_level,
 
730
                                   CONFIG_SECTION_DELTIFICATION,
 
731
                                   CONFIG_OPTION_COMPRESSION_LEVEL,
 
732
                                   SVN_DELTA_COMPRESSION_LEVEL_DEFAULT));
 
733
      ffd->delta_compression_level
 
734
        = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level),
 
735
                   SVN_DELTA_COMPRESSION_LEVEL_MAX);
1194
736
    }
1195
737
  else
1196
738
    {
1198
740
      ffd->deltify_properties = FALSE;
1199
741
      ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
1200
742
      ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
 
743
      ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
1201
744
    }
1202
745
 
1203
746
  /* Initialize revprop packing settings in ffd. */
1204
747
  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
1205
748
    {
1206
 
      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops,
 
749
      SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
1207
750
                                  CONFIG_SECTION_PACKED_REVPROPS,
1208
751
                                  CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
1209
752
                                  FALSE));
1210
 
      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size,
 
753
      SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
1211
754
                                   CONFIG_SECTION_PACKED_REVPROPS,
1212
755
                                   CONFIG_OPTION_REVPROP_PACK_SIZE,
1213
756
                                   ffd->compress_packed_revprops
1214
 
                                       ? 0x100
1215
 
                                       : 0x40));
 
757
                                       ? 0x10
 
758
                                       : 0x4));
1216
759
 
1217
760
      ffd->revprop_pack_size *= 1024;
1218
761
    }
1222
765
      ffd->compress_packed_revprops = FALSE;
1223
766
    }
1224
767
 
 
768
  if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
 
769
    {
 
770
      SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
 
771
                                   CONFIG_SECTION_IO,
 
772
                                   CONFIG_OPTION_BLOCK_SIZE,
 
773
                                   64));
 
774
      SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
 
775
                                   CONFIG_SECTION_IO,
 
776
                                   CONFIG_OPTION_L2P_PAGE_SIZE,
 
777
                                   0x2000));
 
778
      SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
 
779
                                   CONFIG_SECTION_IO,
 
780
                                   CONFIG_OPTION_P2L_PAGE_SIZE,
 
781
                                   0x400));
 
782
 
 
783
      /* Don't accept unreasonable or illegal values.
 
784
       * Block size and P2L page size are in kbytes;
 
785
       * L2P blocks are arrays of apr_off_t. */
 
786
      SVN_ERR(verify_block_size(ffd->block_size, 0x400,
 
787
                                CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
 
788
      SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
 
789
                                CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
 
790
      SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
 
791
                                CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
 
792
 
 
793
      /* convert kBytes to bytes */
 
794
      ffd->block_size *= 0x400;
 
795
      ffd->p2l_page_size *= 0x400;
 
796
      /* L2P pages are in entries - not in (k)Bytes */
 
797
    }
 
798
  else
 
799
    {
 
800
      /* should be irrelevant but we initialize them anyway */
 
801
      ffd->block_size = 0x1000; /* Matches default APR file buffer size. */
 
802
      ffd->l2p_page_size = 0x2000;    /* Matches above default. */
 
803
      ffd->p2l_page_size = 0x100000;  /* Matches above default in bytes. */
 
804
    }
 
805
 
 
806
  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
 
807
    {
 
808
      SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
 
809
                                  CONFIG_SECTION_DEBUG,
 
810
                                  CONFIG_OPTION_PACK_AFTER_COMMIT,
 
811
                                  FALSE));
 
812
    }
 
813
  else
 
814
    {
 
815
      ffd->pack_after_commit = FALSE;
 
816
    }
 
817
 
 
818
  /* memcached configuration */
 
819
  SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
 
820
                                               result_pool, scratch_pool));
 
821
 
 
822
  SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
 
823
                              CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
 
824
                              FALSE));
 
825
 
1225
826
  return SVN_NO_ERROR;
1226
827
}
1227
828
 
1286
887
"###"                                                                        NL
1287
888
"### The following parameter enables deltification for directories. It can"  NL
1288
889
"### be switched on and off at will, but for best space-saving results"      NL
1289
 
"### should be enabled consistently over the life of the repository."        NL
 
890
"### should be enabled consistently over the lifetime of the repository."    NL
1290
891
"### Repositories containing large directories will benefit greatly."        NL
1291
 
"### In rarely read repositories, the I/O overhead may be significant as"    NL
1292
 
"### cache hit rates will most likely be low"                                NL
1293
 
"### directory deltification is disabled by default."                        NL
1294
 
"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false"                       NL
 
892
"### In rarely accessed repositories, the I/O overhead may be significant"   NL
 
893
"### as caches will most likely be low."                                     NL
 
894
"### directory deltification is enabled by default."                         NL
 
895
"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = true"                        NL
1295
896
"###"                                                                        NL
1296
897
"### The following parameter enables deltification for properties on files"  NL
1297
898
"### and directories.  Overall, this is a minor tuning option but can save"  NL
1298
899
"### some disk space if you merge frequently or frequently change node"      NL
1299
900
"### properties.  You should not activate this if rep-sharing has been"      NL
1300
901
"### disabled because this may result in a net increase in repository size." NL
1301
 
"### property deltification is disabled by default."                         NL
1302
 
"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false"                     NL
 
902
"### property deltification is enabled by default."                          NL
 
903
"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = true"                      NL
1303
904
"###"                                                                        NL
1304
905
"### During commit, the server may need to walk the whole change history of" NL
1305
906
"### of a given node to find a suitable deltification base.  This linear"    NL
1331
932
"### exclusive use of skip-deltas (as in pre-1.8)."                          NL
1332
933
"### For 1.8, the default value is 16; earlier versions use 1."              NL
1333
934
"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
 
935
"###"                                                                        NL
 
936
"### After deltification, we compress the data through zlib to minimize on-" NL
 
937
"### disk size.  That can be an expensive and ineffective process.  This"    NL
 
938
"### setting controls the usage of zlib in future revisions."                NL
 
939
"### Revisions with highly compressible data in them may shrink in size"     NL
 
940
"### if the setting is increased but may take much longer to commit.  The"   NL
 
941
"### time taken to uncompress that data again is widely independent of the"  NL
 
942
"### compression level."                                                     NL
 
943
"### Compression will be ineffective if the incoming content is already"     NL
 
944
"### highly compressed.  In that case, disabling the compression entirely"   NL
 
945
"### will speed up commits as well as reading the data.  Repositories with"  NL
 
946
"### many small compressible files (source code) but also a high percentage" NL
 
947
"### of large incompressible ones (artwork) may benefit from compression"    NL
 
948
"### levels lowered to e.g. 1."                                              NL
 
949
"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL
 
950
"### and 0 disabling it altogether."                                         NL
 
951
"### The default value is 5."                                                NL
 
952
"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5"                                  NL
1334
953
""                                                                           NL
1335
954
"[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
1336
955
"### This parameter controls the size (in kBytes) of packed revprop files."  NL
1340
959
"### much larger than the limit set here.  The threshold will be applied"    NL
1341
960
"### before optional compression takes place."                               NL
1342
961
"### Large values will reduce disk space usage at the expense of increased"  NL
1343
 
"### latency and CPU usage reading and changing individual revprops.  They"  NL
1344
 
"### become an advantage when revprop caching has been enabled because a"    NL
1345
 
"### lot of data can be read in one go.  Values smaller than 4 kByte will"   NL
1346
 
"### not improve latency any further and quickly render revprop packing"     NL
1347
 
"### ineffective."                                                           NL
1348
 
"### revprop-pack-size is 64 kBytes by default for non-compressed revprop"   NL
1349
 
"### pack files and 256 kBytes when compression has been enabled."           NL
1350
 
"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64"                                 NL
 
962
"### latency and CPU usage reading and changing individual revprops."        NL
 
963
"### Values smaller than 4 kByte will not improve latency any further and "  NL
 
964
"### quickly render revprop packing ineffective."                            NL
 
965
"### revprop-pack-size is 4 kBytes by default for non-compressed revprop"    NL
 
966
"### pack files and 16 kBytes when compression has been enabled."            NL
 
967
"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 4"                                  NL
1351
968
"###"                                                                        NL
1352
969
"### To save disk space, packed revprop files may be compressed.  Standard"  NL
1353
970
"### revprops tend to allow for very effective compression.  Reading and"    NL
1354
 
"### even more so writing, become significantly more CPU intensive.  With"   NL
1355
 
"### revprop caching enabled, the overhead can be offset by reduced I/O"     NL
1356
 
"### unless you often modify revprops after packing."                        NL
 
971
"### even more so writing, become significantly more CPU intensive."         NL
1357
972
"### Compressing packed revprops is disabled by default."                    NL
1358
973
"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false"                       NL
 
974
""                                                                           NL
 
975
"[" CONFIG_SECTION_IO "]"                                                    NL
 
976
"### Parameters in this section control the data access granularity in"      NL
 
977
"### format 7 repositories and later.  The defaults should translate into"   NL
 
978
"### decent performance over a wide range of setups."                        NL
 
979
"###"                                                                        NL
 
980
"### When a specific piece of information needs to be read from disk,  a"    NL
 
981
"### data block is being read at once and its contents are being cached."    NL
 
982
"### If the repository is being stored on a RAID, the block size should be"  NL
 
983
"### either 50% or 100% of RAID block size / granularity.  Also, your file"  NL
 
984
"### system blocks/clusters should be properly aligned and sized.  In that"  NL
 
985
"### setup, each access will hit only one disk (minimizes I/O load) but"     NL
 
986
"### uses all the data provided by the disk in a single access."             NL
 
987
"### For SSD-based storage systems, slightly lower values around 16 kB"      NL
 
988
"### may improve latency while still maximizing throughput.  If block-read"  NL
 
989
"### has not been enabled, this will be capped to 4 kBytes."                 NL
 
990
"### Can be changed at any time but must be a power of 2."                   NL
 
991
"### block-size is given in kBytes and with a default of 64 kBytes."         NL
 
992
"# " CONFIG_OPTION_BLOCK_SIZE " = 64"                                        NL
 
993
"###"                                                                        NL
 
994
"### The log-to-phys index maps data item numbers to offsets within the"     NL
 
995
"### rev or pack file.  This index is organized in pages of a fixed maximum" NL
 
996
"### capacity.  To access an item, the page table and the respective page"   NL
 
997
"### must be read."                                                          NL
 
998
"### This parameter only affects revisions with thousands of changed paths." NL
 
999
"### If you have several extremely large revisions (~1 mio changes), think"  NL
 
1000
"### about increasing this setting.  Reducing the value will rarely result"  NL
 
1001
"### in a net speedup."                                                      NL
 
1002
"### This is an expert setting.  Must be a power of 2."                      NL
 
1003
"### l2p-page-size is 8192 entries by default."                              NL
 
1004
"# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192"                                   NL
 
1005
"###"                                                                        NL
 
1006
"### The phys-to-log index maps positions within the rev or pack file to"    NL
 
1007
"### to data items,  i.e. describes what piece of information is being"      NL
 
1008
"### stored at any particular offset.  The index describes the rev file"     NL
 
1009
"### in chunks (pages) and keeps a global list of all those pages.  Large"   NL
 
1010
"### pages mean a shorter page table but a larger per-page description of"   NL
 
1011
"### data items in it.  The latency sweetspot depends on the change size"    NL
 
1012
"### distribution but covers a relatively wide range."                       NL
 
1013
"### If the repository contains very large files,  i.e. individual changes"  NL
 
1014
"### of tens of MB each,  increasing the page size will shorten the index"   NL
 
1015
"### file at the expense of a slightly increased latency in sections with"   NL
 
1016
"### smaller changes."                                                       NL
 
1017
"### For source code repositories, this should be about 16x the block-size." NL
 
1018
"### Must be a power of 2."                                                  NL
 
1019
"### p2l-page-size is given in kBytes and with a default of 1024 kBytes."    NL
 
1020
"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024"                                   NL
1359
1021
;
1360
1022
#undef NL
1361
1023
  return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1362
1024
                            fsfs_conf_contents, pool);
1363
1025
}
1364
1026
 
 
1027
/* Read / Evaluate the global configuration in FS->CONFIG to set up
 
1028
 * parameters in FS. */
1365
1029
static svn_error_t *
1366
 
read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
1367
 
                      const char *path,
1368
 
                      apr_pool_t *pool)
 
1030
read_global_config(svn_fs_t *fs)
1369
1031
{
1370
 
  char buf[80];
1371
 
  apr_file_t *file;
1372
 
  apr_size_t len;
1373
 
 
1374
 
  SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
1375
 
                           APR_OS_DEFAULT, pool));
1376
 
  len = sizeof(buf);
1377
 
  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
1378
 
  SVN_ERR(svn_io_file_close(file, pool));
1379
 
 
1380
 
  *min_unpacked_rev = SVN_STR_TO_REV(buf);
 
1032
  fs_fs_data_t *ffd = fs->fsap_data;
 
1033
 
 
1034
  /* Providing a config hash is optional. */
 
1035
  if (fs->config)
 
1036
    ffd->use_block_read = svn_hash__get_bool(fs->config,
 
1037
                                             SVN_FS_CONFIG_FSFS_BLOCK_READ,
 
1038
                                             FALSE);
 
1039
  else
 
1040
    ffd->use_block_read = FALSE;
 
1041
 
 
1042
  /* Ignore the user-specified larger block size if we don't use block-read.
 
1043
     Defaulting to 4k gives us the same access granularity in format 7 as in
 
1044
     older formats. */
 
1045
  if (!ffd->use_block_read)
 
1046
    ffd->block_size = MIN(0x1000, ffd->block_size);
 
1047
 
1381
1048
  return SVN_NO_ERROR;
1382
1049
}
1383
1050
 
 
1051
/* Read FS's UUID file and store the data in the FS struct. */
1384
1052
static svn_error_t *
1385
 
update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
1386
 
{
1387
 
  fs_fs_data_t *ffd = fs->fsap_data;
1388
 
 
1389
 
  SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
1390
 
 
1391
 
  return read_min_unpacked_rev(&ffd->min_unpacked_rev,
1392
 
                               path_min_unpacked_rev(fs, pool),
1393
 
                               pool);
1394
 
}
1395
 
 
1396
 
svn_error_t *
1397
 
svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
 
1053
read_uuid(svn_fs_t *fs,
 
1054
          apr_pool_t *scratch_pool)
1398
1055
{
1399
1056
  fs_fs_data_t *ffd = fs->fsap_data;
1400
1057
  apr_file_t *uuid_file;
1401
 
  int format, max_files_per_dir;
1402
1058
  char buf[APR_UUID_FORMATTED_LENGTH + 2];
1403
1059
  apr_size_t limit;
1404
1060
 
1405
 
  fs->path = apr_pstrdup(fs->pool, path);
1406
 
 
1407
 
  /* Read the FS format number. */
1408
 
  SVN_ERR(read_format(&format, &max_files_per_dir,
1409
 
                      path_format(fs, pool), pool));
1410
 
 
1411
 
  /* Now we've got a format number no matter what. */
 
1061
  /* Read the repository uuid. */
 
1062
  SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, scratch_pool),
 
1063
                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
 
1064
                           scratch_pool));
 
1065
 
 
1066
  limit = sizeof(buf);
 
1067
  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
 
1068
  fs->uuid = apr_pstrdup(fs->pool, buf);
 
1069
 
 
1070
  /* Read the instance ID. */
 
1071
  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
 
1072
    {
 
1073
      limit = sizeof(buf);
 
1074
      SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
 
1075
                                      scratch_pool));
 
1076
      ffd->instance_id = apr_pstrdup(fs->pool, buf);
 
1077
    }
 
1078
  else
 
1079
    {
 
1080
      ffd->instance_id = fs->uuid;
 
1081
    }
 
1082
 
 
1083
  SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
 
1084
 
 
1085
  return SVN_NO_ERROR;
 
1086
}
 
1087
 
 
1088
svn_error_t *
 
1089
svn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool)
 
1090
{
 
1091
  fs_fs_data_t *ffd = fs->fsap_data;
 
1092
  int format, max_files_per_dir;
 
1093
  svn_boolean_t use_log_addressing;
 
1094
 
 
1095
  /* Read info from format file. */
 
1096
  SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
 
1097
                      path_format(fs, scratch_pool), scratch_pool));
 
1098
 
 
1099
  /* Now that we've got *all* info, store / update values in FFD. */
1412
1100
  ffd->format = format;
1413
1101
  ffd->max_files_per_dir = max_files_per_dir;
 
1102
  ffd->use_log_addressing = use_log_addressing;
 
1103
 
 
1104
  return SVN_NO_ERROR;
 
1105
}
 
1106
 
 
1107
svn_error_t *
 
1108
svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
 
1109
{
 
1110
  fs_fs_data_t *ffd = fs->fsap_data;
 
1111
  fs->path = apr_pstrdup(fs->pool, path);
 
1112
 
 
1113
  /* Read the FS format file. */
 
1114
  SVN_ERR(svn_fs_fs__read_format_file(fs, pool));
1414
1115
 
1415
1116
  /* Read in and cache the repository uuid. */
1416
 
  SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
1417
 
                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1418
 
 
1419
 
  limit = sizeof(buf);
1420
 
  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
1421
 
  fs->uuid = apr_pstrdup(fs->pool, buf);
1422
 
 
1423
 
  SVN_ERR(svn_io_file_close(uuid_file, pool));
 
1117
  SVN_ERR(read_uuid(fs, pool));
1424
1118
 
1425
1119
  /* Read the min unpacked revision. */
1426
1120
  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1427
 
    SVN_ERR(update_min_unpacked_rev(fs, pool));
 
1121
    SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1428
1122
 
1429
1123
  /* Read the configuration file. */
1430
 
  SVN_ERR(read_config(ffd, fs->path, pool));
1431
 
 
1432
 
  return get_youngest(&(ffd->youngest_rev_cache), path, pool);
 
1124
  SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
 
1125
 
 
1126
  /* Global configuration options. */
 
1127
  SVN_ERR(read_global_config(fs));
 
1128
 
 
1129
  return get_youngest(&(ffd->youngest_rev_cache), fs, pool);
1433
1130
}
1434
1131
 
1435
1132
/* Wrapper around svn_io_file_create which ignores EEXIST. */
1447
1144
  return svn_error_trace(err);
1448
1145
}
1449
1146
 
1450
 
/* forward declarations */
1451
 
 
1452
 
static svn_error_t *
1453
 
pack_revprops_shard(const char *pack_file_dir,
1454
 
                    const char *shard_path,
1455
 
                    apr_int64_t shard,
1456
 
                    int max_files_per_dir,
1457
 
                    apr_off_t max_pack_size,
1458
 
                    int compression_level,
1459
 
                    svn_cancel_func_t cancel_func,
1460
 
                    void *cancel_baton,
1461
 
                    apr_pool_t *scratch_pool);
1462
 
 
1463
 
static svn_error_t *
1464
 
delete_revprops_shard(const char *shard_path,
1465
 
                      apr_int64_t shard,
1466
 
                      int max_files_per_dir,
1467
 
                      svn_cancel_func_t cancel_func,
1468
 
                      void *cancel_baton,
1469
 
                      apr_pool_t *scratch_pool);
1470
 
 
1471
 
/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
1472
 
 * 
1473
 
 * NOTE: Keep the old non-packed shards around until after the format bump.
1474
 
 * Otherwise, re-running upgrade will drop the packed revprop shard but
1475
 
 * have no unpacked data anymore.  Call upgrade_cleanup_pack_revprops after
1476
 
 * the bump.
1477
 
 * 
1478
 
 * Use SCRATCH_POOL for temporary allocations.
1479
 
 */
1480
 
static svn_error_t *
1481
 
upgrade_pack_revprops(svn_fs_t *fs,
1482
 
                      apr_pool_t *scratch_pool)
1483
 
{
1484
 
  fs_fs_data_t *ffd = fs->fsap_data;
1485
 
  const char *revprops_shard_path;
1486
 
  const char *revprops_pack_file_dir;
1487
 
  apr_int64_t shard;
1488
 
  apr_int64_t first_unpacked_shard
1489
 
    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1490
 
 
1491
 
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1492
 
  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1493
 
                                              scratch_pool);
1494
 
  int compression_level = ffd->compress_packed_revprops
1495
 
                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
1496
 
                           : SVN_DELTA_COMPRESSION_LEVEL_NONE;
1497
 
 
1498
 
  /* first, pack all revprops shards to match the packed revision shards */
1499
 
  for (shard = 0; shard < first_unpacked_shard; ++shard)
1500
 
    {
1501
 
      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
1502
 
                   apr_psprintf(iterpool,
1503
 
                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
1504
 
                                shard),
1505
 
                   iterpool);
1506
 
      revprops_shard_path = svn_dirent_join(revsprops_dir,
1507
 
                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1508
 
                       iterpool);
1509
 
 
1510
 
      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
1511
 
                                  shard, ffd->max_files_per_dir,
1512
 
                                  (int)(0.9 * ffd->revprop_pack_size),
1513
 
                                  compression_level,
1514
 
                                  NULL, NULL, iterpool));
1515
 
      svn_pool_clear(iterpool);
1516
 
    }
1517
 
 
1518
 
  svn_pool_destroy(iterpool);
1519
 
 
1520
 
  return SVN_NO_ERROR;
1521
 
}
1522
 
 
1523
 
/* In the filesystem FS, remove all non-packed revprop shards up to
1524
 
 * min_unpacked_rev.  Use SCRATCH_POOL for temporary allocations.
1525
 
 * See upgrade_pack_revprops for more info.
1526
 
 */
1527
 
static svn_error_t *
1528
 
upgrade_cleanup_pack_revprops(svn_fs_t *fs,
1529
 
                              apr_pool_t *scratch_pool)
1530
 
{
1531
 
  fs_fs_data_t *ffd = fs->fsap_data;
1532
 
  const char *revprops_shard_path;
1533
 
  apr_int64_t shard;
1534
 
  apr_int64_t first_unpacked_shard
1535
 
    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1536
 
 
1537
 
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1538
 
  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1539
 
                                              scratch_pool);
1540
 
  
1541
 
  /* delete the non-packed revprops shards afterwards */
1542
 
  for (shard = 0; shard < first_unpacked_shard; ++shard)
1543
 
    {
1544
 
      revprops_shard_path = svn_dirent_join(revsprops_dir,
1545
 
                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1546
 
                       iterpool);
1547
 
      SVN_ERR(delete_revprops_shard(revprops_shard_path,
1548
 
                                    shard, ffd->max_files_per_dir,
1549
 
                                    NULL, NULL, iterpool));
1550
 
      svn_pool_clear(iterpool);
1551
 
    }
1552
 
 
1553
 
  svn_pool_destroy(iterpool);
1554
 
 
1555
 
  return SVN_NO_ERROR;
1556
 
}
 
1147
/* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying
 
1148
 * parameters over between them. */
 
1149
struct upgrade_baton_t
 
1150
{
 
1151
  svn_fs_t *fs;
 
1152
  svn_fs_upgrade_notify_t notify_func;
 
1153
  void *notify_baton;
 
1154
  svn_cancel_func_t cancel_func;
 
1155
  void *cancel_baton;
 
1156
};
1557
1157
 
1558
1158
static svn_error_t *
1559
1159
upgrade_body(void *baton, apr_pool_t *pool)
1560
1160
{
1561
 
  svn_fs_t *fs = baton;
 
1161
  struct upgrade_baton_t *upgrade_baton = baton;
 
1162
  svn_fs_t *fs = upgrade_baton->fs;
 
1163
  fs_fs_data_t *ffd = fs->fsap_data;
1562
1164
  int format, max_files_per_dir;
 
1165
  svn_boolean_t use_log_addressing;
1563
1166
  const char *format_path = path_format(fs, pool);
1564
1167
  svn_node_kind_t kind;
1565
1168
  svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1566
1169
 
1567
1170
  /* Read the FS format number and max-files-per-dir setting. */
1568
 
  SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
 
1171
  SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
 
1172
                      format_path, pool));
1569
1173
 
1570
1174
  /* If the config file does not exist, create one. */
1571
1175
  SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1589
1193
  if (format == SVN_FS_FS__FORMAT_NUMBER)
1590
1194
    return SVN_NO_ERROR;
1591
1195
 
1592
 
  /* If our filesystem predates the existance of the 'txn-current
 
1196
  /* If our filesystem predates the existence of the 'txn-current
1593
1197
     file', make that file and its corresponding lock file. */
1594
1198
  if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1595
1199
    {
1596
 
      SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
1597
 
                                        pool));
1598
 
      SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
1599
 
                                        pool));
 
1200
      SVN_ERR(create_file_ignore_eexist(
 
1201
                           svn_fs_fs__path_txn_current(fs, pool), "0\n",
 
1202
                           pool));
 
1203
      SVN_ERR(create_file_ignore_eexist(
 
1204
                           svn_fs_fs__path_txn_current_lock(fs, pool), "",
 
1205
                           pool));
1600
1206
    }
1601
1207
 
1602
 
  /* If our filesystem predates the existance of the 'txn-protorevs'
 
1208
  /* If our filesystem predates the existence of the 'txn-protorevs'
1603
1209
     dir, make that directory.  */
1604
1210
  if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1605
1211
    {
1606
 
      /* We don't use path_txn_proto_rev() here because it expects
1607
 
         we've already bumped our format. */
1608
1212
      SVN_ERR(svn_io_make_dir_recursively(
1609
 
          svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
 
1213
          svn_fs_fs__path_txn_proto_revs(fs, pool), pool));
1610
1214
    }
1611
1215
 
1612
1216
  /* If our filesystem is new enough, write the min unpacked rev file. */
1613
1217
  if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1614
 
    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
 
1218
    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
 
1219
                               "0\n", pool));
1615
1220
 
1616
1221
  /* If the file system supports revision packing but not revprop packing
1617
1222
     *and* the FS has been sharded, pack the revprops up to the point that
1622
1227
      && max_files_per_dir > 0)
1623
1228
    {
1624
1229
      needs_revprop_shard_cleanup = TRUE;
1625
 
      SVN_ERR(upgrade_pack_revprops(fs, pool));
 
1230
      SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs,
 
1231
                                               upgrade_baton->notify_func,
 
1232
                                               upgrade_baton->notify_baton,
 
1233
                                               upgrade_baton->cancel_func,
 
1234
                                               upgrade_baton->cancel_baton,
 
1235
                                               pool));
1626
1236
    }
1627
1237
 
 
1238
  /* We will need the UUID info shortly ...
 
1239
     Read it before the format bump as the UUID file still uses the old
 
1240
     format. */
 
1241
  SVN_ERR(read_uuid(fs, pool));
 
1242
 
 
1243
  /* Update the format info in the FS struct.  Upgrade steps further
 
1244
     down will use the format from FS to create missing info. */
 
1245
  ffd->format = SVN_FS_FS__FORMAT_NUMBER;
 
1246
  ffd->max_files_per_dir = max_files_per_dir;
 
1247
  ffd->use_log_addressing = use_log_addressing;
 
1248
 
 
1249
  /* Always add / bump the instance ID such that no form of caching
 
1250
     accidentally uses outdated information.  Keep the UUID. */
 
1251
  SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
 
1252
 
1628
1253
  /* Bump the format file. */
1629
 
  SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER,
1630
 
                       max_files_per_dir, TRUE, pool));
 
1254
  SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool));
 
1255
 
 
1256
  if (upgrade_baton->notify_func)
 
1257
    SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton,
 
1258
                                       SVN_FS_FS__FORMAT_NUMBER,
 
1259
                                       svn_fs_upgrade_format_bumped,
 
1260
                                       pool));
1631
1261
 
1632
1262
  /* Now, it is safe to remove the redundant revprop files. */
1633
1263
  if (needs_revprop_shard_cleanup)
1634
 
    SVN_ERR(upgrade_cleanup_pack_revprops(fs, pool));
 
1264
    SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs,
 
1265
                                               upgrade_baton->notify_func,
 
1266
                                               upgrade_baton->notify_baton,
 
1267
                                               upgrade_baton->cancel_func,
 
1268
                                               upgrade_baton->cancel_baton,
 
1269
                                               pool));
1635
1270
 
1636
1271
  /* Done */
1637
1272
  return SVN_NO_ERROR;
1639
1274
 
1640
1275
 
1641
1276
svn_error_t *
1642
 
svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
1643
 
{
1644
 
  return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
1645
 
}
1646
 
 
1647
 
 
1648
 
/* Functions for dealing with recoverable errors on mutable files
1649
 
 *
1650
 
 * Revprops, current, and txn-current files are mutable; that is, they
1651
 
 * change as part of normal fsfs operation, in constrat to revs files, or
1652
 
 * the format file, which are written once at create (or upgrade) time.
1653
 
 * When more than one host writes to the same repository, we will
1654
 
 * sometimes see these recoverable errors when accesssing these files.
1655
 
 *
1656
 
 * These errors all relate to NFS, and thus we only use this retry code if
1657
 
 * ESTALE is defined.
1658
 
 *
1659
 
 ** ESTALE
1660
 
 *
1661
 
 * In NFS v3 and under, the server doesn't track opened files.  If you
1662
 
 * unlink(2) or rename(2) a file held open by another process *on the
1663
 
 * same host*, that host's kernel typically renames the file to
1664
 
 * .nfsXXXX and automatically deletes that when it's no longer open,
1665
 
 * but this behavior is not required.
1666
 
 *
1667
 
 * For obvious reasons, this does not work *across hosts*.  No one
1668
 
 * knows about the opened file; not the server, and not the deleting
1669
 
 * client.  So the file vanishes, and the reader gets stale NFS file
1670
 
 * handle.
1671
 
 *
1672
 
 ** EIO, ENOENT
1673
 
 *
1674
 
 * Some client implementations (at least the 2.6.18.5 kernel that ships
1675
 
 * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
1676
 
 * even EIO errors when trying to read these files that have been renamed
1677
 
 * over on some other host.
1678
 
 *
1679
 
 ** Solution
1680
 
 *
1681
 
 * Try open and read of such files in try_stringbuf_from_file().  Call
1682
 
 * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
1683
 
 * (though, realistically, the second try will succeed).
1684
 
 */
1685
 
 
1686
 
#define RECOVERABLE_RETRY_COUNT 10
1687
 
 
1688
 
/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
1689
 
 * not be modified unless the whole file was read successfully.
1690
 
 *
1691
 
 * ESTALE, EIO and ENOENT will not cause this function to return an error
1692
 
 * unless LAST_ATTEMPT has been set.  If MISSING is not NULL, indicate
1693
 
 * missing files (ENOENT) there.
1694
 
 *
1695
 
 * Use POOL for allocations.
1696
 
 */
1697
 
static svn_error_t *
1698
 
try_stringbuf_from_file(svn_stringbuf_t **content,
1699
 
                        svn_boolean_t *missing,
1700
 
                        const char *path,
1701
 
                        svn_boolean_t last_attempt,
1702
 
                        apr_pool_t *pool)
1703
 
{
1704
 
  svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
1705
 
  if (missing)
1706
 
    *missing = FALSE;
1707
 
 
1708
 
  if (err)
1709
 
    {
1710
 
      *content = NULL;
1711
 
 
1712
 
      if (APR_STATUS_IS_ENOENT(err->apr_err))
1713
 
        {
1714
 
          if (!last_attempt)
1715
 
            {
1716
 
              svn_error_clear(err);
1717
 
              if (missing)
1718
 
                *missing = TRUE;
1719
 
              return SVN_NO_ERROR;
1720
 
            }
1721
 
        }
1722
 
#ifdef ESTALE
1723
 
      else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
1724
 
                || APR_TO_OS_ERROR(err->apr_err) == EIO)
1725
 
        {
1726
 
          if (!last_attempt)
1727
 
            {
1728
 
              svn_error_clear(err);
1729
 
              return SVN_NO_ERROR;
1730
 
            }
1731
 
        }
1732
 
#endif
1733
 
    }
1734
 
 
1735
 
  return svn_error_trace(err);
1736
 
}
1737
 
 
1738
 
/* Read the 'current' file FNAME and store the contents in *BUF.
1739
 
   Allocations are performed in POOL. */
1740
 
static svn_error_t *
1741
 
read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
1742
 
{
1743
 
  int i;
1744
 
  *content = NULL;
1745
 
 
1746
 
  for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
1747
 
    SVN_ERR(try_stringbuf_from_file(content, NULL,
1748
 
                                    fname, i + 1 < RECOVERABLE_RETRY_COUNT,
1749
 
                                    pool));
1750
 
 
1751
 
  if (!*content)
1752
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1753
 
                             _("Can't read '%s'"),
1754
 
                             svn_dirent_local_style(fname, pool));
1755
 
 
1756
 
  return SVN_NO_ERROR;
 
1277
svn_fs_fs__upgrade(svn_fs_t *fs,
 
1278
                   svn_fs_upgrade_notify_t notify_func,
 
1279
                   void *notify_baton,
 
1280
                   svn_cancel_func_t cancel_func,
 
1281
                   void *cancel_baton,
 
1282
                   apr_pool_t *pool)
 
1283
{
 
1284
  struct upgrade_baton_t baton;
 
1285
  baton.fs = fs;
 
1286
  baton.notify_func = notify_func;
 
1287
  baton.notify_baton = notify_baton;
 
1288
  baton.cancel_func = cancel_func;
 
1289
  baton.cancel_baton = cancel_baton;
 
1290
 
 
1291
  return svn_fs_fs__with_all_locks(fs, upgrade_body, (void *)&baton, pool);
1757
1292
}
1758
1293
 
1759
1294
/* Find the youngest revision in a repository at path FS_PATH and
1761
1296
   POOL. */
1762
1297
static svn_error_t *
1763
1298
get_youngest(svn_revnum_t *youngest_p,
1764
 
             const char *fs_path,
 
1299
             svn_fs_t *fs,
1765
1300
             apr_pool_t *pool)
1766
1301
{
1767
 
  svn_stringbuf_t *buf;
1768
 
  SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
1769
 
                       pool));
1770
 
 
1771
 
  *youngest_p = SVN_STR_TO_REV(buf->data);
1772
 
 
 
1302
  apr_uint64_t dummy;
 
1303
  SVN_ERR(svn_fs_fs__read_current(youngest_p, &dummy, &dummy, fs, pool));
1773
1304
  return SVN_NO_ERROR;
1774
1305
}
1775
1306
 
1781
1312
{
1782
1313
  fs_fs_data_t *ffd = fs->fsap_data;
1783
1314
 
1784
 
  SVN_ERR(get_youngest(youngest_p, fs->path, pool));
 
1315
  SVN_ERR(get_youngest(youngest_p, fs, pool));
1785
1316
  ffd->youngest_rev_cache = *youngest_p;
1786
1317
 
1787
1318
  return SVN_NO_ERROR;
1788
1319
}
1789
1320
 
1790
 
/* Given a revision file FILE that has been pre-positioned at the
1791
 
   beginning of a Node-Rev header block, read in that header block and
1792
 
   store it in the apr_hash_t HEADERS.  All allocations will be from
1793
 
   POOL. */
1794
 
static svn_error_t * read_header_block(apr_hash_t **headers,
1795
 
                                       svn_stream_t *stream,
1796
 
                                       apr_pool_t *pool)
1797
 
{
1798
 
  *headers = apr_hash_make(pool);
1799
 
 
1800
 
  while (1)
1801
 
    {
1802
 
      svn_stringbuf_t *header_str;
1803
 
      const char *name, *value;
1804
 
      apr_size_t i = 0;
1805
 
      svn_boolean_t eof;
1806
 
 
1807
 
      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
1808
 
 
1809
 
      if (eof || header_str->len == 0)
1810
 
        break; /* end of header block */
1811
 
 
1812
 
      while (header_str->data[i] != ':')
1813
 
        {
1814
 
          if (header_str->data[i] == '\0')
1815
 
            return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1816
 
                                     _("Found malformed header '%s' in "
1817
 
                                       "revision file"),
1818
 
                                     header_str->data);
1819
 
          i++;
1820
 
        }
1821
 
 
1822
 
      /* Create a 'name' string and point to it. */
1823
 
      header_str->data[i] = '\0';
1824
 
      name = header_str->data;
1825
 
 
1826
 
      /* Skip over the NULL byte and the space following it. */
1827
 
      i += 2;
1828
 
 
1829
 
      if (i > header_str->len)
1830
 
        {
1831
 
          /* Restore the original line for the error. */
1832
 
          i -= 2;
1833
 
          header_str->data[i] = ':';
1834
 
          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1835
 
                                   _("Found malformed header '%s' in "
1836
 
                                     "revision file"),
1837
 
                                   header_str->data);
1838
 
        }
1839
 
 
1840
 
      value = header_str->data + i;
1841
 
 
1842
 
      /* header_str is safely in our pool, so we can use bits of it as
1843
 
         key and value. */
1844
 
      svn_hash_sets(*headers, name, value);
1845
 
    }
 
1321
int
 
1322
svn_fs_fs__shard_size(svn_fs_t *fs)
 
1323
{
 
1324
  fs_fs_data_t *ffd = fs->fsap_data;
 
1325
 
 
1326
  return ffd->max_files_per_dir;
 
1327
}
 
1328
 
 
1329
svn_error_t *
 
1330
svn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
 
1331
                            svn_fs_t *fs,
 
1332
                            apr_pool_t *pool)
 
1333
{
 
1334
  fs_fs_data_t *ffd = fs->fsap_data;
 
1335
 
 
1336
  SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
 
1337
  *min_unpacked = ffd->min_unpacked_rev;
1846
1338
 
1847
1339
  return SVN_NO_ERROR;
1848
1340
}
1849
1341
 
1850
 
/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
1851
 
   than the current youngest revision or is simply not a valid
1852
 
   revision number, else return success.
1853
 
 
1854
 
   FSFS is based around the concept that commits only take effect when
1855
 
   the number in "current" is bumped.  Thus if there happens to be a rev
1856
 
   or revprops file installed for a revision higher than the one recorded
1857
 
   in "current" (because a commit failed between installing the rev file
1858
 
   and bumping "current", or because an administrator rolled back the
1859
 
   repository by resetting "current" without deleting rev files, etc), it
1860
 
   ought to be completely ignored.  This function provides the check
1861
 
   by which callers can make that decision. */
1862
 
static svn_error_t *
1863
 
ensure_revision_exists(svn_fs_t *fs,
1864
 
                       svn_revnum_t rev,
1865
 
                       apr_pool_t *pool)
 
1342
svn_error_t *
 
1343
svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
 
1344
                                  svn_fs_t *fs,
 
1345
                                  apr_pool_t *pool)
1866
1346
{
1867
1347
  fs_fs_data_t *ffd = fs->fsap_data;
1868
1348
 
1876
1356
  if (rev <= ffd->youngest_rev_cache)
1877
1357
    return SVN_NO_ERROR;
1878
1358
 
1879
 
  SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
 
1359
  SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs, pool));
1880
1360
 
1881
1361
  /* Check again. */
1882
1362
  if (rev <= ffd->youngest_rev_cache)
1887
1367
}
1888
1368
 
1889
1369
svn_error_t *
1890
 
svn_fs_fs__revision_exists(svn_revnum_t rev,
1891
 
                           svn_fs_t *fs,
1892
 
                           apr_pool_t *pool)
1893
 
{
1894
 
  /* Different order of parameters. */
1895
 
  SVN_ERR(ensure_revision_exists(fs, rev, pool));
1896
 
  return SVN_NO_ERROR;
1897
 
}
1898
 
 
1899
 
/* Open the correct revision file for REV.  If the filesystem FS has
1900
 
   been packed, *FILE will be set to the packed file; otherwise, set *FILE
1901
 
   to the revision file for REV.  Return SVN_ERR_FS_NO_SUCH_REVISION if the
1902
 
   file doesn't exist.
1903
 
 
1904
 
   TODO: Consider returning an indication of whether this is a packed rev
1905
 
         file, so the caller need not rely on is_packed_rev() which in turn
1906
 
         relies on the cached FFD->min_unpacked_rev value not having changed
1907
 
         since the rev file was opened.
1908
 
 
1909
 
   Use POOL for allocations. */
1910
 
static svn_error_t *
1911
 
open_pack_or_rev_file(apr_file_t **file,
1912
 
                      svn_fs_t *fs,
1913
 
                      svn_revnum_t rev,
1914
 
                      apr_pool_t *pool)
1915
 
{
1916
 
  fs_fs_data_t *ffd = fs->fsap_data;
1917
 
  svn_error_t *err;
1918
 
  const char *path;
1919
 
  svn_boolean_t retry = FALSE;
1920
 
 
1921
 
  do
1922
 
    {
1923
 
      err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
1924
 
 
1925
 
      /* open the revision file in buffered r/o mode */
1926
 
      if (! err)
1927
 
        err = svn_io_file_open(file, path,
1928
 
                              APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1929
 
 
1930
 
      if (err && APR_STATUS_IS_ENOENT(err->apr_err))
 
1370
svn_fs_fs__file_length(svn_filesize_t *length,
 
1371
                       node_revision_t *noderev,
 
1372
                       apr_pool_t *pool)
 
1373
{
 
1374
  representation_t *data_rep = noderev->data_rep;
 
1375
  if (!data_rep)
 
1376
    {
 
1377
      /* Treat "no representation" as "empty file". */
 
1378
      *length = 0;
 
1379
    }
 
1380
  else if (data_rep->expanded_size)
 
1381
    {
 
1382
      /* Standard case: a non-empty file. */
 
1383
      *length = data_rep->expanded_size;
 
1384
    }
 
1385
  else
 
1386
    {
 
1387
      /* Work around a FSFS format quirk (see issue #4554).
 
1388
 
 
1389
         A plain representation may specify its EXPANDED LENGTH as "0"
 
1390
         in which case, the SIZE value is what we want.
 
1391
 
 
1392
         Because EXPANDED_LENGTH will also be 0 for empty files, while
 
1393
         SIZE is non-null, we need to check wether the content is
 
1394
         actually empty.  We simply compare with the MD5 checksum of
 
1395
         empty content (sha-1 is not always available).
 
1396
       */
 
1397
      svn_checksum_t *empty_md5
 
1398
        = svn_checksum_empty_checksum(svn_checksum_md5, pool);
 
1399
 
 
1400
      if (memcmp(empty_md5->digest, data_rep->md5_digest,
 
1401
                 sizeof(data_rep->md5_digest)))
1931
1402
        {
1932
 
          if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1933
 
            {
1934
 
              /* Could not open the file. This may happen if the
1935
 
               * file once existed but got packed later. */
1936
 
              svn_error_clear(err);
1937
 
 
1938
 
              /* if that was our 2nd attempt, leave it at that. */
1939
 
              if (retry)
1940
 
                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1941
 
                                         _("No such revision %ld"), rev);
1942
 
 
1943
 
              /* We failed for the first time. Refresh cache & retry. */
1944
 
              SVN_ERR(update_min_unpacked_rev(fs, pool));
1945
 
 
1946
 
              retry = TRUE;
1947
 
            }
1948
 
          else
1949
 
            {
1950
 
              svn_error_clear(err);
1951
 
              return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1952
 
                                       _("No such revision %ld"), rev);
1953
 
            }
 
1403
          /* Contents is not empty, i.e. EXPANDED_LENGTH cannot be the
 
1404
             actual file length. */
 
1405
          *length = data_rep->size;
1954
1406
        }
1955
1407
      else
1956
1408
        {
1957
 
          retry = FALSE;
 
1409
          /* Contents is empty. */
 
1410
          *length = 0;
1958
1411
        }
1959
1412
    }
1960
 
  while (retry);
1961
 
 
1962
 
  return svn_error_trace(err);
1963
 
}
1964
 
 
1965
 
/* Reads a line from STREAM and converts it to a 64 bit integer to be
1966
 
 * returned in *RESULT.  If we encounter eof, set *HIT_EOF and leave
1967
 
 * *RESULT unchanged.  If HIT_EOF is NULL, EOF causes an "corrupt FS"
1968
 
 * error return.
1969
 
 * SCRATCH_POOL is used for temporary allocations.
1970
 
 */
1971
 
static svn_error_t *
1972
 
read_number_from_stream(apr_int64_t *result,
1973
 
                        svn_boolean_t *hit_eof,
1974
 
                        svn_stream_t *stream,
1975
 
                        apr_pool_t *scratch_pool)
1976
 
{
1977
 
  svn_stringbuf_t *sb;
1978
 
  svn_boolean_t eof;
1979
 
  svn_error_t *err;
1980
 
 
1981
 
  SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
1982
 
  if (hit_eof)
1983
 
    *hit_eof = eof;
1984
 
  else
1985
 
    if (eof)
1986
 
      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
1987
 
 
1988
 
  if (!eof)
1989
 
    {
1990
 
      err = svn_cstring_atoi64(result, sb->data);
1991
 
      if (err)
1992
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
1993
 
                                 _("Number '%s' invalid or too large"),
1994
 
                                 sb->data);
1995
 
    }
1996
 
 
1997
 
  return SVN_NO_ERROR;
1998
 
}
1999
 
 
2000
 
/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
2001
 
   Use POOL for temporary allocations. */
2002
 
static svn_error_t *
2003
 
get_packed_offset(apr_off_t *rev_offset,
2004
 
                  svn_fs_t *fs,
2005
 
                  svn_revnum_t rev,
2006
 
                  apr_pool_t *pool)
2007
 
{
2008
 
  fs_fs_data_t *ffd = fs->fsap_data;
2009
 
  svn_stream_t *manifest_stream;
2010
 
  svn_boolean_t is_cached;
2011
 
  svn_revnum_t shard;
2012
 
  apr_int64_t shard_pos;
2013
 
  apr_array_header_t *manifest;
2014
 
  apr_pool_t *iterpool;
2015
 
 
2016
 
  shard = rev / ffd->max_files_per_dir;
2017
 
 
2018
 
  /* position of the shard within the manifest */
2019
 
  shard_pos = rev % ffd->max_files_per_dir;
2020
 
 
2021
 
  /* fetch exactly that element into *rev_offset, if the manifest is found
2022
 
     in the cache */
2023
 
  SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
2024
 
                                 ffd->packed_offset_cache, &shard,
2025
 
                                 svn_fs_fs__get_sharded_offset, &shard_pos,
2026
 
                                 pool));
2027
 
 
2028
 
  if (is_cached)
2029
 
      return SVN_NO_ERROR;
2030
 
 
2031
 
  /* Open the manifest file. */
2032
 
  SVN_ERR(svn_stream_open_readonly(&manifest_stream,
2033
 
                                   path_rev_packed(fs, rev, PATH_MANIFEST,
2034
 
                                                   pool),
2035
 
                                   pool, pool));
2036
 
 
2037
 
  /* While we're here, let's just read the entire manifest file into an array,
2038
 
     so we can cache the entire thing. */
2039
 
  iterpool = svn_pool_create(pool);
2040
 
  manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
2041
 
  while (1)
2042
 
    {
2043
 
      svn_boolean_t eof;
2044
 
      apr_int64_t val;
2045
 
 
2046
 
      svn_pool_clear(iterpool);
2047
 
      SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
2048
 
      if (eof)
2049
 
        break;
2050
 
 
2051
 
      APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
2052
 
    }
2053
 
  svn_pool_destroy(iterpool);
2054
 
 
2055
 
  *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
2056
 
                              apr_off_t);
2057
 
 
2058
 
  /* Close up shop and cache the array. */
2059
 
  SVN_ERR(svn_stream_close(manifest_stream));
2060
 
  return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
2061
 
}
2062
 
 
2063
 
/* Open the revision file for revision REV in filesystem FS and store
2064
 
   the newly opened file in FILE.  Seek to location OFFSET before
2065
 
   returning.  Perform temporary allocations in POOL. */
2066
 
static svn_error_t *
2067
 
open_and_seek_revision(apr_file_t **file,
2068
 
                       svn_fs_t *fs,
2069
 
                       svn_revnum_t rev,
2070
 
                       apr_off_t offset,
2071
 
                       apr_pool_t *pool)
2072
 
{
2073
 
  apr_file_t *rev_file;
2074
 
 
2075
 
  SVN_ERR(ensure_revision_exists(fs, rev, pool));
2076
 
 
2077
 
  SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
2078
 
 
2079
 
  if (is_packed_rev(fs, rev))
2080
 
    {
2081
 
      apr_off_t rev_offset;
2082
 
 
2083
 
      SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2084
 
      offset += rev_offset;
2085
 
    }
2086
 
 
2087
 
  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2088
 
 
2089
 
  *file = rev_file;
2090
 
 
2091
 
  return SVN_NO_ERROR;
2092
 
}
2093
 
 
2094
 
/* Open the representation for a node-revision in transaction TXN_ID
2095
 
   in filesystem FS and store the newly opened file in FILE.  Seek to
2096
 
   location OFFSET before returning.  Perform temporary allocations in
2097
 
   POOL.  Only appropriate for file contents, nor props or directory
2098
 
   contents. */
2099
 
static svn_error_t *
2100
 
open_and_seek_transaction(apr_file_t **file,
 
1413
 
 
1414
  return SVN_NO_ERROR;
 
1415
}
 
1416
 
 
1417
svn_error_t *
 
1418
svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
 
1419
                               svn_fs_t *fs,
 
1420
                               node_revision_t *a,
 
1421
                               node_revision_t *b,
 
1422
                               svn_boolean_t strict,
 
1423
                               apr_pool_t *scratch_pool)
 
1424
{
 
1425
  svn_stream_t *contents_a, *contents_b;
 
1426
  representation_t *rep_a = a->data_rep;
 
1427
  representation_t *rep_b = b->data_rep;
 
1428
  svn_boolean_t a_empty = !rep_a || rep_a->expanded_size == 0;
 
1429
  svn_boolean_t b_empty = !rep_b || rep_b->expanded_size == 0;
 
1430
 
 
1431
  /* This makes sure that neither rep will be NULL later on */
 
1432
  if (a_empty && b_empty)
 
1433
    {
 
1434
      *equal = TRUE;
 
1435
      return SVN_NO_ERROR;
 
1436
    }
 
1437
 
 
1438
  if (a_empty != b_empty)
 
1439
    {
 
1440
      *equal = FALSE;
 
1441
      return SVN_NO_ERROR;
 
1442
    }
 
1443
 
 
1444
  /* File text representations always know their checksums - even in a txn. */
 
1445
  if (memcmp(rep_a->md5_digest, rep_b->md5_digest, sizeof(rep_a->md5_digest)))
 
1446
    {
 
1447
      *equal = FALSE;
 
1448
      return SVN_NO_ERROR;
 
1449
    }
 
1450
 
 
1451
  /* Paranoia. Compare SHA1 checksums because that's the level of
 
1452
     confidence we require for e.g. the working copy. */
 
1453
  if (rep_a->has_sha1 && rep_b->has_sha1)
 
1454
    {
 
1455
      *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
 
1456
                      sizeof(rep_a->sha1_digest)) == 0;
 
1457
      return SVN_NO_ERROR;
 
1458
    }
 
1459
 
 
1460
  /* Same path in same rev or txn? */
 
1461
  if (svn_fs_fs__id_eq(a->id, b->id))
 
1462
    {
 
1463
      *equal = TRUE;
 
1464
      return SVN_NO_ERROR;
 
1465
    }
 
1466
 
 
1467
  /* Old repositories may not have the SHA1 checksum handy.
 
1468
     This check becomes expensive.  Skip it unless explicitly required.
 
1469
 
 
1470
     We already have seen that the ID is different, so produce a likely
 
1471
     false negative as allowed by the API description - even though the
 
1472
     MD5 matched, there is an extremely slim chance that the SHA1 wouldn't.
 
1473
   */
 
1474
  if (!strict)
 
1475
    {
 
1476
      *equal = FALSE;
 
1477
      return SVN_NO_ERROR;
 
1478
    }
 
1479
 
 
1480
  SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
 
1481
                                  scratch_pool));
 
1482
  SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE,
 
1483
                                  scratch_pool));
 
1484
  SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b,
 
1485
                                   scratch_pool));
 
1486
 
 
1487
  return SVN_NO_ERROR;
 
1488
}
 
1489
 
 
1490
svn_error_t *
 
1491
svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
2101
1492
                          svn_fs_t *fs,
2102
 
                          const char *txn_id,
2103
 
                          representation_t *rep,
2104
 
                          apr_pool_t *pool)
2105
 
{
2106
 
  apr_file_t *rev_file;
2107
 
  apr_off_t offset;
2108
 
 
2109
 
  SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
2110
 
                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
2111
 
 
2112
 
  offset = rep->offset;
2113
 
  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2114
 
 
2115
 
  *file = rev_file;
2116
 
 
2117
 
  return SVN_NO_ERROR;
2118
 
}
2119
 
 
2120
 
/* Given a node-id ID, and a representation REP in filesystem FS, open
2121
 
   the correct file and seek to the correction location.  Store this
2122
 
   file in *FILE_P.  Perform any allocations in POOL. */
2123
 
static svn_error_t *
2124
 
open_and_seek_representation(apr_file_t **file_p,
2125
 
                             svn_fs_t *fs,
2126
 
                             representation_t *rep,
2127
 
                             apr_pool_t *pool)
2128
 
{
2129
 
  if (! rep->txn_id)
2130
 
    return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
2131
 
                                  pool);
2132
 
  else
2133
 
    return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
2134
 
}
2135
 
 
2136
 
/* Parse the description of a representation from STRING and store it
2137
 
   into *REP_P.  If the representation is mutable (the revision is
2138
 
   given as -1), then use TXN_ID for the representation's txn_id
2139
 
   field.  If MUTABLE_REP_TRUNCATED is true, then this representation
2140
 
   is for property or directory contents, and no information will be
2141
 
   expected except the "-1" revision number for a mutable
2142
 
   representation.  Allocate *REP_P in POOL. */
2143
 
static svn_error_t *
2144
 
read_rep_offsets_body(representation_t **rep_p,
2145
 
                      char *string,
2146
 
                      const char *txn_id,
2147
 
                      svn_boolean_t mutable_rep_truncated,
2148
 
                      apr_pool_t *pool)
2149
 
{
2150
 
  representation_t *rep;
2151
 
  char *str;
2152
 
  apr_int64_t val;
2153
 
 
2154
 
  rep = apr_pcalloc(pool, sizeof(*rep));
2155
 
  *rep_p = rep;
2156
 
 
2157
 
  str = svn_cstring_tokenize(" ", &string);
2158
 
  if (str == NULL)
2159
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2160
 
                            _("Malformed text representation offset line in node-rev"));
2161
 
 
2162
 
 
2163
 
  rep->revision = SVN_STR_TO_REV(str);
2164
 
  if (rep->revision == SVN_INVALID_REVNUM)
2165
 
    {
2166
 
      rep->txn_id = txn_id;
2167
 
      if (mutable_rep_truncated)
2168
 
        return SVN_NO_ERROR;
2169
 
    }
2170
 
 
2171
 
  str = svn_cstring_tokenize(" ", &string);
2172
 
  if (str == NULL)
2173
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2174
 
                            _("Malformed text representation offset line in node-rev"));
2175
 
 
2176
 
  SVN_ERR(svn_cstring_atoi64(&val, str));
2177
 
  rep->offset = (apr_off_t)val;
2178
 
 
2179
 
  str = svn_cstring_tokenize(" ", &string);
2180
 
  if (str == NULL)
2181
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2182
 
                            _("Malformed text representation offset line in node-rev"));
2183
 
 
2184
 
  SVN_ERR(svn_cstring_atoi64(&val, str));
2185
 
  rep->size = (svn_filesize_t)val;
2186
 
 
2187
 
  str = svn_cstring_tokenize(" ", &string);
2188
 
  if (str == NULL)
2189
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2190
 
                            _("Malformed text representation offset line in node-rev"));
2191
 
 
2192
 
  SVN_ERR(svn_cstring_atoi64(&val, str));
2193
 
  rep->expanded_size = (svn_filesize_t)val;
2194
 
 
2195
 
  /* Read in the MD5 hash. */
2196
 
  str = svn_cstring_tokenize(" ", &string);
2197
 
  if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
2198
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2199
 
                            _("Malformed text representation offset line in node-rev"));
2200
 
 
2201
 
  SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
2202
 
                                 pool));
2203
 
 
2204
 
  /* The remaining fields are only used for formats >= 4, so check that. */
2205
 
  str = svn_cstring_tokenize(" ", &string);
2206
 
  if (str == NULL)
2207
 
    return SVN_NO_ERROR;
2208
 
 
2209
 
  /* Read the SHA1 hash. */
2210
 
  if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
2211
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2212
 
                            _("Malformed text representation offset line in node-rev"));
2213
 
 
2214
 
  SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
2215
 
                                 pool));
2216
 
 
2217
 
  /* Read the uniquifier. */
2218
 
  str = svn_cstring_tokenize(" ", &string);
2219
 
  if (str == NULL)
2220
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2221
 
                            _("Malformed text representation offset line in node-rev"));
2222
 
 
2223
 
  rep->uniquifier = apr_pstrdup(pool, str);
2224
 
 
2225
 
  return SVN_NO_ERROR;
2226
 
}
2227
 
 
2228
 
/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
2229
 
   and adding an error message. */
2230
 
static svn_error_t *
2231
 
read_rep_offsets(representation_t **rep_p,
2232
 
                 char *string,
2233
 
                 const svn_fs_id_t *noderev_id,
2234
 
                 svn_boolean_t mutable_rep_truncated,
2235
 
                 apr_pool_t *pool)
2236
 
{
2237
 
  svn_error_t *err;
2238
 
  const char *txn_id;
2239
 
 
2240
 
  if (noderev_id)
2241
 
    txn_id = svn_fs_fs__id_txn_id(noderev_id);
2242
 
  else
2243
 
    txn_id = NULL;
2244
 
 
2245
 
  err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
2246
 
                              pool);
2247
 
  if (err)
2248
 
    {
2249
 
      const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
2250
 
      const char *where;
2251
 
      where = apr_psprintf(pool,
2252
 
                           _("While reading representation offsets "
2253
 
                             "for node-revision '%s':"),
2254
 
                           noderev_id ? id_unparsed->data : "(null)");
2255
 
 
2256
 
      return svn_error_quick_wrap(err, where);
2257
 
    }
2258
 
  else
2259
 
    return SVN_NO_ERROR;
2260
 
}
2261
 
 
2262
 
static svn_error_t *
2263
 
err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
2264
 
{
2265
 
  svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
2266
 
  return svn_error_createf
2267
 
    (SVN_ERR_FS_ID_NOT_FOUND, 0,
2268
 
     _("Reference to non-existent node '%s' in filesystem '%s'"),
2269
 
     id_str->data, fs->path);
2270
 
}
2271
 
 
2272
 
/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
2273
 
 * caching has been enabled and the data can be found, IS_CACHED will
2274
 
 * be set to TRUE. The noderev will be allocated from POOL.
2275
 
 *
2276
 
 * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2277
 
 */
2278
 
static svn_error_t *
2279
 
get_cached_node_revision_body(node_revision_t **noderev_p,
2280
 
                              svn_fs_t *fs,
2281
 
                              const svn_fs_id_t *id,
2282
 
                              svn_boolean_t *is_cached,
2283
 
                              apr_pool_t *pool)
2284
 
{
2285
 
  fs_fs_data_t *ffd = fs->fsap_data;
2286
 
  if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
2287
 
    {
2288
 
      *is_cached = FALSE;
2289
 
    }
2290
 
  else
2291
 
    {
2292
 
      pair_cache_key_t key = { 0 };
2293
 
 
2294
 
      key.revision = svn_fs_fs__id_rev(id);
2295
 
      key.second = svn_fs_fs__id_offset(id);
2296
 
      SVN_ERR(svn_cache__get((void **) noderev_p,
2297
 
                            is_cached,
2298
 
                            ffd->node_revision_cache,
2299
 
                            &key,
2300
 
                            pool));
2301
 
    }
2302
 
 
2303
 
  return SVN_NO_ERROR;
2304
 
}
2305
 
 
2306
 
/* If noderev caching has been enabled, store the NODEREV_P for the given ID
2307
 
 * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
2308
 
 *
2309
 
 * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2310
 
 */
2311
 
static svn_error_t *
2312
 
set_cached_node_revision_body(node_revision_t *noderev_p,
2313
 
                              svn_fs_t *fs,
2314
 
                              const svn_fs_id_t *id,
2315
 
                              apr_pool_t *scratch_pool)
2316
 
{
2317
 
  fs_fs_data_t *ffd = fs->fsap_data;
2318
 
 
2319
 
  if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
2320
 
    {
2321
 
      pair_cache_key_t key = { 0 };
2322
 
 
2323
 
      key.revision = svn_fs_fs__id_rev(id);
2324
 
      key.second = svn_fs_fs__id_offset(id);
2325
 
      return svn_cache__set(ffd->node_revision_cache,
2326
 
                            &key,
2327
 
                            noderev_p,
2328
 
                            scratch_pool);
2329
 
    }
2330
 
 
2331
 
  return SVN_NO_ERROR;
2332
 
}
2333
 
 
2334
 
/* Get the node-revision for the node ID in FS.
2335
 
   Set *NODEREV_P to the new node-revision structure, allocated in POOL.
2336
 
   See svn_fs_fs__get_node_revision, which wraps this and adds another
2337
 
   error. */
2338
 
static svn_error_t *
2339
 
get_node_revision_body(node_revision_t **noderev_p,
2340
 
                       svn_fs_t *fs,
2341
 
                       const svn_fs_id_t *id,
2342
 
                       apr_pool_t *pool)
2343
 
{
2344
 
  apr_file_t *revision_file;
2345
 
  svn_error_t *err;
2346
 
  svn_boolean_t is_cached = FALSE;
2347
 
 
2348
 
  /* First, try a cache lookup. If that succeeds, we are done here. */
2349
 
  SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
2350
 
  if (is_cached)
2351
 
    return SVN_NO_ERROR;
2352
 
 
2353
 
  if (svn_fs_fs__id_txn_id(id))
2354
 
    {
2355
 
      /* This is a transaction node-rev. */
2356
 
      err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
2357
 
                             APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
2358
 
    }
2359
 
  else
2360
 
    {
2361
 
      /* This is a revision node-rev. */
2362
 
      err = open_and_seek_revision(&revision_file, fs,
2363
 
                                   svn_fs_fs__id_rev(id),
2364
 
                                   svn_fs_fs__id_offset(id),
2365
 
                                   pool);
2366
 
    }
2367
 
 
2368
 
  if (err)
2369
 
    {
2370
 
      if (APR_STATUS_IS_ENOENT(err->apr_err))
2371
 
        {
2372
 
          svn_error_clear(err);
2373
 
          return svn_error_trace(err_dangling_id(fs, id));
2374
 
        }
2375
 
 
2376
 
      return svn_error_trace(err);
2377
 
    }
2378
 
 
2379
 
  SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
2380
 
                                  svn_stream_from_aprfile2(revision_file, FALSE,
2381
 
                                                           pool),
2382
 
                                  pool));
2383
 
 
2384
 
  /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
2385
 
  return set_cached_node_revision_body(*noderev_p, fs, id, pool);
2386
 
}
2387
 
 
2388
 
svn_error_t *
2389
 
svn_fs_fs__read_noderev(node_revision_t **noderev_p,
2390
 
                        svn_stream_t *stream,
2391
 
                        apr_pool_t *pool)
2392
 
{
2393
 
  apr_hash_t *headers;
2394
 
  node_revision_t *noderev;
2395
 
  char *value;
2396
 
  const char *noderev_id;
2397
 
 
2398
 
  SVN_ERR(read_header_block(&headers, stream, pool));
2399
 
 
2400
 
  noderev = apr_pcalloc(pool, sizeof(*noderev));
2401
 
 
2402
 
  /* Read the node-rev id. */
2403
 
  value = svn_hash_gets(headers, HEADER_ID);
2404
 
  if (value == NULL)
2405
 
      /* ### More information: filename/offset coordinates */
2406
 
      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2407
 
                              _("Missing id field in node-rev"));
2408
 
 
2409
 
  SVN_ERR(svn_stream_close(stream));
2410
 
 
2411
 
  noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
2412
 
  noderev_id = value; /* for error messages later */
2413
 
 
2414
 
  /* Read the type. */
2415
 
  value = svn_hash_gets(headers, HEADER_TYPE);
2416
 
 
2417
 
  if ((value == NULL) ||
2418
 
      (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
2419
 
    /* ### s/kind/type/ */
2420
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2421
 
                             _("Missing kind field in node-rev '%s'"),
2422
 
                             noderev_id);
2423
 
 
2424
 
  noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
2425
 
    : svn_node_dir;
2426
 
 
2427
 
  /* Read the 'count' field. */
2428
 
  value = svn_hash_gets(headers, HEADER_COUNT);
2429
 
  if (value)
2430
 
    SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
2431
 
  else
2432
 
    noderev->predecessor_count = 0;
2433
 
 
2434
 
  /* Get the properties location. */
2435
 
  value = svn_hash_gets(headers, HEADER_PROPS);
2436
 
  if (value)
2437
 
    {
2438
 
      SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
2439
 
                               noderev->id, TRUE, pool));
2440
 
    }
2441
 
 
2442
 
  /* Get the data location. */
2443
 
  value = svn_hash_gets(headers, HEADER_TEXT);
2444
 
  if (value)
2445
 
    {
2446
 
      SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
2447
 
                               noderev->id,
2448
 
                               (noderev->kind == svn_node_dir), pool));
2449
 
    }
2450
 
 
2451
 
  /* Get the created path. */
2452
 
  value = svn_hash_gets(headers, HEADER_CPATH);
2453
 
  if (value == NULL)
2454
 
    {
2455
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2456
 
                               _("Missing cpath field in node-rev '%s'"),
2457
 
                               noderev_id);
2458
 
    }
2459
 
  else
2460
 
    {
2461
 
      noderev->created_path = apr_pstrdup(pool, value);
2462
 
    }
2463
 
 
2464
 
  /* Get the predecessor ID. */
2465
 
  value = svn_hash_gets(headers, HEADER_PRED);
2466
 
  if (value)
2467
 
    noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
2468
 
                                                  pool);
2469
 
 
2470
 
  /* Get the copyroot. */
2471
 
  value = svn_hash_gets(headers, HEADER_COPYROOT);
2472
 
  if (value == NULL)
2473
 
    {
2474
 
      noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
2475
 
      noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
2476
 
    }
2477
 
  else
2478
 
    {
2479
 
      char *str;
2480
 
 
2481
 
      str = svn_cstring_tokenize(" ", &value);
2482
 
      if (str == NULL)
2483
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2484
 
                                 _("Malformed copyroot line in node-rev '%s'"),
2485
 
                                 noderev_id);
2486
 
 
2487
 
      noderev->copyroot_rev = SVN_STR_TO_REV(str);
2488
 
 
2489
 
      if (*value == '\0')
2490
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2491
 
                                 _("Malformed copyroot line in node-rev '%s'"),
2492
 
                                 noderev_id);
2493
 
      noderev->copyroot_path = apr_pstrdup(pool, value);
2494
 
    }
2495
 
 
2496
 
  /* Get the copyfrom. */
2497
 
  value = svn_hash_gets(headers, HEADER_COPYFROM);
2498
 
  if (value == NULL)
2499
 
    {
2500
 
      noderev->copyfrom_path = NULL;
2501
 
      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
2502
 
    }
2503
 
  else
2504
 
    {
2505
 
      char *str = svn_cstring_tokenize(" ", &value);
2506
 
      if (str == NULL)
2507
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2508
 
                                 _("Malformed copyfrom line in node-rev '%s'"),
2509
 
                                 noderev_id);
2510
 
 
2511
 
      noderev->copyfrom_rev = SVN_STR_TO_REV(str);
2512
 
 
2513
 
      if (*value == 0)
2514
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2515
 
                                 _("Malformed copyfrom line in node-rev '%s'"),
2516
 
                                 noderev_id);
2517
 
      noderev->copyfrom_path = apr_pstrdup(pool, value);
2518
 
    }
2519
 
 
2520
 
  /* Get whether this is a fresh txn root. */
2521
 
  value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
2522
 
  noderev->is_fresh_txn_root = (value != NULL);
2523
 
 
2524
 
  /* Get the mergeinfo count. */
2525
 
  value = svn_hash_gets(headers, HEADER_MINFO_CNT);
2526
 
  if (value)
2527
 
    SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
2528
 
  else
2529
 
    noderev->mergeinfo_count = 0;
2530
 
 
2531
 
  /* Get whether *this* node has mergeinfo. */
2532
 
  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
2533
 
  noderev->has_mergeinfo = (value != NULL);
2534
 
 
2535
 
  *noderev_p = noderev;
2536
 
 
2537
 
  return SVN_NO_ERROR;
2538
 
}
2539
 
 
2540
 
svn_error_t *
2541
 
svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
2542
 
                             svn_fs_t *fs,
2543
 
                             const svn_fs_id_t *id,
2544
 
                             apr_pool_t *pool)
2545
 
{
2546
 
  svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
2547
 
  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
2548
 
    {
2549
 
      svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
2550
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
2551
 
                               "Corrupt node-revision '%s'",
2552
 
                               id_string->data);
2553
 
    }
2554
 
  return svn_error_trace(err);
2555
 
}
2556
 
 
2557
 
 
2558
 
/* Return a formatted string, compatible with filesystem format FORMAT,
2559
 
   that represents the location of representation REP.  If
2560
 
   MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
2561
 
   and only a "-1" revision number will be given for a mutable rep.
2562
 
   If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
2563
 
   Perform the allocation from POOL.  */
2564
 
static const char *
2565
 
representation_string(representation_t *rep,
2566
 
                      int format,
2567
 
                      svn_boolean_t mutable_rep_truncated,
2568
 
                      svn_boolean_t may_be_corrupt,
2569
 
                      apr_pool_t *pool)
2570
 
{
2571
 
  if (rep->txn_id && mutable_rep_truncated)
2572
 
    return "-1";
2573
 
 
2574
 
#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum)          \
2575
 
  ((!may_be_corrupt || (checksum) != NULL)     \
2576
 
   ? svn_checksum_to_cstring_display((checksum), pool) \
2577
 
   : "(null)")
2578
 
 
2579
 
  if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
2580
 
    return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2581
 
                        " %" SVN_FILESIZE_T_FMT " %s",
2582
 
                        rep->revision, rep->offset, rep->size,
2583
 
                        rep->expanded_size,
2584
 
                        DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
2585
 
 
2586
 
  return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2587
 
                      " %" SVN_FILESIZE_T_FMT " %s %s %s",
2588
 
                      rep->revision, rep->offset, rep->size,
2589
 
                      rep->expanded_size,
2590
 
                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
2591
 
                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
2592
 
                      rep->uniquifier);
2593
 
 
2594
 
#undef DISPLAY_MAYBE_NULL_CHECKSUM
2595
 
 
2596
 
}
2597
 
 
2598
 
 
2599
 
svn_error_t *
2600
 
svn_fs_fs__write_noderev(svn_stream_t *outfile,
2601
 
                         node_revision_t *noderev,
2602
 
                         int format,
2603
 
                         svn_boolean_t include_mergeinfo,
2604
 
                         apr_pool_t *pool)
2605
 
{
2606
 
  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
2607
 
                            svn_fs_fs__id_unparse(noderev->id,
2608
 
                                                  pool)->data));
2609
 
 
2610
 
  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
2611
 
                            (noderev->kind == svn_node_file) ?
2612
 
                            KIND_FILE : KIND_DIR));
2613
 
 
2614
 
  if (noderev->predecessor_id)
2615
 
    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
2616
 
                              svn_fs_fs__id_unparse(noderev->predecessor_id,
2617
 
                                                    pool)->data));
2618
 
 
2619
 
  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
2620
 
                            noderev->predecessor_count));
2621
 
 
2622
 
  if (noderev->data_rep)
2623
 
    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
2624
 
                              representation_string(noderev->data_rep,
2625
 
                                                    format,
2626
 
                                                    (noderev->kind
2627
 
                                                     == svn_node_dir),
2628
 
                                                    FALSE,
2629
 
                                                    pool)));
2630
 
 
2631
 
  if (noderev->prop_rep)
2632
 
    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
2633
 
                              representation_string(noderev->prop_rep, format,
2634
 
                                                    TRUE, FALSE, pool)));
2635
 
 
2636
 
  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
2637
 
                            noderev->created_path));
2638
 
 
2639
 
  if (noderev->copyfrom_path)
2640
 
    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
2641
 
                              " %s\n",
2642
 
                              noderev->copyfrom_rev,
2643
 
                              noderev->copyfrom_path));
2644
 
 
2645
 
  if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
2646
 
      (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
2647
 
    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
2648
 
                              " %s\n",
2649
 
                              noderev->copyroot_rev,
2650
 
                              noderev->copyroot_path));
2651
 
 
2652
 
  if (noderev->is_fresh_txn_root)
2653
 
    SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
2654
 
 
2655
 
  if (include_mergeinfo)
2656
 
    {
2657
 
      if (noderev->mergeinfo_count > 0)
2658
 
        SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
2659
 
                                  APR_INT64_T_FMT "\n",
2660
 
                                  noderev->mergeinfo_count));
2661
 
 
2662
 
      if (noderev->has_mergeinfo)
2663
 
        SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
2664
 
    }
2665
 
 
2666
 
  return svn_stream_puts(outfile, "\n");
2667
 
}
2668
 
 
2669
 
svn_error_t *
2670
 
svn_fs_fs__put_node_revision(svn_fs_t *fs,
2671
 
                             const svn_fs_id_t *id,
2672
 
                             node_revision_t *noderev,
2673
 
                             svn_boolean_t fresh_txn_root,
2674
 
                             apr_pool_t *pool)
2675
 
{
2676
 
  fs_fs_data_t *ffd = fs->fsap_data;
2677
 
  apr_file_t *noderev_file;
2678
 
  const char *txn_id = svn_fs_fs__id_txn_id(id);
2679
 
 
2680
 
  noderev->is_fresh_txn_root = fresh_txn_root;
2681
 
 
2682
 
  if (! txn_id)
2683
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2684
 
                             _("Attempted to write to non-transaction '%s'"),
2685
 
                             svn_fs_fs__id_unparse(id, pool)->data);
2686
 
 
2687
 
  SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
2688
 
                           APR_WRITE | APR_CREATE | APR_TRUNCATE
2689
 
                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
2690
 
 
2691
 
  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
2692
 
                                                            pool),
2693
 
                                   noderev, ffd->format,
2694
 
                                   svn_fs_fs__fs_supports_mergeinfo(fs),
2695
 
                                   pool));
2696
 
 
2697
 
  SVN_ERR(svn_io_file_close(noderev_file, pool));
2698
 
 
2699
 
  return SVN_NO_ERROR;
2700
 
}
2701
 
 
2702
 
/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
2703
 
 * file in the respective transaction, if rep sharing has been enabled etc.
2704
 
 * Use POOL for temporary allocations.
2705
 
 */
2706
 
static svn_error_t *
2707
 
store_sha1_rep_mapping(svn_fs_t *fs,
2708
 
                       node_revision_t *noderev,
2709
 
                       apr_pool_t *pool)
2710
 
{
2711
 
  fs_fs_data_t *ffd = fs->fsap_data;
2712
 
 
2713
 
  /* if rep sharing has been enabled and the noderev has a data rep and
2714
 
   * its SHA-1 is known, store the rep struct under its SHA1. */
2715
 
  if (   ffd->rep_sharing_allowed
2716
 
      && noderev->data_rep
2717
 
      && noderev->data_rep->sha1_checksum)
2718
 
    {
2719
 
      apr_file_t *rep_file;
2720
 
      const char *file_name = path_txn_sha1(fs,
2721
 
                                            svn_fs_fs__id_txn_id(noderev->id),
2722
 
                                            noderev->data_rep->sha1_checksum,
2723
 
                                            pool);
2724
 
      const char *rep_string = representation_string(noderev->data_rep,
2725
 
                                                     ffd->format,
2726
 
                                                     (noderev->kind
2727
 
                                                      == svn_node_dir),
2728
 
                                                     FALSE,
2729
 
                                                     pool);
2730
 
      SVN_ERR(svn_io_file_open(&rep_file, file_name,
2731
 
                               APR_WRITE | APR_CREATE | APR_TRUNCATE
2732
 
                               | APR_BUFFERED, APR_OS_DEFAULT, pool));
2733
 
 
2734
 
      SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
2735
 
                                     strlen(rep_string), NULL, pool));
2736
 
 
2737
 
      SVN_ERR(svn_io_file_close(rep_file, pool));
2738
 
    }
2739
 
 
2740
 
  return SVN_NO_ERROR;
2741
 
}
2742
 
 
2743
 
 
2744
 
/* This structure is used to hold the information associated with a
2745
 
   REP line. */
2746
 
struct rep_args
2747
 
{
2748
 
  svn_boolean_t is_delta;
2749
 
  svn_boolean_t is_delta_vs_empty;
2750
 
 
2751
 
  svn_revnum_t base_revision;
2752
 
  apr_off_t base_offset;
2753
 
  svn_filesize_t base_length;
2754
 
};
2755
 
 
2756
 
/* Read the next line from file FILE and parse it as a text
2757
 
   representation entry.  Return the parsed entry in *REP_ARGS_P.
2758
 
   Perform all allocations in POOL. */
2759
 
static svn_error_t *
2760
 
read_rep_line(struct rep_args **rep_args_p,
2761
 
              apr_file_t *file,
2762
 
              apr_pool_t *pool)
2763
 
{
2764
 
  char buffer[160];
2765
 
  apr_size_t limit;
2766
 
  struct rep_args *rep_args;
2767
 
  char *str, *last_str = buffer;
2768
 
  apr_int64_t val;
2769
 
 
2770
 
  limit = sizeof(buffer);
2771
 
  SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
2772
 
 
2773
 
  rep_args = apr_pcalloc(pool, sizeof(*rep_args));
2774
 
  rep_args->is_delta = FALSE;
2775
 
 
2776
 
  if (strcmp(buffer, REP_PLAIN) == 0)
2777
 
    {
2778
 
      *rep_args_p = rep_args;
2779
 
      return SVN_NO_ERROR;
2780
 
    }
2781
 
 
2782
 
  if (strcmp(buffer, REP_DELTA) == 0)
2783
 
    {
2784
 
      /* This is a delta against the empty stream. */
2785
 
      rep_args->is_delta = TRUE;
2786
 
      rep_args->is_delta_vs_empty = TRUE;
2787
 
      *rep_args_p = rep_args;
2788
 
      return SVN_NO_ERROR;
2789
 
    }
2790
 
 
2791
 
  rep_args->is_delta = TRUE;
2792
 
  rep_args->is_delta_vs_empty = FALSE;
2793
 
 
2794
 
  /* We have hopefully a DELTA vs. a non-empty base revision. */
2795
 
  str = svn_cstring_tokenize(" ", &last_str);
2796
 
  if (! str || (strcmp(str, REP_DELTA) != 0))
2797
 
    goto error;
2798
 
 
2799
 
  str = svn_cstring_tokenize(" ", &last_str);
2800
 
  if (! str)
2801
 
    goto error;
2802
 
  rep_args->base_revision = SVN_STR_TO_REV(str);
2803
 
 
2804
 
  str = svn_cstring_tokenize(" ", &last_str);
2805
 
  if (! str)
2806
 
    goto error;
2807
 
  SVN_ERR(svn_cstring_atoi64(&val, str));
2808
 
  rep_args->base_offset = (apr_off_t)val;
2809
 
 
2810
 
  str = svn_cstring_tokenize(" ", &last_str);
2811
 
  if (! str)
2812
 
    goto error;
2813
 
  SVN_ERR(svn_cstring_atoi64(&val, str));
2814
 
  rep_args->base_length = (svn_filesize_t)val;
2815
 
 
2816
 
  *rep_args_p = rep_args;
2817
 
  return SVN_NO_ERROR;
2818
 
 
2819
 
 error:
2820
 
  return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2821
 
                           _("Malformed representation header at %s"),
2822
 
                           path_and_offset_of(file, pool));
2823
 
}
2824
 
 
2825
 
/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
2826
 
   of the header located at OFFSET and store it in *ID_P.  Allocate
2827
 
   temporary variables from POOL. */
2828
 
static svn_error_t *
2829
 
get_fs_id_at_offset(svn_fs_id_t **id_p,
2830
 
                    apr_file_t *rev_file,
2831
 
                    svn_fs_t *fs,
2832
 
                    svn_revnum_t rev,
2833
 
                    apr_off_t offset,
2834
 
                    apr_pool_t *pool)
2835
 
{
2836
 
  svn_fs_id_t *id;
2837
 
  apr_hash_t *headers;
2838
 
  const char *node_id_str;
2839
 
 
2840
 
  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2841
 
 
2842
 
  SVN_ERR(read_header_block(&headers,
2843
 
                            svn_stream_from_aprfile2(rev_file, TRUE, pool),
2844
 
                            pool));
2845
 
 
2846
 
  /* In error messages, the offset is relative to the pack file,
2847
 
     not to the rev file. */
2848
 
 
2849
 
  node_id_str = svn_hash_gets(headers, HEADER_ID);
2850
 
 
2851
 
  if (node_id_str == NULL)
2852
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2853
 
                             _("Missing node-id in node-rev at r%ld "
2854
 
                             "(offset %s)"),
2855
 
                             rev,
2856
 
                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2857
 
 
2858
 
  id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
2859
 
 
2860
 
  if (id == NULL)
2861
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2862
 
                             _("Corrupt node-id '%s' in node-rev at r%ld "
2863
 
                               "(offset %s)"),
2864
 
                             node_id_str, rev,
2865
 
                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2866
 
 
2867
 
  *id_p = id;
2868
 
 
2869
 
  /* ### assert that the txn_id is REV/OFFSET ? */
2870
 
 
2871
 
  return SVN_NO_ERROR;
2872
 
}
2873
 
 
2874
 
 
2875
 
/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
2876
 
   specifies the offset to the root node-id and to the changed path
2877
 
   information.  Store the root node offset in *ROOT_OFFSET and the
2878
 
   changed path offset in *CHANGES_OFFSET.  If either of these
2879
 
   pointers is NULL, do nothing with it.
2880
 
 
2881
 
   If PACKED is true, REV_FILE should be a packed shard file.
2882
 
   ### There is currently no such parameter.  This function assumes that
2883
 
       is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
2884
 
       file.  Therefore FS->fsap_data->min_unpacked_rev must not have been
2885
 
       refreshed since REV_FILE was opened if there is a possibility that
2886
 
       revision REV may have become packed since then.
2887
 
       TODO: Take an IS_PACKED parameter instead, in order to remove this
2888
 
       requirement.
2889
 
 
2890
 
   Allocate temporary variables from POOL. */
2891
 
static svn_error_t *
2892
 
get_root_changes_offset(apr_off_t *root_offset,
2893
 
                        apr_off_t *changes_offset,
2894
 
                        apr_file_t *rev_file,
2895
 
                        svn_fs_t *fs,
2896
 
                        svn_revnum_t rev,
2897
 
                        apr_pool_t *pool)
2898
 
{
2899
 
  fs_fs_data_t *ffd = fs->fsap_data;
2900
 
  apr_off_t offset;
2901
 
  apr_off_t rev_offset;
2902
 
  char buf[64];
2903
 
  int i, num_bytes;
2904
 
  const char *str;
2905
 
  apr_size_t len;
2906
 
  apr_seek_where_t seek_relative;
2907
 
 
2908
 
  /* Determine where to seek to in the file.
2909
 
 
2910
 
     If we've got a pack file, we want to seek to the end of the desired
2911
 
     revision.  But we don't track that, so we seek to the beginning of the
2912
 
     next revision.
2913
 
 
2914
 
     Unless the next revision is in a different file, in which case, we can
2915
 
     just seek to the end of the pack file -- just like we do in the
2916
 
     non-packed case. */
2917
 
  if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
2918
 
    {
2919
 
      SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
2920
 
      seek_relative = APR_SET;
2921
 
    }
2922
 
  else
2923
 
    {
2924
 
      seek_relative = APR_END;
2925
 
      offset = 0;
2926
 
    }
2927
 
 
2928
 
  /* Offset of the revision from the start of the pack file, if applicable. */
2929
 
  if (is_packed_rev(fs, rev))
2930
 
    SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2931
 
  else
2932
 
    rev_offset = 0;
2933
 
 
2934
 
  /* We will assume that the last line containing the two offsets
2935
 
     will never be longer than 64 characters. */
2936
 
  SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
2937
 
 
2938
 
  offset -= sizeof(buf);
2939
 
  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2940
 
 
2941
 
  /* Read in this last block, from which we will identify the last line. */
2942
 
  len = sizeof(buf);
2943
 
  SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
2944
 
 
2945
 
  /* This cast should be safe since the maximum amount read, 64, will
2946
 
     never be bigger than the size of an int. */
2947
 
  num_bytes = (int) len;
2948
 
 
2949
 
  /* The last byte should be a newline. */
2950
 
  if (buf[num_bytes - 1] != '\n')
2951
 
    {
2952
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2953
 
                               _("Revision file (r%ld) lacks trailing newline"),
2954
 
                               rev);
2955
 
    }
2956
 
 
2957
 
  /* Look for the next previous newline. */
2958
 
  for (i = num_bytes - 2; i >= 0; i--)
2959
 
    {
2960
 
      if (buf[i] == '\n')
2961
 
        break;
2962
 
    }
2963
 
 
2964
 
  if (i < 0)
2965
 
    {
2966
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2967
 
                               _("Final line in revision file (r%ld) longer "
2968
 
                                 "than 64 characters"),
2969
 
                               rev);
2970
 
    }
2971
 
 
2972
 
  i++;
2973
 
  str = &buf[i];
2974
 
 
2975
 
  /* find the next space */
2976
 
  for ( ; i < (num_bytes - 2) ; i++)
2977
 
    if (buf[i] == ' ')
2978
 
      break;
2979
 
 
2980
 
  if (i == (num_bytes - 2))
2981
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2982
 
                             _("Final line in revision file r%ld missing space"),
2983
 
                             rev);
2984
 
 
2985
 
  if (root_offset)
2986
 
    {
2987
 
      apr_int64_t val;
2988
 
 
2989
 
      buf[i] = '\0';
2990
 
      SVN_ERR(svn_cstring_atoi64(&val, str));
2991
 
      *root_offset = rev_offset + (apr_off_t)val;
2992
 
    }
2993
 
 
2994
 
  i++;
2995
 
  str = &buf[i];
2996
 
 
2997
 
  /* find the next newline */
2998
 
  for ( ; i < num_bytes; i++)
2999
 
    if (buf[i] == '\n')
3000
 
      break;
3001
 
 
3002
 
  if (changes_offset)
3003
 
    {
3004
 
      apr_int64_t val;
3005
 
 
3006
 
      buf[i] = '\0';
3007
 
      SVN_ERR(svn_cstring_atoi64(&val, str));
3008
 
      *changes_offset = rev_offset + (apr_off_t)val;
3009
 
    }
3010
 
 
3011
 
  return SVN_NO_ERROR;
3012
 
}
3013
 
 
3014
 
/* Move a file into place from OLD_FILENAME in the transactions
3015
 
   directory to its final location NEW_FILENAME in the repository.  On
3016
 
   Unix, match the permissions of the new file to the permissions of
3017
 
   PERMS_REFERENCE.  Temporary allocations are from POOL.
3018
 
 
3019
 
   This function almost duplicates svn_io_file_move(), but it tries to
3020
 
   guarantee a flush. */
3021
 
static svn_error_t *
3022
 
move_into_place(const char *old_filename,
3023
 
                const char *new_filename,
3024
 
                const char *perms_reference,
3025
 
                apr_pool_t *pool)
3026
 
{
3027
 
  svn_error_t *err;
3028
 
 
3029
 
  SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
3030
 
 
3031
 
  /* Move the file into place. */
3032
 
  err = svn_io_file_rename(old_filename, new_filename, pool);
3033
 
  if (err && APR_STATUS_IS_EXDEV(err->apr_err))
3034
 
    {
3035
 
      apr_file_t *file;
3036
 
 
3037
 
      /* Can't rename across devices; fall back to copying. */
3038
 
      svn_error_clear(err);
3039
 
      err = SVN_NO_ERROR;
3040
 
      SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
3041
 
 
3042
 
      /* Flush the target of the copy to disk. */
3043
 
      SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
3044
 
                               APR_OS_DEFAULT, pool));
3045
 
      /* ### BH: Does this really guarantee a flush of the data written
3046
 
         ### via a completely different handle on all operating systems?
3047
 
         ###
3048
 
         ### Maybe we should perform the copy ourselves instead of making
3049
 
         ### apr do that and flush the real handle? */
3050
 
      SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3051
 
      SVN_ERR(svn_io_file_close(file, pool));
3052
 
    }
3053
 
  if (err)
3054
 
    return svn_error_trace(err);
3055
 
 
3056
 
#ifdef __linux__
3057
 
  {
3058
 
    /* Linux has the unusual feature that fsync() on a file is not
3059
 
       enough to ensure that a file's directory entries have been
3060
 
       flushed to disk; you have to fsync the directory as well.
3061
 
       On other operating systems, we'd only be asking for trouble
3062
 
       by trying to open and fsync a directory. */
3063
 
    const char *dirname;
3064
 
    apr_file_t *file;
3065
 
 
3066
 
    dirname = svn_dirent_dirname(new_filename, pool);
3067
 
    SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
3068
 
                             pool));
3069
 
    SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3070
 
    SVN_ERR(svn_io_file_close(file, pool));
3071
 
  }
3072
 
#endif
3073
 
 
3074
 
  return SVN_NO_ERROR;
3075
 
}
3076
 
 
3077
 
svn_error_t *
3078
 
svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
3079
 
                        svn_fs_t *fs,
3080
 
                        svn_revnum_t rev,
3081
 
                        apr_pool_t *pool)
3082
 
{
3083
 
  fs_fs_data_t *ffd = fs->fsap_data;
3084
 
  apr_file_t *revision_file;
3085
 
  apr_off_t root_offset;
3086
 
  svn_fs_id_t *root_id = NULL;
3087
 
  svn_boolean_t is_cached;
3088
 
 
3089
 
  SVN_ERR(ensure_revision_exists(fs, rev, pool));
3090
 
 
3091
 
  SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
3092
 
                         ffd->rev_root_id_cache, &rev, pool));
3093
 
  if (is_cached)
3094
 
    return SVN_NO_ERROR;
3095
 
 
3096
 
  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
3097
 
  SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
3098
 
                                  pool));
3099
 
 
3100
 
  SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
3101
 
                              root_offset, pool));
3102
 
 
3103
 
  SVN_ERR(svn_io_file_close(revision_file, pool));
3104
 
 
3105
 
  SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
3106
 
 
3107
 
  *root_id_p = root_id;
3108
 
 
3109
 
  return SVN_NO_ERROR;
3110
 
}
3111
 
 
3112
 
/* Revprop caching management.
3113
 
 *
3114
 
 * Mechanism:
3115
 
 * ----------
3116
 
 *
3117
 
 * Revprop caching needs to be activated and will be deactivated for the
3118
 
 * respective FS instance if the necessary infrastructure could not be
3119
 
 * initialized.  In deactivated mode, there is almost no runtime overhead
3120
 
 * associated with revprop caching.  As long as no revprops are being read
3121
 
 * or changed, revprop caching imposes no overhead.
3122
 
 *
3123
 
 * When activated, we cache revprops using (revision, generation) pairs
3124
 
 * as keys with the generation being incremented upon every revprop change.
3125
 
 * Since the cache is process-local, the generation needs to be tracked
3126
 
 * for at least as long as the process lives but may be reset afterwards.
3127
 
 *
3128
 
 * To track the revprop generation, we use two-layer approach. On the lower
3129
 
 * level, we use named atomics to have a system-wide consistent value for
3130
 
 * the current revprop generation.  However, those named atomics will only
3131
 
 * remain valid for as long as at least one process / thread in the system
3132
 
 * accesses revprops in the respective repository.  The underlying shared
3133
 
 * memory gets cleaned up afterwards.
3134
 
 *
3135
 
 * On the second level, we will use a persistent file to track the latest
3136
 
 * revprop generation.  It will be written upon each revprop change but
3137
 
 * only be read if we are the first process to initialize the named atomics
3138
 
 * with that value.
3139
 
 *
3140
 
 * The overhead for the second and following accesses to revprops is
3141
 
 * almost zero on most systems.
3142
 
 *
3143
 
 *
3144
 
 * Tech aspects:
3145
 
 * -------------
3146
 
 *
3147
 
 * A problem is that we need to provide a globally available file name to
3148
 
 * back the SHM implementation on OSes that need it.  We can only assume
3149
 
 * write access to some file within the respective repositories.  Because
3150
 
 * a given server process may access thousands of repositories during its
3151
 
 * lifetime, keeping the SHM data alive for all of them is also not an
3152
 
 * option.
3153
 
 *
3154
 
 * So, we store the new revprop generation on disk as part of each
3155
 
 * setrevprop call, i.e. this write will be serialized and the write order
3156
 
 * be guaranteed by the repository write lock.
3157
 
 *
3158
 
 * The only racy situation occurs when the data is being read again by two
3159
 
 * processes concurrently but in that situation, the first process to
3160
 
 * finish that procedure is guaranteed to be the only one that initializes
3161
 
 * the SHM data.  Since even writers will first go through that
3162
 
 * initialization phase, they will never operate on stale data.
3163
 
 */
3164
 
 
3165
 
/* Read revprop generation as stored on disk for repository FS. The result
3166
 
 * is returned in *CURRENT. Default to 2 if no such file is available.
3167
 
 */
3168
 
static svn_error_t *
3169
 
read_revprop_generation_file(apr_int64_t *current,
3170
 
                             svn_fs_t *fs,
3171
 
                             apr_pool_t *pool)
3172
 
{
3173
 
  svn_error_t *err;
3174
 
  apr_file_t *file;
3175
 
  char buf[80];
3176
 
  apr_size_t len;
3177
 
  const char *path = path_revprop_generation(fs, pool);
3178
 
 
3179
 
  err = svn_io_file_open(&file, path,
3180
 
                         APR_READ | APR_BUFFERED,
3181
 
                         APR_OS_DEFAULT, pool);
3182
 
  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3183
 
    {
3184
 
      svn_error_clear(err);
3185
 
      *current = 2;
3186
 
 
3187
 
      return SVN_NO_ERROR;
3188
 
    }
3189
 
  SVN_ERR(err);
3190
 
 
3191
 
  len = sizeof(buf);
3192
 
  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3193
 
 
3194
 
  /* Check that the first line contains only digits. */
3195
 
  SVN_ERR(check_file_buffer_numeric(buf, 0, path,
3196
 
                                    "Revprop Generation", pool));
3197
 
  SVN_ERR(svn_cstring_atoi64(current, buf));
3198
 
 
3199
 
  return svn_io_file_close(file, pool);
3200
 
}
3201
 
 
3202
 
/* Write the CURRENT revprop generation to disk for repository FS.
3203
 
 */
3204
 
static svn_error_t *
3205
 
write_revprop_generation_file(svn_fs_t *fs,
3206
 
                              apr_int64_t current,
3207
 
                              apr_pool_t *pool)
3208
 
{
3209
 
  apr_file_t *file;
3210
 
  const char *tmp_path;
3211
 
 
3212
 
  char buf[SVN_INT64_BUFFER_SIZE];
3213
 
  apr_size_t len = svn__i64toa(buf, current);
3214
 
  buf[len] = '\n';
3215
 
 
3216
 
  SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path,
3217
 
                                   svn_io_file_del_none, pool, pool));
3218
 
  SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool));
3219
 
  SVN_ERR(svn_io_file_close(file, pool));
3220
 
 
3221
 
  return move_into_place(tmp_path, path_revprop_generation(fs, pool),
3222
 
                         tmp_path, pool);
3223
 
}
3224
 
 
3225
 
/* Make sure the revprop_namespace member in FS is set. */
3226
 
static svn_error_t *
3227
 
ensure_revprop_namespace(svn_fs_t *fs)
3228
 
{
3229
 
  fs_fs_data_t *ffd = fs->fsap_data;
3230
 
 
3231
 
  return ffd->revprop_namespace == NULL
3232
 
    ? svn_atomic_namespace__create(&ffd->revprop_namespace,
3233
 
                                   svn_dirent_join(fs->path,
3234
 
                                                   ATOMIC_REVPROP_NAMESPACE,
3235
 
                                                   fs->pool),
3236
 
                                   fs->pool)
3237
 
    : SVN_NO_ERROR;
3238
 
}
3239
 
 
3240
 
/* Make sure the revprop_namespace member in FS is set. */
3241
 
static svn_error_t *
3242
 
cleanup_revprop_namespace(svn_fs_t *fs)
3243
 
{
3244
 
  const char *name = svn_dirent_join(fs->path,
3245
 
                                     ATOMIC_REVPROP_NAMESPACE,
3246
 
                                     fs->pool);
3247
 
  return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
3248
 
}
3249
 
 
3250
 
/* Make sure the revprop_generation member in FS is set and, if necessary,
3251
 
 * initialized with the latest value stored on disk.
3252
 
 */
3253
 
static svn_error_t *
3254
 
ensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
3255
 
{
3256
 
  fs_fs_data_t *ffd = fs->fsap_data;
3257
 
 
3258
 
  SVN_ERR(ensure_revprop_namespace(fs));
3259
 
  if (ffd->revprop_generation == NULL)
3260
 
    {
3261
 
      apr_int64_t current = 0;
3262
 
 
3263
 
      SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation,
3264
 
                                    ffd->revprop_namespace,
3265
 
                                    ATOMIC_REVPROP_GENERATION,
3266
 
                                    TRUE));
3267
 
 
3268
 
      /* If the generation is at 0, we just created a new namespace
3269
 
       * (it would be at least 2 otherwise). Read the latest generation
3270
 
       * from disk and if we are the first one to initialize the atomic
3271
 
       * (i.e. is still 0), set it to the value just gotten.
3272
 
       */
3273
 
      SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3274
 
      if (current == 0)
3275
 
        {
3276
 
          SVN_ERR(read_revprop_generation_file(&current, fs, pool));
3277
 
          SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0,
3278
 
                                            ffd->revprop_generation));
3279
 
        }
3280
 
    }
3281
 
 
3282
 
  return SVN_NO_ERROR;
3283
 
}
3284
 
 
3285
 
/* Make sure the revprop_timeout member in FS is set. */
3286
 
static svn_error_t *
3287
 
ensure_revprop_timeout(svn_fs_t *fs)
3288
 
{
3289
 
  fs_fs_data_t *ffd = fs->fsap_data;
3290
 
 
3291
 
  SVN_ERR(ensure_revprop_namespace(fs));
3292
 
  return ffd->revprop_timeout == NULL
3293
 
    ? svn_named_atomic__get(&ffd->revprop_timeout,
3294
 
                            ffd->revprop_namespace,
3295
 
                            ATOMIC_REVPROP_TIMEOUT,
3296
 
                            TRUE)
3297
 
    : SVN_NO_ERROR;
3298
 
}
3299
 
 
3300
 
/* Create an error object with the given MESSAGE and pass it to the
3301
 
   WARNING member of FS. */
3302
 
static void
3303
 
log_revprop_cache_init_warning(svn_fs_t *fs,
3304
 
                               svn_error_t *underlying_err,
3305
 
                               const char *message)
3306
 
{
3307
 
  svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
3308
 
                                       underlying_err,
3309
 
                                       message, fs->path);
3310
 
 
3311
 
  if (fs->warning)
3312
 
    (fs->warning)(fs->warning_baton, err);
3313
 
 
3314
 
  svn_error_clear(err);
3315
 
}
3316
 
 
3317
 
/* Test whether revprop cache and necessary infrastructure are
3318
 
   available in FS. */
3319
 
static svn_boolean_t
3320
 
has_revprop_cache(svn_fs_t *fs, apr_pool_t *pool)
3321
 
{
3322
 
  fs_fs_data_t *ffd = fs->fsap_data;
3323
 
  svn_error_t *error;
3324
 
 
3325
 
  /* is the cache (still) enabled? */
3326
 
  if (ffd->revprop_cache == NULL)
3327
 
    return FALSE;
3328
 
 
3329
 
  /* is it efficient? */
3330
 
  if (!svn_named_atomic__is_efficient())
3331
 
    {
3332
 
      /* access to it would be quite slow
3333
 
       * -> disable the revprop cache for good
3334
 
       */
3335
 
      ffd->revprop_cache = NULL;
3336
 
      log_revprop_cache_init_warning(fs, NULL,
3337
 
                                     "Revprop caching for '%s' disabled"
3338
 
                                     " because it would be inefficient.");
3339
 
 
3340
 
      return FALSE;
3341
 
    }
3342
 
 
3343
 
  /* try to access our SHM-backed infrastructure */
3344
 
  error = ensure_revprop_generation(fs, pool);
3345
 
  if (error)
3346
 
    {
3347
 
      /* failure -> disable revprop cache for good */
3348
 
 
3349
 
      ffd->revprop_cache = NULL;
3350
 
      log_revprop_cache_init_warning(fs, error,
3351
 
                                     "Revprop caching for '%s' disabled "
3352
 
                                     "because SHM infrastructure for revprop "
3353
 
                                     "caching failed to initialize.");
3354
 
 
3355
 
      return FALSE;
3356
 
    }
3357
 
 
3358
 
  return TRUE;
3359
 
}
3360
 
 
3361
 
/* Baton structure for revprop_generation_fixup. */
3362
 
typedef struct revprop_generation_fixup_t
3363
 
{
3364
 
  /* revprop generation to read */
3365
 
  apr_int64_t *generation;
3366
 
 
3367
 
  /* containing the revprop_generation member to query */
3368
 
  fs_fs_data_t *ffd;
3369
 
} revprop_generation_upgrade_t;
3370
 
 
3371
 
/* If the revprop generation has an odd value, it means the original writer
3372
 
   of the revprop got killed. We don't know whether that process as able
3373
 
   to change the revprop data but we assume that it was. Therefore, we
3374
 
   increase the generation in that case to basically invalidate everyones
3375
 
   cache content.
3376
 
   Execute this onlx while holding the write lock to the repo in baton->FFD.
3377
 
 */
3378
 
static svn_error_t *
3379
 
revprop_generation_fixup(void *void_baton,
3380
 
                         apr_pool_t *pool)
3381
 
{
3382
 
  revprop_generation_upgrade_t *baton = void_baton;
3383
 
  assert(baton->ffd->has_write_lock);
3384
 
 
3385
 
  /* Maybe, either the original revprop writer or some other reader has
3386
 
     already corrected / bumped the revprop generation.  Thus, we need
3387
 
     to read it again. */
3388
 
  SVN_ERR(svn_named_atomic__read(baton->generation,
3389
 
                                 baton->ffd->revprop_generation));
3390
 
 
3391
 
  /* Cause everyone to re-read revprops upon their next access, if the
3392
 
     last revprop write did not complete properly. */
3393
 
  while (*baton->generation % 2)
3394
 
    SVN_ERR(svn_named_atomic__add(baton->generation,
3395
 
                                  1,
3396
 
                                  baton->ffd->revprop_generation));
3397
 
 
3398
 
  return SVN_NO_ERROR;
3399
 
}
3400
 
 
3401
 
/* Read the current revprop generation and return it in *GENERATION.
3402
 
   Also, detect aborted / crashed writers and recover from that.
3403
 
   Use the access object in FS to set the shared mem values. */
3404
 
static svn_error_t *
3405
 
read_revprop_generation(apr_int64_t *generation,
3406
 
                        svn_fs_t *fs,
3407
 
                        apr_pool_t *pool)
3408
 
{
3409
 
  apr_int64_t current = 0;
3410
 
  fs_fs_data_t *ffd = fs->fsap_data;
3411
 
 
3412
 
  /* read the current revprop generation number */
3413
 
  SVN_ERR(ensure_revprop_generation(fs, pool));
3414
 
  SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3415
 
 
3416
 
  /* is an unfinished revprop write under the way? */
3417
 
  if (current % 2)
3418
 
    {
3419
 
      apr_int64_t timeout = 0;
3420
 
 
3421
 
      /* read timeout for the write operation */
3422
 
      SVN_ERR(ensure_revprop_timeout(fs));
3423
 
      SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout));
3424
 
 
3425
 
      /* has the writer process been aborted,
3426
 
       * i.e. has the timeout been reached?
3427
 
       */
3428
 
      if (apr_time_now() > timeout)
3429
 
        {
3430
 
          revprop_generation_upgrade_t baton;
3431
 
          baton.generation = &current;
3432
 
          baton.ffd = ffd;
3433
 
 
3434
 
          /* Ensure that the original writer process no longer exists by
3435
 
           * acquiring the write lock to this repository.  Then, fix up
3436
 
           * the revprop generation.
3437
 
           */
3438
 
          if (ffd->has_write_lock)
3439
 
            SVN_ERR(revprop_generation_fixup(&baton, pool));
3440
 
          else
3441
 
            SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
3442
 
                                               &baton, pool));
3443
 
        }
3444
 
    }
3445
 
 
3446
 
  /* return the value we just got */
3447
 
  *generation = current;
3448
 
  return SVN_NO_ERROR;
3449
 
}
3450
 
 
3451
 
/* Set the revprop generation to the next odd number to indicate that
3452
 
   there is a revprop write process under way. If that times out,
3453
 
   readers shall recover from that state & re-read revprops.
3454
 
   Use the access object in FS to set the shared mem value. */
3455
 
static svn_error_t *
3456
 
begin_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3457
 
{
3458
 
  apr_int64_t current;
3459
 
  fs_fs_data_t *ffd = fs->fsap_data;
3460
 
 
3461
 
  /* set the timeout for the write operation */
3462
 
  SVN_ERR(ensure_revprop_timeout(fs));
3463
 
  SVN_ERR(svn_named_atomic__write(NULL,
3464
 
                                  apr_time_now() + REVPROP_CHANGE_TIMEOUT,
3465
 
                                  ffd->revprop_timeout));
3466
 
 
3467
 
  /* set the revprop generation to an odd value to indicate
3468
 
   * that a write is in progress
3469
 
   */
3470
 
  SVN_ERR(ensure_revprop_generation(fs, pool));
3471
 
  do
3472
 
    {
3473
 
      SVN_ERR(svn_named_atomic__add(&current,
3474
 
                                    1,
3475
 
                                    ffd->revprop_generation));
3476
 
    }
3477
 
  while (current % 2 == 0);
3478
 
 
3479
 
  return SVN_NO_ERROR;
3480
 
}
3481
 
 
3482
 
/* Set the revprop generation to the next even number to indicate that
3483
 
   a) readers shall re-read revprops, and
3484
 
   b) the write process has been completed (no recovery required)
3485
 
   Use the access object in FS to set the shared mem value. */
3486
 
static svn_error_t *
3487
 
end_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3488
 
{
3489
 
  apr_int64_t current = 1;
3490
 
  fs_fs_data_t *ffd = fs->fsap_data;
3491
 
 
3492
 
  /* set the revprop generation to an even value to indicate
3493
 
   * that a write has been completed
3494
 
   */
3495
 
  SVN_ERR(ensure_revprop_generation(fs, pool));
3496
 
  do
3497
 
    {
3498
 
      SVN_ERR(svn_named_atomic__add(&current,
3499
 
                                    1,
3500
 
                                    ffd->revprop_generation));
3501
 
    }
3502
 
  while (current % 2);
3503
 
 
3504
 
  /* Save the latest generation to disk. FS is currently in a "locked"
3505
 
   * state such that we can be sure the be the only ones to write that
3506
 
   * file.
3507
 
   */
3508
 
  return write_revprop_generation_file(fs, current, pool);
3509
 
}
3510
 
 
3511
 
/* Container for all data required to access the packed revprop file
3512
 
 * for a given REVISION.  This structure will be filled incrementally
3513
 
 * by read_pack_revprops() its sub-routines.
3514
 
 */
3515
 
typedef struct packed_revprops_t
3516
 
{
3517
 
  /* revision number to read (not necessarily the first in the pack) */
3518
 
  svn_revnum_t revision;
3519
 
 
3520
 
  /* current revprop generation. Used when populating the revprop cache */
3521
 
  apr_int64_t generation;
3522
 
 
3523
 
  /* the actual revision properties */
3524
 
  apr_hash_t *properties;
3525
 
 
3526
 
  /* their size when serialized to a single string
3527
 
   * (as found in PACKED_REVPROPS) */
3528
 
  apr_size_t serialized_size;
3529
 
 
3530
 
 
3531
 
  /* name of the pack file (without folder path) */
3532
 
  const char *filename;
3533
 
 
3534
 
  /* packed shard folder path */
3535
 
  const char *folder;
3536
 
 
3537
 
  /* sum of values in SIZES */
3538
 
  apr_size_t total_size;
3539
 
 
3540
 
  /* first revision in the pack (>= MANIFEST_START) */
3541
 
  svn_revnum_t start_revision;
3542
 
 
3543
 
  /* size of the revprops in PACKED_REVPROPS */
3544
 
  apr_array_header_t *sizes;
3545
 
 
3546
 
  /* offset of the revprops in PACKED_REVPROPS */
3547
 
  apr_array_header_t *offsets;
3548
 
 
3549
 
 
3550
 
  /* concatenation of the serialized representation of all revprops
3551
 
   * in the pack, i.e. the pack content without header and compression */
3552
 
  svn_stringbuf_t *packed_revprops;
3553
 
 
3554
 
  /* First revision covered by MANIFEST.
3555
 
   * Will equal the shard start revision or 1, for the 1st shard. */
3556
 
  svn_revnum_t manifest_start;
3557
 
 
3558
 
  /* content of the manifest.
3559
 
   * Maps long(rev - MANIFEST_START) to const char* pack file name */
3560
 
  apr_array_header_t *manifest;
3561
 
} packed_revprops_t;
3562
 
 
3563
 
/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
3564
 
 * Also, put them into the revprop cache, if activated, for future use.
3565
 
 * Three more parameters are being used to update the revprop cache: FS is
3566
 
 * our file system, the revprops belong to REVISION and the global revprop
3567
 
 * GENERATION is used as well.
3568
 
 *
3569
 
 * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
3570
 
 * for temporary allocations.
3571
 
 */
3572
 
static svn_error_t *
3573
 
parse_revprop(apr_hash_t **properties,
3574
 
              svn_fs_t *fs,
3575
 
              svn_revnum_t revision,
3576
 
              apr_int64_t generation,
3577
 
              svn_string_t *content,
3578
 
              apr_pool_t *pool,
3579
 
              apr_pool_t *scratch_pool)
3580
 
{
3581
 
  svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
3582
 
  *properties = apr_hash_make(pool);
3583
 
 
3584
 
  SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
3585
 
  if (has_revprop_cache(fs, pool))
3586
 
    {
3587
 
      fs_fs_data_t *ffd = fs->fsap_data;
3588
 
      pair_cache_key_t key = { 0 };
3589
 
 
3590
 
      key.revision = revision;
3591
 
      key.second = generation;
3592
 
      SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
3593
 
                             scratch_pool));
3594
 
    }
3595
 
 
3596
 
  return SVN_NO_ERROR;
3597
 
}
3598
 
 
3599
 
/* Read the non-packed revprops for revision REV in FS, put them into the
3600
 
 * revprop cache if activated and return them in *PROPERTIES.  GENERATION
3601
 
 * is the current revprop generation.
3602
 
 *
3603
 
 * If the data could not be read due to an otherwise recoverable error,
3604
 
 * leave *PROPERTIES unchanged. No error will be returned in that case.
3605
 
 *
3606
 
 * Allocations will be done in POOL.
3607
 
 */
3608
 
static svn_error_t *
3609
 
read_non_packed_revprop(apr_hash_t **properties,
3610
 
                        svn_fs_t *fs,
3611
 
                        svn_revnum_t rev,
3612
 
                        apr_int64_t generation,
3613
 
                        apr_pool_t *pool)
3614
 
{
3615
 
  svn_stringbuf_t *content = NULL;
3616
 
  apr_pool_t *iterpool = svn_pool_create(pool);
3617
 
  svn_boolean_t missing = FALSE;
3618
 
  int i;
3619
 
 
3620
 
  for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i)
3621
 
    {
3622
 
      svn_pool_clear(iterpool);
3623
 
      SVN_ERR(try_stringbuf_from_file(&content,
3624
 
                                      &missing,
3625
 
                                      path_revprops(fs, rev, iterpool),
3626
 
                                      i + 1 < RECOVERABLE_RETRY_COUNT,
3627
 
                                      iterpool));
3628
 
    }
3629
 
 
3630
 
  if (content)
3631
 
    SVN_ERR(parse_revprop(properties, fs, rev, generation,
3632
 
                          svn_stringbuf__morph_into_string(content),
3633
 
                          pool, iterpool));
3634
 
 
3635
 
  svn_pool_clear(iterpool);
3636
 
 
3637
 
  return SVN_NO_ERROR;
3638
 
}
3639
 
 
3640
 
/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
3641
 
 * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
3642
 
 */
3643
 
static svn_error_t *
3644
 
get_revprop_packname(svn_fs_t *fs,
3645
 
                     packed_revprops_t *revprops,
3646
 
                     apr_pool_t *pool,
3647
 
                     apr_pool_t *scratch_pool)
3648
 
{
3649
 
  fs_fs_data_t *ffd = fs->fsap_data;
3650
 
  svn_stringbuf_t *content = NULL;
3651
 
  const char *manifest_file_path;
3652
 
  int idx;
3653
 
 
3654
 
  /* read content of the manifest file */
3655
 
  revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool);
3656
 
  manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
3657
 
 
3658
 
  SVN_ERR(read_content(&content, manifest_file_path, pool));
3659
 
 
3660
 
  /* parse the manifest. Every line is a file name */
3661
 
  revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir,
3662
 
                                      sizeof(const char*));
3663
 
 
3664
 
  /* Read all lines.  Since the last line ends with a newline, we will
3665
 
     end up with a valid but empty string after the last entry. */
3666
 
  while (content->data && *content->data)
3667
 
    {
3668
 
      APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data;
3669
 
      content->data = strchr(content->data, '\n');
3670
 
      if (content->data)
3671
 
        {
3672
 
          *content->data = 0;
3673
 
          content->data++;
3674
 
        }
3675
 
    }
3676
 
 
3677
 
  /* Index for our revision. Rev 0 is excluded from the first shard. */
3678
 
  revprops->manifest_start = revprops->revision
3679
 
                           - (revprops->revision % ffd->max_files_per_dir);
3680
 
  if (revprops->manifest_start == 0)
3681
 
    ++revprops->manifest_start;
3682
 
  idx = (int)(revprops->revision - revprops->manifest_start);
3683
 
 
3684
 
  if (revprops->manifest->nelts <= idx)
3685
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3686
 
                             _("Packed revprop manifest for r%ld too "
3687
 
                               "small"), revprops->revision);
3688
 
 
3689
 
  /* Now get the file name */
3690
 
  revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
3691
 
 
3692
 
  return SVN_NO_ERROR;
3693
 
}
3694
 
 
3695
 
/* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
3696
 
 */
3697
 
static svn_boolean_t
3698
 
same_shard(svn_fs_t *fs,
3699
 
           svn_revnum_t r1,
3700
 
           svn_revnum_t r2)
3701
 
{
3702
 
  fs_fs_data_t *ffd = fs->fsap_data;
3703
 
  return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
3704
 
}
3705
 
 
3706
 
/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
3707
 
 * fill the START_REVISION, SIZES, OFFSETS members. Also, make
3708
 
 * PACKED_REVPROPS point to the first serialized revprop.
3709
 
 *
3710
 
 * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
3711
 
 * well as the SERIALIZED_SIZE member.  If revprop caching has been
3712
 
 * enabled, parse all revprops in the pack and cache them.
3713
 
 */
3714
 
static svn_error_t *
3715
 
parse_packed_revprops(svn_fs_t *fs,
3716
 
                      packed_revprops_t *revprops,
3717
 
                      apr_pool_t *pool,
3718
 
                      apr_pool_t *scratch_pool)
3719
 
{
3720
 
  svn_stream_t *stream;
3721
 
  apr_int64_t first_rev, count, i;
3722
 
  apr_off_t offset;
3723
 
  const char *header_end;
3724
 
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3725
 
 
3726
 
  /* decompress (even if the data is only "stored", there is still a
3727
 
   * length header to remove) */
3728
 
  svn_string_t *compressed
3729
 
      = svn_stringbuf__morph_into_string(revprops->packed_revprops);
3730
 
  svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
3731
 
  SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
3732
 
 
3733
 
  /* read first revision number and number of revisions in the pack */
3734
 
  stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
3735
 
  SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool));
3736
 
  SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool));
3737
 
 
3738
 
  /* Check revision range for validity. */
3739
 
  if (   !same_shard(fs, revprops->revision, first_rev)
3740
 
      || !same_shard(fs, revprops->revision, first_rev + count - 1)
3741
 
      || count < 1)
3742
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3743
 
                             _("Revprop pack for revision r%ld"
3744
 
                               " contains revprops for r%ld .. r%ld"),
3745
 
                             revprops->revision,
3746
 
                             (svn_revnum_t)first_rev,
3747
 
                             (svn_revnum_t)(first_rev + count -1));
3748
 
 
3749
 
  /* Since start & end are in the same shard, it is enough to just test
3750
 
   * the FIRST_REV for being actually packed.  That will also cover the
3751
 
   * special case of rev 0 never being packed. */
3752
 
  if (!is_packed_revprop(fs, first_rev))
3753
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3754
 
                             _("Revprop pack for revision r%ld"
3755
 
                               " starts at non-packed revisions r%ld"),
3756
 
                             revprops->revision, (svn_revnum_t)first_rev);
3757
 
 
3758
 
  /* make PACKED_REVPROPS point to the first char after the header.
3759
 
   * This is where the serialized revprops are. */
3760
 
  header_end = strstr(uncompressed->data, "\n\n");
3761
 
  if (header_end == NULL)
3762
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3763
 
                            _("Header end not found"));
3764
 
 
3765
 
  offset = header_end - uncompressed->data + 2;
3766
 
 
3767
 
  revprops->packed_revprops = svn_stringbuf_create_empty(pool);
3768
 
  revprops->packed_revprops->data = uncompressed->data + offset;
3769
 
  revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
3770
 
  revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
3771
 
 
3772
 
  /* STREAM still points to the first entry in the sizes list.
3773
 
   * Init / construct REVPROPS members. */
3774
 
  revprops->start_revision = (svn_revnum_t)first_rev;
3775
 
  revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
3776
 
  revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
3777
 
 
3778
 
  /* Now parse, revision by revision, the size and content of each
3779
 
   * revisions' revprops. */
3780
 
  for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
3781
 
    {
3782
 
      apr_int64_t size;
3783
 
      svn_string_t serialized;
3784
 
      apr_hash_t *properties;
3785
 
      svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
3786
 
 
3787
 
      /* read & check the serialized size */
3788
 
      SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool));
3789
 
      if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
3790
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3791
 
                        _("Packed revprop size exceeds pack file size"));
3792
 
 
3793
 
      /* Parse this revprops list, if necessary */
3794
 
      serialized.data = revprops->packed_revprops->data + offset;
3795
 
      serialized.len = (apr_size_t)size;
3796
 
 
3797
 
      if (revision == revprops->revision)
3798
 
        {
3799
 
          SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
3800
 
                                revprops->generation, &serialized,
3801
 
                                pool, iterpool));
3802
 
          revprops->serialized_size = serialized.len;
3803
 
        }
3804
 
      else
3805
 
        {
3806
 
          /* If revprop caching is enabled, parse any revprops.
3807
 
           * They will get cached as a side-effect of this. */
3808
 
          if (has_revprop_cache(fs, pool))
3809
 
            SVN_ERR(parse_revprop(&properties, fs, revision,
3810
 
                                  revprops->generation, &serialized,
3811
 
                                  iterpool, iterpool));
3812
 
        }
3813
 
 
3814
 
      /* fill REVPROPS data structures */
3815
 
      APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
3816
 
      APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
3817
 
      revprops->total_size += serialized.len;
3818
 
 
3819
 
      offset += serialized.len;
3820
 
 
3821
 
      svn_pool_clear(iterpool);
3822
 
    }
3823
 
 
3824
 
  return SVN_NO_ERROR;
3825
 
}
3826
 
 
3827
 
/* In filesystem FS, read the packed revprops for revision REV into
3828
 
 * *REVPROPS.  Use GENERATION to populate the revprop cache, if enabled.
3829
 
 * Allocate data in POOL.
3830
 
 */
3831
 
static svn_error_t *
3832
 
read_pack_revprop(packed_revprops_t **revprops,
3833
 
                  svn_fs_t *fs,
3834
 
                  svn_revnum_t rev,
3835
 
                  apr_int64_t generation,
3836
 
                  apr_pool_t *pool)
3837
 
{
3838
 
  apr_pool_t *iterpool = svn_pool_create(pool);
3839
 
  svn_boolean_t missing = FALSE;
3840
 
  svn_error_t *err;
3841
 
  packed_revprops_t *result;
3842
 
  int i;
3843
 
 
3844
 
  /* someone insisted that REV is packed. Double-check if necessary */
3845
 
  if (!is_packed_revprop(fs, rev))
3846
 
     SVN_ERR(update_min_unpacked_rev(fs, iterpool));
3847
 
 
3848
 
  if (!is_packed_revprop(fs, rev))
3849
 
    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3850
 
                              _("No such packed revision %ld"), rev);
3851
 
 
3852
 
  /* initialize the result data structure */
3853
 
  result = apr_pcalloc(pool, sizeof(*result));
3854
 
  result->revision = rev;
3855
 
  result->generation = generation;
3856
 
 
3857
 
  /* try to read the packed revprops. This may require retries if we have
3858
 
   * concurrent writers. */
3859
 
  for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i)
3860
 
    {
3861
 
      const char *file_path;
3862
 
 
3863
 
      /* there might have been concurrent writes.
3864
 
       * Re-read the manifest and the pack file.
3865
 
       */
3866
 
      SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
3867
 
      file_path  = svn_dirent_join(result->folder,
3868
 
                                   result->filename,
3869
 
                                   iterpool);
3870
 
      SVN_ERR(try_stringbuf_from_file(&result->packed_revprops,
3871
 
                                      &missing,
3872
 
                                      file_path,
3873
 
                                      i + 1 < RECOVERABLE_RETRY_COUNT,
3874
 
                                      pool));
3875
 
 
3876
 
      /* If we could not find the file, there was a write.
3877
 
       * So, we should refresh our revprop generation info as well such
3878
 
       * that others may find data we will put into the cache.  They would
3879
 
       * consider it outdated, otherwise.
3880
 
       */
3881
 
      if (missing && has_revprop_cache(fs, pool))
3882
 
        SVN_ERR(read_revprop_generation(&result->generation, fs, pool));
3883
 
 
3884
 
      svn_pool_clear(iterpool);
3885
 
    }
3886
 
 
3887
 
  /* the file content should be available now */
3888
 
  if (!result->packed_revprops)
3889
 
    return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
3890
 
                  _("Failed to read revprop pack file for r%ld"), rev);
3891
 
 
3892
 
  /* parse it. RESULT will be complete afterwards. */
3893
 
  err = parse_packed_revprops(fs, result, pool, iterpool);
3894
 
  svn_pool_destroy(iterpool);
3895
 
  if (err)
3896
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
3897
 
                  _("Revprop pack file for r%ld is corrupt"), rev);
3898
 
 
3899
 
  *revprops = result;
3900
 
 
3901
 
  return SVN_NO_ERROR;
3902
 
}
3903
 
 
3904
 
/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
3905
 
 *
3906
 
 * Allocations will be done in POOL.
3907
 
 */
3908
 
static svn_error_t *
3909
 
get_revision_proplist(apr_hash_t **proplist_p,
3910
 
                      svn_fs_t *fs,
3911
 
                      svn_revnum_t rev,
3912
 
                      apr_pool_t *pool)
3913
 
{
3914
 
  fs_fs_data_t *ffd = fs->fsap_data;
3915
 
  apr_int64_t generation = 0;
3916
 
 
3917
 
  /* not found, yet */
3918
 
  *proplist_p = NULL;
3919
 
 
3920
 
  /* should they be available at all? */
3921
 
  SVN_ERR(ensure_revision_exists(fs, rev, pool));
3922
 
 
3923
 
  /* Try cache lookup first. */
3924
 
  if (has_revprop_cache(fs, pool))
3925
 
    {
3926
 
      svn_boolean_t is_cached;
3927
 
      pair_cache_key_t key = { 0 };
3928
 
 
3929
 
      SVN_ERR(read_revprop_generation(&generation, fs, pool));
3930
 
 
3931
 
      key.revision = rev;
3932
 
      key.second = generation;
3933
 
      SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
3934
 
                             ffd->revprop_cache, &key, pool));
3935
 
      if (is_cached)
3936
 
        return SVN_NO_ERROR;
3937
 
    }
3938
 
 
3939
 
  /* if REV had not been packed when we began, try reading it from the
3940
 
   * non-packed shard.  If that fails, we will fall through to packed
3941
 
   * shard reads. */
3942
 
  if (!is_packed_revprop(fs, rev))
3943
 
    {
3944
 
      svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
3945
 
                                                 generation, pool);
3946
 
      if (err)
3947
 
        {
3948
 
          if (!APR_STATUS_IS_ENOENT(err->apr_err)
3949
 
              || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
3950
 
            return svn_error_trace(err);
3951
 
 
3952
 
          svn_error_clear(err);
3953
 
          *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
3954
 
        }
3955
 
    }
3956
 
 
3957
 
  /* if revprop packing is available and we have not read the revprops, yet,
3958
 
   * try reading them from a packed shard.  If that fails, REV is most
3959
 
   * likely invalid (or its revprops highly contested). */
3960
 
  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
3961
 
    {
3962
 
      packed_revprops_t *packed_revprops;
3963
 
      SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool));
3964
 
      *proplist_p = packed_revprops->properties;
3965
 
    }
3966
 
 
3967
 
  /* The revprops should have been there. Did we get them? */
3968
 
  if (!*proplist_p)
3969
 
    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3970
 
                             _("Could not read revprops for revision %ld"),
3971
 
                             rev);
3972
 
 
3973
 
  return SVN_NO_ERROR;
3974
 
}
3975
 
 
3976
 
/* Serialize the revision property list PROPLIST of revision REV in
3977
 
 * filesystem FS to a non-packed file.  Return the name of that temporary
3978
 
 * file in *TMP_PATH and the file path that it must be moved to in
3979
 
 * *FINAL_PATH.
3980
 
 *
3981
 
 * Use POOL for allocations.
3982
 
 */
3983
 
static svn_error_t *
3984
 
write_non_packed_revprop(const char **final_path,
3985
 
                         const char **tmp_path,
3986
 
                         svn_fs_t *fs,
3987
 
                         svn_revnum_t rev,
3988
 
                         apr_hash_t *proplist,
3989
 
                         apr_pool_t *pool)
3990
 
{
3991
 
  svn_stream_t *stream;
3992
 
  *final_path = path_revprops(fs, rev, pool);
3993
 
 
3994
 
  /* ### do we have a directory sitting around already? we really shouldn't
3995
 
     ### have to get the dirname here. */
3996
 
  SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
3997
 
                                 svn_dirent_dirname(*final_path, pool),
3998
 
                                 svn_io_file_del_none, pool, pool));
3999
 
  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4000
 
  SVN_ERR(svn_stream_close(stream));
4001
 
 
4002
 
  return SVN_NO_ERROR;
4003
 
}
4004
 
 
4005
 
/* After writing the new revprop file(s), call this function to move the
4006
 
 * file at TMP_PATH to FINAL_PATH and give it the permissions from
4007
 
 * PERMS_REFERENCE.
4008
 
 *
4009
 
 * If indicated in BUMP_GENERATION, increase FS' revprop generation.
4010
 
 * Finally, delete all the temporary files given in FILES_TO_DELETE.
4011
 
 * The latter may be NULL.
4012
 
 *
4013
 
 * Use POOL for temporary allocations.
4014
 
 */
4015
 
static svn_error_t *
4016
 
switch_to_new_revprop(svn_fs_t *fs,
4017
 
                      const char *final_path,
4018
 
                      const char *tmp_path,
4019
 
                      const char *perms_reference,
4020
 
                      apr_array_header_t *files_to_delete,
4021
 
                      svn_boolean_t bump_generation,
4022
 
                      apr_pool_t *pool)
4023
 
{
4024
 
  /* Now, we may actually be replacing revprops. Make sure that all other
4025
 
     threads and processes will know about this. */
4026
 
  if (bump_generation)
4027
 
    SVN_ERR(begin_revprop_change(fs, pool));
4028
 
 
4029
 
  SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
4030
 
 
4031
 
  /* Indicate that the update (if relevant) has been completed. */
4032
 
  if (bump_generation)
4033
 
    SVN_ERR(end_revprop_change(fs, pool));
4034
 
 
4035
 
  /* Clean up temporary files, if necessary. */
4036
 
  if (files_to_delete)
4037
 
    {
4038
 
      apr_pool_t *iterpool = svn_pool_create(pool);
4039
 
      int i;
4040
 
 
4041
 
      for (i = 0; i < files_to_delete->nelts; ++i)
4042
 
        {
4043
 
          const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
4044
 
          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
4045
 
          svn_pool_clear(iterpool);
4046
 
        }
4047
 
 
4048
 
      svn_pool_destroy(iterpool);
4049
 
    }
4050
 
  return SVN_NO_ERROR;
4051
 
}
4052
 
 
4053
 
/* Write a pack file header to STREAM that starts at revision START_REVISION
4054
 
 * and contains the indexes [START,END) of SIZES.
4055
 
 */
4056
 
static svn_error_t *
4057
 
serialize_revprops_header(svn_stream_t *stream,
4058
 
                          svn_revnum_t start_revision,
4059
 
                          apr_array_header_t *sizes,
4060
 
                          int start,
4061
 
                          int end,
4062
 
                          apr_pool_t *pool)
4063
 
{
4064
 
  apr_pool_t *iterpool = svn_pool_create(pool);
4065
 
  int i;
4066
 
 
4067
 
  SVN_ERR_ASSERT(start < end);
4068
 
 
4069
 
  /* start revision and entry count */
4070
 
  SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
4071
 
  SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
4072
 
 
4073
 
  /* the sizes array */
4074
 
  for (i = start; i < end; ++i)
4075
 
    {
4076
 
      apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
4077
 
      SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
4078
 
                                size));
4079
 
    }
4080
 
 
4081
 
  /* the double newline char indicates the end of the header */
4082
 
  SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
4083
 
 
4084
 
  svn_pool_clear(iterpool);
4085
 
  return SVN_NO_ERROR;
4086
 
}
4087
 
 
4088
 
/* Writes the a pack file to FILE_STREAM.  It copies the serialized data
4089
 
 * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
4090
 
 *
4091
 
 * The data for the latter is taken from NEW_SERIALIZED.  Note, that
4092
 
 * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
4093
 
 * taken in that case but only a subset of the old data will be copied.
4094
 
 *
4095
 
 * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
4096
 
 * POOL is used for temporary allocations.
4097
 
 */
4098
 
static svn_error_t *
4099
 
repack_revprops(svn_fs_t *fs,
4100
 
                packed_revprops_t *revprops,
4101
 
                int start,
4102
 
                int end,
4103
 
                int changed_index,
4104
 
                svn_stringbuf_t *new_serialized,
4105
 
                apr_off_t new_total_size,
4106
 
                svn_stream_t *file_stream,
4107
 
                apr_pool_t *pool)
4108
 
{
4109
 
  fs_fs_data_t *ffd = fs->fsap_data;
4110
 
  svn_stream_t *stream;
4111
 
  int i;
4112
 
 
4113
 
  /* create data empty buffers and the stream object */
4114
 
  svn_stringbuf_t *uncompressed
4115
 
    = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
4116
 
  svn_stringbuf_t *compressed
4117
 
    = svn_stringbuf_create_empty(pool);
4118
 
  stream = svn_stream_from_stringbuf(uncompressed, pool);
4119
 
 
4120
 
  /* write the header*/
4121
 
  SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
4122
 
                                    revprops->sizes, start, end, pool));
4123
 
 
4124
 
  /* append the serialized revprops */
4125
 
  for (i = start; i < end; ++i)
4126
 
    if (i == changed_index)
4127
 
      {
4128
 
        SVN_ERR(svn_stream_write(stream,
4129
 
                                 new_serialized->data,
4130
 
                                 &new_serialized->len));
4131
 
      }
4132
 
    else
4133
 
      {
4134
 
        apr_size_t size
4135
 
            = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
4136
 
        apr_size_t offset
4137
 
            = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
4138
 
 
4139
 
        SVN_ERR(svn_stream_write(stream,
4140
 
                                 revprops->packed_revprops->data + offset,
4141
 
                                 &size));
4142
 
      }
4143
 
 
4144
 
  /* flush the stream buffer (if any) to our underlying data buffer */
4145
 
  SVN_ERR(svn_stream_close(stream));
4146
 
 
4147
 
  /* compress / store the data */
4148
 
  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
4149
 
                        compressed,
4150
 
                        ffd->compress_packed_revprops
4151
 
                          ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
4152
 
                          : SVN_DELTA_COMPRESSION_LEVEL_NONE));
4153
 
 
4154
 
  /* finally, write the content to the target stream and close it */
4155
 
  SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
4156
 
  SVN_ERR(svn_stream_close(file_stream));
4157
 
 
4158
 
  return SVN_NO_ERROR;
4159
 
}
4160
 
 
4161
 
/* Allocate a new pack file name for revisions
4162
 
 *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
4163
 
 * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
4164
 
 * auto-create that array if necessary.  Return an open file stream to
4165
 
 * the new file in *STREAM allocated in POOL.
4166
 
 */
4167
 
static svn_error_t *
4168
 
repack_stream_open(svn_stream_t **stream,
4169
 
                   svn_fs_t *fs,
4170
 
                   packed_revprops_t *revprops,
4171
 
                   int start,
4172
 
                   int end,
4173
 
                   apr_array_header_t **files_to_delete,
4174
 
                   apr_pool_t *pool)
4175
 
{
4176
 
  apr_int64_t tag;
4177
 
  const char *tag_string;
4178
 
  svn_string_t *new_filename;
4179
 
  int i;
4180
 
  apr_file_t *file;
4181
 
  int manifest_offset
4182
 
    = (int)(revprops->start_revision - revprops->manifest_start);
4183
 
 
4184
 
  /* get the old (= current) file name and enlist it for later deletion */
4185
 
  const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
4186
 
                                           start + manifest_offset,
4187
 
                                           const char*);
4188
 
 
4189
 
  if (*files_to_delete == NULL)
4190
 
    *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
4191
 
 
4192
 
  APR_ARRAY_PUSH(*files_to_delete, const char*)
4193
 
    = svn_dirent_join(revprops->folder, old_filename, pool);
4194
 
 
4195
 
  /* increase the tag part, i.e. the counter after the dot */
4196
 
  tag_string = strchr(old_filename, '.');
4197
 
  if (tag_string == NULL)
4198
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4199
 
                             _("Packed file '%s' misses a tag"),
4200
 
                             old_filename);
4201
 
 
4202
 
  SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
4203
 
  new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
4204
 
                                    revprops->start_revision + start,
4205
 
                                    ++tag);
4206
 
 
4207
 
  /* update the manifest to point to the new file */
4208
 
  for (i = start; i < end; ++i)
4209
 
    APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
4210
 
      = new_filename->data;
4211
 
 
4212
 
  /* create a file stream for the new file */
4213
 
  SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
4214
 
                                                  new_filename->data,
4215
 
                                                  pool),
4216
 
                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
4217
 
  *stream = svn_stream_from_aprfile2(file, FALSE, pool);
4218
 
 
4219
 
  return SVN_NO_ERROR;
4220
 
}
4221
 
 
4222
 
/* For revision REV in filesystem FS, set the revision properties to
4223
 
 * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
4224
 
 * to *FINAL_PATH to make the change visible.  Files to be deleted will
4225
 
 * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
4226
 
 * Use POOL for allocations.
4227
 
 */
4228
 
static svn_error_t *
4229
 
write_packed_revprop(const char **final_path,
4230
 
                     const char **tmp_path,
4231
 
                     apr_array_header_t **files_to_delete,
4232
 
                     svn_fs_t *fs,
4233
 
                     svn_revnum_t rev,
4234
 
                     apr_hash_t *proplist,
4235
 
                     apr_pool_t *pool)
4236
 
{
4237
 
  fs_fs_data_t *ffd = fs->fsap_data;
4238
 
  packed_revprops_t *revprops;
4239
 
  apr_int64_t generation = 0;
4240
 
  svn_stream_t *stream;
4241
 
  svn_stringbuf_t *serialized;
4242
 
  apr_off_t new_total_size;
4243
 
  int changed_index;
4244
 
 
4245
 
  /* read the current revprop generation. This value will not change
4246
 
   * while we hold the global write lock to this FS. */
4247
 
  if (has_revprop_cache(fs, pool))
4248
 
    SVN_ERR(read_revprop_generation(&generation, fs, pool));
4249
 
 
4250
 
  /* read contents of the current pack file */
4251
 
  SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
4252
 
 
4253
 
  /* serialize the new revprops */
4254
 
  serialized = svn_stringbuf_create_empty(pool);
4255
 
  stream = svn_stream_from_stringbuf(serialized, pool);
4256
 
  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4257
 
  SVN_ERR(svn_stream_close(stream));
4258
 
 
4259
 
  /* calculate the size of the new data */
4260
 
  changed_index = (int)(rev - revprops->start_revision);
4261
 
  new_total_size = revprops->total_size - revprops->serialized_size
4262
 
                 + serialized->len
4263
 
                 + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
4264
 
 
4265
 
  APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
4266
 
 
4267
 
  /* can we put the new data into the same pack as the before? */
4268
 
  if (   new_total_size < ffd->revprop_pack_size
4269
 
      || revprops->sizes->nelts == 1)
4270
 
    {
4271
 
      /* simply replace the old pack file with new content as we do it
4272
 
       * in the non-packed case */
4273
 
 
4274
 
      *final_path = svn_dirent_join(revprops->folder, revprops->filename,
4275
 
                                    pool);
4276
 
      SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4277
 
                                     svn_io_file_del_none, pool, pool));
4278
 
      SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
4279
 
                              changed_index, serialized, new_total_size,
4280
 
                              stream, pool));
4281
 
    }
4282
 
  else
4283
 
    {
4284
 
      /* split the pack file into two of roughly equal size */
4285
 
      int right_count, left_count, i;
4286
 
 
4287
 
      int left = 0;
4288
 
      int right = revprops->sizes->nelts - 1;
4289
 
      apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
4290
 
      apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
4291
 
 
4292
 
      /* let left and right side grow such that their size difference
4293
 
       * is minimal after each step. */
4294
 
      while (left <= right)
4295
 
        if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4296
 
            < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
4297
 
          {
4298
 
            left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4299
 
                      + SVN_INT64_BUFFER_SIZE;
4300
 
            ++left;
4301
 
          }
4302
 
        else
4303
 
          {
4304
 
            right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
4305
 
                        + SVN_INT64_BUFFER_SIZE;
4306
 
            --right;
4307
 
          }
4308
 
 
4309
 
       /* since the items need much less than SVN_INT64_BUFFER_SIZE
4310
 
        * bytes to represent their length, the split may not be optimal */
4311
 
      left_count = left;
4312
 
      right_count = revprops->sizes->nelts - left;
4313
 
 
4314
 
      /* if new_size is large, one side may exceed the pack size limit.
4315
 
       * In that case, split before and after the modified revprop.*/
4316
 
      if (   left_size > ffd->revprop_pack_size
4317
 
          || right_size > ffd->revprop_pack_size)
4318
 
        {
4319
 
          left_count = changed_index;
4320
 
          right_count = revprops->sizes->nelts - left_count - 1;
4321
 
        }
4322
 
 
4323
 
      /* write the new, split files */
4324
 
      if (left_count)
4325
 
        {
4326
 
          SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
4327
 
                                     left_count, files_to_delete, pool));
4328
 
          SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
4329
 
                                  changed_index, serialized, new_total_size,
4330
 
                                  stream, pool));
4331
 
        }
4332
 
 
4333
 
      if (left_count + right_count < revprops->sizes->nelts)
4334
 
        {
4335
 
          SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
4336
 
                                     changed_index + 1, files_to_delete,
4337
 
                                     pool));
4338
 
          SVN_ERR(repack_revprops(fs, revprops, changed_index,
4339
 
                                  changed_index + 1,
4340
 
                                  changed_index, serialized, new_total_size,
4341
 
                                  stream, pool));
4342
 
        }
4343
 
 
4344
 
      if (right_count)
4345
 
        {
4346
 
          SVN_ERR(repack_stream_open(&stream, fs, revprops,
4347
 
                                     revprops->sizes->nelts - right_count,
4348
 
                                     revprops->sizes->nelts,
4349
 
                                     files_to_delete, pool));
4350
 
          SVN_ERR(repack_revprops(fs, revprops,
4351
 
                                  revprops->sizes->nelts - right_count,
4352
 
                                  revprops->sizes->nelts, changed_index,
4353
 
                                  serialized, new_total_size, stream,
4354
 
                                  pool));
4355
 
        }
4356
 
 
4357
 
      /* write the new manifest */
4358
 
      *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
4359
 
      SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4360
 
                                     svn_io_file_del_none, pool, pool));
4361
 
 
4362
 
      for (i = 0; i < revprops->manifest->nelts; ++i)
4363
 
        {
4364
 
          const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
4365
 
                                               const char*);
4366
 
          SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
4367
 
        }
4368
 
 
4369
 
      SVN_ERR(svn_stream_close(stream));
4370
 
    }
4371
 
 
4372
 
  return SVN_NO_ERROR;
4373
 
}
4374
 
 
4375
 
/* Set the revision property list of revision REV in filesystem FS to
4376
 
   PROPLIST.  Use POOL for temporary allocations. */
4377
 
static svn_error_t *
4378
 
set_revision_proplist(svn_fs_t *fs,
4379
 
                      svn_revnum_t rev,
4380
 
                      apr_hash_t *proplist,
4381
 
                      apr_pool_t *pool)
4382
 
{
4383
 
  svn_boolean_t is_packed;
4384
 
  svn_boolean_t bump_generation = FALSE;
4385
 
  const char *final_path;
4386
 
  const char *tmp_path;
4387
 
  const char *perms_reference;
4388
 
  apr_array_header_t *files_to_delete = NULL;
4389
 
 
4390
 
  SVN_ERR(ensure_revision_exists(fs, rev, pool));
4391
 
 
4392
 
  /* this info will not change while we hold the global FS write lock */
4393
 
  is_packed = is_packed_revprop(fs, rev);
4394
 
 
4395
 
  /* Test whether revprops already exist for this revision.
4396
 
   * Only then will we need to bump the revprop generation. */
4397
 
  if (has_revprop_cache(fs, pool))
4398
 
    {
4399
 
      if (is_packed)
4400
 
        {
4401
 
          bump_generation = TRUE;
4402
 
        }
4403
 
      else
4404
 
        {
4405
 
          svn_node_kind_t kind;
4406
 
          SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
4407
 
                                    pool));
4408
 
          bump_generation = kind != svn_node_none;
4409
 
        }
4410
 
    }
4411
 
 
4412
 
  /* Serialize the new revprop data */
4413
 
  if (is_packed)
4414
 
    SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
4415
 
                                 fs, rev, proplist, pool));
4416
 
  else
4417
 
    SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
4418
 
                                     fs, rev, proplist, pool));
4419
 
 
4420
 
  /* We use the rev file of this revision as the perms reference,
4421
 
   * because when setting revprops for the first time, the revprop
4422
 
   * file won't exist and therefore can't serve as its own reference.
4423
 
   * (Whereas the rev file should already exist at this point.)
4424
 
   */
4425
 
  SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
4426
 
 
4427
 
  /* Now, switch to the new revprop data. */
4428
 
  SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
4429
 
                                files_to_delete, bump_generation, pool));
4430
 
 
4431
 
  return SVN_NO_ERROR;
4432
 
}
4433
 
 
4434
 
svn_error_t *
4435
 
svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
4436
 
                             svn_fs_t *fs,
4437
 
                             svn_revnum_t rev,
4438
 
                             apr_pool_t *pool)
4439
 
{
4440
 
  SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
4441
 
 
4442
 
  return SVN_NO_ERROR;
4443
 
}
4444
 
 
4445
 
/* Represents where in the current svndiff data block each
4446
 
   representation is. */
4447
 
struct rep_state
4448
 
{
4449
 
  apr_file_t *file;
4450
 
                    /* The txdelta window cache to use or NULL. */
4451
 
  svn_cache__t *window_cache;
4452
 
                    /* Caches un-deltified windows. May be NULL. */
4453
 
  svn_cache__t *combined_cache;
4454
 
  apr_off_t start;  /* The starting offset for the raw
4455
 
                       svndiff/plaintext data minus header. */
4456
 
  apr_off_t off;    /* The current offset into the file. */
4457
 
  apr_off_t end;    /* The end offset of the raw data. */
4458
 
  int ver;          /* If a delta, what svndiff version? */
4459
 
  int chunk_index;
4460
 
};
4461
 
 
4462
 
/* See create_rep_state, which wraps this and adds another error. */
4463
 
static svn_error_t *
4464
 
create_rep_state_body(struct rep_state **rep_state,
4465
 
                      struct rep_args **rep_args,
4466
 
                      apr_file_t **file_hint,
4467
 
                      svn_revnum_t *rev_hint,
4468
 
                      representation_t *rep,
4469
 
                      svn_fs_t *fs,
4470
 
                      apr_pool_t *pool)
4471
 
{
4472
 
  fs_fs_data_t *ffd = fs->fsap_data;
4473
 
  struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
4474
 
  struct rep_args *ra;
4475
 
  unsigned char buf[4];
4476
 
 
4477
 
  /* If the hint is
4478
 
   * - given,
4479
 
   * - refers to a valid revision,
4480
 
   * - refers to a packed revision,
4481
 
   * - as does the rep we want to read, and
4482
 
   * - refers to the same pack file as the rep
4483
 
   * ...
4484
 
   */
4485
 
  if (   file_hint && rev_hint && *file_hint
4486
 
      && SVN_IS_VALID_REVNUM(*rev_hint)
4487
 
      && *rev_hint < ffd->min_unpacked_rev
4488
 
      && rep->revision < ffd->min_unpacked_rev
4489
 
      && (   (*rev_hint / ffd->max_files_per_dir)
4490
 
          == (rep->revision / ffd->max_files_per_dir)))
4491
 
    {
4492
 
      /* ... we can re-use the same, already open file object
4493
 
       */
4494
 
      apr_off_t offset;
4495
 
      SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
4496
 
 
4497
 
      offset += rep->offset;
4498
 
      SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
4499
 
 
4500
 
      rs->file = *file_hint;
4501
 
    }
4502
 
  else
4503
 
    {
4504
 
      /* otherwise, create a new file object
4505
 
       */
4506
 
      SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
4507
 
    }
4508
 
 
4509
 
  /* remember the current file, if suggested by the caller */
4510
 
  if (file_hint)
4511
 
    *file_hint = rs->file;
4512
 
  if (rev_hint)
4513
 
    *rev_hint = rep->revision;
4514
 
 
4515
 
  /* continue constructing RS and RA */
4516
 
  rs->window_cache = ffd->txdelta_window_cache;
4517
 
  rs->combined_cache = ffd->combined_window_cache;
4518
 
 
4519
 
  SVN_ERR(read_rep_line(&ra, rs->file, pool));
4520
 
  SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
4521
 
  rs->off = rs->start;
4522
 
  rs->end = rs->start + rep->size;
4523
 
  *rep_state = rs;
4524
 
  *rep_args = ra;
4525
 
 
4526
 
  if (!ra->is_delta)
4527
 
    /* This is a plaintext, so just return the current rep_state. */
4528
 
    return SVN_NO_ERROR;
4529
 
 
4530
 
  /* We are dealing with a delta, find out what version. */
4531
 
  SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
4532
 
                                 NULL, NULL, pool));
4533
 
  /* ### Layering violation */
4534
 
  if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
4535
 
    return svn_error_create
4536
 
      (SVN_ERR_FS_CORRUPT, NULL,
4537
 
       _("Malformed svndiff data in representation"));
4538
 
  rs->ver = buf[3];
4539
 
  rs->chunk_index = 0;
4540
 
  rs->off += 4;
4541
 
 
4542
 
  return SVN_NO_ERROR;
4543
 
}
4544
 
 
4545
 
/* Read the rep args for REP in filesystem FS and create a rep_state
4546
 
   for reading the representation.  Return the rep_state in *REP_STATE
4547
 
   and the rep args in *REP_ARGS, both allocated in POOL.
4548
 
 
4549
 
   When reading multiple reps, i.e. a skip delta chain, you may provide
4550
 
   non-NULL FILE_HINT and REV_HINT.  (If FILE_HINT is not NULL, in the first
4551
 
   call it should be a pointer to NULL.)  The function will use these variables
4552
 
   to store the previous call results and tries to re-use them.  This may
4553
 
   result in significant savings in I/O for packed files.
4554
 
 */
4555
 
static svn_error_t *
4556
 
create_rep_state(struct rep_state **rep_state,
4557
 
                 struct rep_args **rep_args,
4558
 
                 apr_file_t **file_hint,
4559
 
                 svn_revnum_t *rev_hint,
4560
 
                 representation_t *rep,
4561
 
                 svn_fs_t *fs,
4562
 
                 apr_pool_t *pool)
4563
 
{
4564
 
  svn_error_t *err = create_rep_state_body(rep_state, rep_args,
4565
 
                                           file_hint, rev_hint,
4566
 
                                           rep, fs, pool);
4567
 
  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
4568
 
    {
4569
 
      fs_fs_data_t *ffd = fs->fsap_data;
4570
 
 
4571
 
      /* ### This always returns "-1" for transaction reps, because
4572
 
         ### this particular bit of code doesn't know if the rep is
4573
 
         ### stored in the protorev or in the mutable area (for props
4574
 
         ### or dir contents).  It is pretty rare for FSFS to *read*
4575
 
         ### from the protorev file, though, so this is probably OK.
4576
 
         ### And anyone going to debug corruption errors is probably
4577
 
         ### going to jump straight to this comment anyway! */
4578
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
4579
 
                               "Corrupt representation '%s'",
4580
 
                               rep
4581
 
                               ? representation_string(rep, ffd->format, TRUE,
4582
 
                                                       TRUE, pool)
4583
 
                               : "(null)");
4584
 
    }
4585
 
  /* ### Call representation_string() ? */
4586
 
  return svn_error_trace(err);
4587
 
}
4588
 
 
4589
 
struct rep_read_baton
4590
 
{
4591
 
  /* The FS from which we're reading. */
4592
 
  svn_fs_t *fs;
4593
 
 
4594
 
  /* If not NULL, this is the base for the first delta window in rs_list */
4595
 
  svn_stringbuf_t *base_window;
4596
 
 
4597
 
  /* The state of all prior delta representations. */
4598
 
  apr_array_header_t *rs_list;
4599
 
 
4600
 
  /* The plaintext state, if there is a plaintext. */
4601
 
  struct rep_state *src_state;
4602
 
 
4603
 
  /* The index of the current delta chunk, if we are reading a delta. */
4604
 
  int chunk_index;
4605
 
 
4606
 
  /* The buffer where we store undeltified data. */
4607
 
  char *buf;
4608
 
  apr_size_t buf_pos;
4609
 
  apr_size_t buf_len;
4610
 
 
4611
 
  /* A checksum context for summing the data read in order to verify it.
4612
 
     Note: we don't need to use the sha1 checksum because we're only doing
4613
 
     data verification, for which md5 is perfectly safe.  */
4614
 
  svn_checksum_ctx_t *md5_checksum_ctx;
4615
 
 
4616
 
  svn_boolean_t checksum_finalized;
4617
 
 
4618
 
  /* The stored checksum of the representation we are reading, its
4619
 
     length, and the amount we've read so far.  Some of this
4620
 
     information is redundant with rs_list and src_state, but it's
4621
 
     convenient for the checksumming code to have it here. */
4622
 
  svn_checksum_t *md5_checksum;
4623
 
 
4624
 
  svn_filesize_t len;
4625
 
  svn_filesize_t off;
4626
 
 
4627
 
  /* The key for the fulltext cache for this rep, if there is a
4628
 
     fulltext cache. */
4629
 
  pair_cache_key_t fulltext_cache_key;
4630
 
  /* The text we've been reading, if we're going to cache it. */
4631
 
  svn_stringbuf_t *current_fulltext;
4632
 
 
4633
 
  /* Used for temporary allocations during the read. */
4634
 
  apr_pool_t *pool;
4635
 
 
4636
 
  /* Pool used to store file handles and other data that is persistant
4637
 
     for the entire stream read. */
4638
 
  apr_pool_t *filehandle_pool;
4639
 
};
4640
 
 
4641
 
/* Combine the name of the rev file in RS with the given OFFSET to form
4642
 
 * a cache lookup key.  Allocations will be made from POOL.  May return
4643
 
 * NULL if the key cannot be constructed. */
4644
 
static const char*
4645
 
get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
4646
 
{
4647
 
  const char *name;
4648
 
  const char *last_part;
4649
 
  const char *name_last;
4650
 
 
4651
 
  /* the rev file name containing the txdelta window.
4652
 
   * If this fails we are in serious trouble anyways.
4653
 
   * And if nobody else detects the problems, the file content checksum
4654
 
   * comparison _will_ find them.
4655
 
   */
4656
 
  if (apr_file_name_get(&name, rs->file))
4657
 
    return NULL;
4658
 
 
4659
 
  /* Handle packed files as well by scanning backwards until we find the
4660
 
   * revision or pack number. */
4661
 
  name_last = name + strlen(name) - 1;
4662
 
  while (! svn_ctype_isdigit(*name_last))
4663
 
    --name_last;
4664
 
 
4665
 
  last_part = name_last;
4666
 
  while (svn_ctype_isdigit(*last_part))
4667
 
    --last_part;
4668
 
 
4669
 
  /* We must differentiate between packed files (as of today, the number
4670
 
   * is being followed by a dot) and non-packed files (followed by \0).
4671
 
   * Otherwise, there might be overlaps in the numbering range if the
4672
 
   * repo gets packed after caching the txdeltas of non-packed revs.
4673
 
   * => add the first non-digit char to the packed number. */
4674
 
  if (name_last[1] != '\0')
4675
 
    ++name_last;
4676
 
 
4677
 
  /* copy one char MORE than the actual number to mark packed files,
4678
 
   * i.e. packed revision file content uses different key space then
4679
 
   * non-packed ones: keys for packed rev file content ends with a dot
4680
 
   * for non-packed rev files they end with a digit. */
4681
 
  name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
4682
 
  return svn_fs_fs__combine_number_and_string(offset, name, pool);
4683
 
}
4684
 
 
4685
 
/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4686
 
 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4687
 
 * cache has been given. If a cache is available IS_CACHED will inform
4688
 
 * the caller about the success of the lookup. Allocations (of the window
4689
 
 * in particualar) will be made from POOL.
4690
 
 *
4691
 
 * If the information could be found, put RS and the position within the
4692
 
 * rev file into the same state as if the data had just been read from it.
4693
 
 */
4694
 
static svn_error_t *
4695
 
get_cached_window(svn_txdelta_window_t **window_p,
4696
 
                  struct rep_state *rs,
4697
 
                  svn_boolean_t *is_cached,
4698
 
                  apr_pool_t *pool)
4699
 
{
4700
 
  if (! rs->window_cache)
4701
 
    {
4702
 
      /* txdelta window has not been enabled */
4703
 
      *is_cached = FALSE;
4704
 
    }
4705
 
  else
4706
 
    {
4707
 
      /* ask the cache for the desired txdelta window */
4708
 
      svn_fs_fs__txdelta_cached_window_t *cached_window;
4709
 
      SVN_ERR(svn_cache__get((void **) &cached_window,
4710
 
                             is_cached,
4711
 
                             rs->window_cache,
4712
 
                             get_window_key(rs, rs->off, pool),
4713
 
                             pool));
4714
 
 
4715
 
      if (*is_cached)
4716
 
        {
4717
 
          /* found it. Pass it back to the caller. */
4718
 
          *window_p = cached_window->window;
4719
 
 
4720
 
          /* manipulate the RS as if we just read the data */
4721
 
          rs->chunk_index++;
4722
 
          rs->off = cached_window->end_offset;
4723
 
 
4724
 
          /* manipulate the rev file as if we just read from it */
4725
 
          SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4726
 
        }
4727
 
    }
4728
 
 
4729
 
  return SVN_NO_ERROR;
4730
 
}
4731
 
 
4732
 
/* Store the WINDOW read at OFFSET for the rep state RS in the current
4733
 
 * FSFS session's cache. This will be a no-op if no cache has been given.
4734
 
 * Temporary allocations will be made from SCRATCH_POOL. */
4735
 
static svn_error_t *
4736
 
set_cached_window(svn_txdelta_window_t *window,
4737
 
                  struct rep_state *rs,
4738
 
                  apr_off_t offset,
4739
 
                  apr_pool_t *scratch_pool)
4740
 
{
4741
 
  if (rs->window_cache)
4742
 
    {
4743
 
      /* store the window and the first offset _past_ it */
4744
 
      svn_fs_fs__txdelta_cached_window_t cached_window;
4745
 
 
4746
 
      cached_window.window = window;
4747
 
      cached_window.end_offset = rs->off;
4748
 
 
4749
 
      /* but key it with the start offset because that is the known state
4750
 
       * when we will look it up */
4751
 
      return svn_cache__set(rs->window_cache,
4752
 
                            get_window_key(rs, offset, scratch_pool),
4753
 
                            &cached_window,
4754
 
                            scratch_pool);
4755
 
    }
4756
 
 
4757
 
  return SVN_NO_ERROR;
4758
 
}
4759
 
 
4760
 
/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4761
 
 * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4762
 
 * cache has been given. If a cache is available IS_CACHED will inform
4763
 
 * the caller about the success of the lookup. Allocations (of the window
4764
 
 * in particualar) will be made from POOL.
4765
 
 */
4766
 
static svn_error_t *
4767
 
get_cached_combined_window(svn_stringbuf_t **window_p,
4768
 
                           struct rep_state *rs,
4769
 
                           svn_boolean_t *is_cached,
4770
 
                           apr_pool_t *pool)
4771
 
{
4772
 
  if (! rs->combined_cache)
4773
 
    {
4774
 
      /* txdelta window has not been enabled */
4775
 
      *is_cached = FALSE;
4776
 
    }
4777
 
  else
4778
 
    {
4779
 
      /* ask the cache for the desired txdelta window */
4780
 
      return svn_cache__get((void **)window_p,
4781
 
                            is_cached,
4782
 
                            rs->combined_cache,
4783
 
                            get_window_key(rs, rs->start, pool),
4784
 
                            pool);
4785
 
    }
4786
 
 
4787
 
  return SVN_NO_ERROR;
4788
 
}
4789
 
 
4790
 
/* Store the WINDOW read at OFFSET for the rep state RS in the current
4791
 
 * FSFS session's cache. This will be a no-op if no cache has been given.
4792
 
 * Temporary allocations will be made from SCRATCH_POOL. */
4793
 
static svn_error_t *
4794
 
set_cached_combined_window(svn_stringbuf_t *window,
4795
 
                           struct rep_state *rs,
4796
 
                           apr_off_t offset,
4797
 
                           apr_pool_t *scratch_pool)
4798
 
{
4799
 
  if (rs->combined_cache)
4800
 
    {
4801
 
      /* but key it with the start offset because that is the known state
4802
 
       * when we will look it up */
4803
 
      return svn_cache__set(rs->combined_cache,
4804
 
                            get_window_key(rs, offset, scratch_pool),
4805
 
                            window,
4806
 
                            scratch_pool);
4807
 
    }
4808
 
 
4809
 
  return SVN_NO_ERROR;
4810
 
}
4811
 
 
4812
 
/* Build an array of rep_state structures in *LIST giving the delta
4813
 
   reps from first_rep to a plain-text or self-compressed rep.  Set
4814
 
   *SRC_STATE to the plain-text rep we find at the end of the chain,
4815
 
   or to NULL if the final delta representation is self-compressed.
4816
 
   The representation to start from is designated by filesystem FS, id
4817
 
   ID, and representation REP.
4818
 
   Also, set *WINDOW_P to the base window content for *LIST, if it
4819
 
   could be found in cache. Otherwise, *LIST will contain the base
4820
 
   representation for the whole delta chain.
4821
 
   Finally, return the expanded size of the representation in
4822
 
   *EXPANDED_SIZE. It will take care of cases where only the on-disk
4823
 
   size is known.  */
4824
 
static svn_error_t *
4825
 
build_rep_list(apr_array_header_t **list,
4826
 
               svn_stringbuf_t **window_p,
4827
 
               struct rep_state **src_state,
4828
 
               svn_filesize_t *expanded_size,
4829
 
               svn_fs_t *fs,
4830
 
               representation_t *first_rep,
4831
 
               apr_pool_t *pool)
4832
 
{
4833
 
  representation_t rep;
4834
 
  struct rep_state *rs = NULL;
4835
 
  struct rep_args *rep_args;
4836
 
  svn_boolean_t is_cached = FALSE;
4837
 
  apr_file_t *last_file = NULL;
4838
 
  svn_revnum_t last_revision;
4839
 
 
4840
 
  *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
4841
 
  rep = *first_rep;
4842
 
 
4843
 
  /* The value as stored in the data struct.
4844
 
     0 is either for unknown length or actually zero length. */
4845
 
  *expanded_size = first_rep->expanded_size;
4846
 
 
4847
 
  /* for the top-level rep, we need the rep_args */
4848
 
  SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4849
 
                           &last_revision, &rep, fs, pool));
4850
 
 
4851
 
  /* Unknown size or empty representation?
4852
 
     That implies the this being the first iteration.
4853
 
     Usually size equals on-disk size, except for empty,
4854
 
     compressed representations (delta, size = 4).
4855
 
     Please note that for all non-empty deltas have
4856
 
     a 4-byte header _plus_ some data. */
4857
 
  if (*expanded_size == 0)
4858
 
    if (! rep_args->is_delta || first_rep->size != 4)
4859
 
      *expanded_size = first_rep->size;
4860
 
 
4861
 
  while (1)
4862
 
    {
4863
 
      /* fetch state, if that has not been done already */
4864
 
      if (!rs)
4865
 
        SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4866
 
                                &last_revision, &rep, fs, pool));
4867
 
 
4868
 
      SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
4869
 
      if (is_cached)
4870
 
        {
4871
 
          /* We already have a reconstructed window in our cache.
4872
 
             Write a pseudo rep_state with the full length. */
4873
 
          rs->off = rs->start;
4874
 
          rs->end = rs->start + (*window_p)->len;
4875
 
          *src_state = rs;
4876
 
          return SVN_NO_ERROR;
4877
 
        }
4878
 
 
4879
 
      if (!rep_args->is_delta)
4880
 
        {
4881
 
          /* This is a plaintext, so just return the current rep_state. */
4882
 
          *src_state = rs;
4883
 
          return SVN_NO_ERROR;
4884
 
        }
4885
 
 
4886
 
      /* Push this rep onto the list.  If it's self-compressed, we're done. */
4887
 
      APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
4888
 
      if (rep_args->is_delta_vs_empty)
4889
 
        {
4890
 
          *src_state = NULL;
4891
 
          return SVN_NO_ERROR;
4892
 
        }
4893
 
 
4894
 
      rep.revision = rep_args->base_revision;
4895
 
      rep.offset = rep_args->base_offset;
4896
 
      rep.size = rep_args->base_length;
4897
 
      rep.txn_id = NULL;
4898
 
 
4899
 
      rs = NULL;
4900
 
    }
4901
 
}
4902
 
 
4903
 
 
4904
 
/* Create a rep_read_baton structure for node revision NODEREV in
4905
 
   filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
4906
 
   NULL, it is the rep's key in the fulltext cache, and a stringbuf
4907
 
   must be allocated to store the text.  Perform all allocations in
4908
 
   POOL.  If rep is mutable, it must be for file contents. */
4909
 
static svn_error_t *
4910
 
rep_read_get_baton(struct rep_read_baton **rb_p,
4911
 
                   svn_fs_t *fs,
4912
 
                   representation_t *rep,
4913
 
                   pair_cache_key_t fulltext_cache_key,
4914
 
                   apr_pool_t *pool)
4915
 
{
4916
 
  struct rep_read_baton *b;
4917
 
 
4918
 
  b = apr_pcalloc(pool, sizeof(*b));
4919
 
  b->fs = fs;
4920
 
  b->base_window = NULL;
4921
 
  b->chunk_index = 0;
4922
 
  b->buf = NULL;
4923
 
  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
4924
 
  b->checksum_finalized = FALSE;
4925
 
  b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
4926
 
  b->len = rep->expanded_size;
4927
 
  b->off = 0;
4928
 
  b->fulltext_cache_key = fulltext_cache_key;
4929
 
  b->pool = svn_pool_create(pool);
4930
 
  b->filehandle_pool = svn_pool_create(pool);
4931
 
 
4932
 
  SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
4933
 
                         &b->src_state, &b->len, fs, rep,
4934
 
                         b->filehandle_pool));
4935
 
 
4936
 
  if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
4937
 
    b->current_fulltext = svn_stringbuf_create_ensure
4938
 
                            ((apr_size_t)b->len,
4939
 
                             b->filehandle_pool);
4940
 
  else
4941
 
    b->current_fulltext = NULL;
4942
 
 
4943
 
  /* Save our output baton. */
4944
 
  *rb_p = b;
4945
 
 
4946
 
  return SVN_NO_ERROR;
4947
 
}
4948
 
 
4949
 
/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
4950
 
   window into *NWIN. */
4951
 
static svn_error_t *
4952
 
read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
4953
 
                  struct rep_state *rs, apr_pool_t *pool)
4954
 
{
4955
 
  svn_stream_t *stream;
4956
 
  svn_boolean_t is_cached;
4957
 
  apr_off_t old_offset;
4958
 
 
4959
 
  SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
4960
 
 
4961
 
  /* RS->FILE may be shared between RS instances -> make sure we point
4962
 
   * to the right data. */
4963
 
  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4964
 
 
4965
 
  /* Skip windows to reach the current chunk if we aren't there yet. */
4966
 
  while (rs->chunk_index < this_chunk)
4967
 
    {
4968
 
      SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
4969
 
      rs->chunk_index++;
4970
 
      SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4971
 
      if (rs->off >= rs->end)
4972
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4973
 
                                _("Reading one svndiff window read "
4974
 
                                  "beyond the end of the "
4975
 
                                  "representation"));
4976
 
    }
4977
 
 
4978
 
  /* Read the next window. But first, try to find it in the cache. */
4979
 
  SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
4980
 
  if (is_cached)
4981
 
    return SVN_NO_ERROR;
4982
 
 
4983
 
  /* Actually read the next window. */
4984
 
  old_offset = rs->off;
4985
 
  stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
4986
 
  SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
4987
 
  rs->chunk_index++;
4988
 
  SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4989
 
 
4990
 
  if (rs->off > rs->end)
4991
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4992
 
                            _("Reading one svndiff window read beyond "
4993
 
                              "the end of the representation"));
4994
 
 
4995
 
  /* the window has not been cached before, thus cache it now
4996
 
   * (if caching is used for them at all) */
4997
 
  return set_cached_window(*nwin, rs, old_offset, pool);
4998
 
}
4999
 
 
5000
 
/* Read SIZE bytes from the representation RS and return it in *NWIN. */
5001
 
static svn_error_t *
5002
 
read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
5003
 
                  apr_size_t size, apr_pool_t *pool)
5004
 
{
5005
 
  /* RS->FILE may be shared between RS instances -> make sure we point
5006
 
   * to the right data. */
5007
 
  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
5008
 
 
5009
 
  /* Read the plain data. */
5010
 
  *nwin = svn_stringbuf_create_ensure(size, pool);
5011
 
  SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
5012
 
                                 pool));
5013
 
  (*nwin)->data[size] = 0;
5014
 
 
5015
 
  /* Update RS. */
5016
 
  rs->off += (apr_off_t)size;
5017
 
 
5018
 
  return SVN_NO_ERROR;
5019
 
}
5020
 
 
5021
 
/* Get the undeltified window that is a result of combining all deltas
5022
 
   from the current desired representation identified in *RB with its
5023
 
   base representation.  Store the window in *RESULT. */
5024
 
static svn_error_t *
5025
 
get_combined_window(svn_stringbuf_t **result,
5026
 
                    struct rep_read_baton *rb)
5027
 
{
5028
 
  apr_pool_t *pool, *new_pool, *window_pool;
5029
 
  int i;
5030
 
  svn_txdelta_window_t *window;
5031
 
  apr_array_header_t *windows;
5032
 
  svn_stringbuf_t *source, *buf = rb->base_window;
5033
 
  struct rep_state *rs;
5034
 
 
5035
 
  /* Read all windows that we need to combine. This is fine because
5036
 
     the size of each window is relatively small (100kB) and skip-
5037
 
     delta limits the number of deltas in a chain to well under 100.
5038
 
     Stop early if one of them does not depend on its predecessors. */
5039
 
  window_pool = svn_pool_create(rb->pool);
5040
 
  windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
5041
 
  for (i = 0; i < rb->rs_list->nelts; ++i)
5042
 
    {
5043
 
      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5044
 
      SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
5045
 
 
5046
 
      APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
5047
 
      if (window->src_ops == 0)
5048
 
        {
5049
 
          ++i;
5050
 
          break;
5051
 
        }
5052
 
    }
5053
 
 
5054
 
  /* Combine in the windows from the other delta reps. */
5055
 
  pool = svn_pool_create(rb->pool);
5056
 
  for (--i; i >= 0; --i)
5057
 
    {
5058
 
 
5059
 
      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5060
 
      window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
5061
 
 
5062
 
      /* Maybe, we've got a PLAIN start representation.  If we do, read
5063
 
         as much data from it as the needed for the txdelta window's source
5064
 
         view.
5065
 
         Note that BUF / SOURCE may only be NULL in the first iteration. */
5066
 
      source = buf;
5067
 
      if (source == NULL && rb->src_state != NULL)
5068
 
        SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
5069
 
                                  pool));
5070
 
 
5071
 
      /* Combine this window with the current one. */
5072
 
      new_pool = svn_pool_create(rb->pool);
5073
 
      buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
5074
 
      buf->len = window->tview_len;
5075
 
 
5076
 
      svn_txdelta_apply_instructions(window, source ? source->data : NULL,
5077
 
                                     buf->data, &buf->len);
5078
 
      if (buf->len != window->tview_len)
5079
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5080
 
                                _("svndiff window length is "
5081
 
                                  "corrupt"));
5082
 
 
5083
 
      /* Cache windows only if the whole rep content could be read as a
5084
 
         single chunk.  Only then will no other chunk need a deeper RS
5085
 
         list than the cached chunk. */
5086
 
      if ((rb->chunk_index == 0) && (rs->off == rs->end))
5087
 
        SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
5088
 
 
5089
 
      /* Cycle pools so that we only need to hold three windows at a time. */
5090
 
      svn_pool_destroy(pool);
5091
 
      pool = new_pool;
5092
 
    }
5093
 
 
5094
 
  svn_pool_destroy(window_pool);
5095
 
 
5096
 
  *result = buf;
5097
 
  return SVN_NO_ERROR;
5098
 
}
5099
 
 
5100
 
/* Returns whether or not the expanded fulltext of the file is cachable
5101
 
 * based on its size SIZE.  The decision depends on the cache used by RB.
5102
 
 */
5103
 
static svn_boolean_t
5104
 
fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
5105
 
{
5106
 
  return (size < APR_SIZE_MAX)
5107
 
      && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
5108
 
}
5109
 
 
5110
 
/* Close method used on streams returned by read_representation().
5111
 
 */
5112
 
static svn_error_t *
5113
 
rep_read_contents_close(void *baton)
5114
 
{
5115
 
  struct rep_read_baton *rb = baton;
5116
 
 
5117
 
  svn_pool_destroy(rb->pool);
5118
 
  svn_pool_destroy(rb->filehandle_pool);
5119
 
 
5120
 
  return SVN_NO_ERROR;
5121
 
}
5122
 
 
5123
 
/* Return the next *LEN bytes of the rep and store them in *BUF. */
5124
 
static svn_error_t *
5125
 
get_contents(struct rep_read_baton *rb,
5126
 
             char *buf,
5127
 
             apr_size_t *len)
5128
 
{
5129
 
  apr_size_t copy_len, remaining = *len;
5130
 
  char *cur = buf;
5131
 
  struct rep_state *rs;
5132
 
 
5133
 
  /* Special case for when there are no delta reps, only a plain
5134
 
     text. */
5135
 
  if (rb->rs_list->nelts == 0)
5136
 
    {
5137
 
      copy_len = remaining;
5138
 
      rs = rb->src_state;
5139
 
 
5140
 
      if (rb->base_window != NULL)
5141
 
        {
5142
 
          /* We got the desired rep directly from the cache.
5143
 
             This is where we need the pseudo rep_state created
5144
 
             by build_rep_list(). */
5145
 
          apr_size_t offset = (apr_size_t)(rs->off - rs->start);
5146
 
          if (copy_len + offset > rb->base_window->len)
5147
 
            copy_len = offset < rb->base_window->len
5148
 
                     ? rb->base_window->len - offset
5149
 
                     : 0ul;
5150
 
 
5151
 
          memcpy (cur, rb->base_window->data + offset, copy_len);
5152
 
        }
5153
 
      else
5154
 
        {
5155
 
          if (((apr_off_t) copy_len) > rs->end - rs->off)
5156
 
            copy_len = (apr_size_t) (rs->end - rs->off);
5157
 
          SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
5158
 
                                         NULL, rb->pool));
5159
 
        }
5160
 
 
5161
 
      rs->off += copy_len;
5162
 
      *len = copy_len;
5163
 
      return SVN_NO_ERROR;
5164
 
    }
5165
 
 
5166
 
  while (remaining > 0)
5167
 
    {
5168
 
      /* If we have buffered data from a previous chunk, use that. */
5169
 
      if (rb->buf)
5170
 
        {
5171
 
          /* Determine how much to copy from the buffer. */
5172
 
          copy_len = rb->buf_len - rb->buf_pos;
5173
 
          if (copy_len > remaining)
5174
 
            copy_len = remaining;
5175
 
 
5176
 
          /* Actually copy the data. */
5177
 
          memcpy(cur, rb->buf + rb->buf_pos, copy_len);
5178
 
          rb->buf_pos += copy_len;
5179
 
          cur += copy_len;
5180
 
          remaining -= copy_len;
5181
 
 
5182
 
          /* If the buffer is all used up, clear it and empty the
5183
 
             local pool. */
5184
 
          if (rb->buf_pos == rb->buf_len)
5185
 
            {
5186
 
              svn_pool_clear(rb->pool);
5187
 
              rb->buf = NULL;
5188
 
            }
5189
 
        }
5190
 
      else
5191
 
        {
5192
 
          svn_stringbuf_t *sbuf = NULL;
5193
 
 
5194
 
          rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
5195
 
          if (rs->off == rs->end)
5196
 
            break;
5197
 
 
5198
 
          /* Get more buffered data by evaluating a chunk. */
5199
 
          SVN_ERR(get_combined_window(&sbuf, rb));
5200
 
 
5201
 
          rb->chunk_index++;
5202
 
          rb->buf_len = sbuf->len;
5203
 
          rb->buf = sbuf->data;
5204
 
          rb->buf_pos = 0;
5205
 
        }
5206
 
    }
5207
 
 
5208
 
  *len = cur - buf;
5209
 
 
5210
 
  return SVN_NO_ERROR;
5211
 
}
5212
 
 
5213
 
/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
5214
 
   representation and store them in *BUF.  Sum as we read and verify
5215
 
   the MD5 sum at the end. */
5216
 
static svn_error_t *
5217
 
rep_read_contents(void *baton,
5218
 
                  char *buf,
5219
 
                  apr_size_t *len)
5220
 
{
5221
 
  struct rep_read_baton *rb = baton;
5222
 
 
5223
 
  /* Get the next block of data. */
5224
 
  SVN_ERR(get_contents(rb, buf, len));
5225
 
 
5226
 
  if (rb->current_fulltext)
5227
 
    svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
5228
 
 
5229
 
  /* Perform checksumming.  We want to check the checksum as soon as
5230
 
     the last byte of data is read, in case the caller never performs
5231
 
     a short read, but we don't want to finalize the MD5 context
5232
 
     twice. */
5233
 
  if (!rb->checksum_finalized)
5234
 
    {
5235
 
      SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
5236
 
      rb->off += *len;
5237
 
      if (rb->off == rb->len)
5238
 
        {
5239
 
          svn_checksum_t *md5_checksum;
5240
 
 
5241
 
          rb->checksum_finalized = TRUE;
5242
 
          SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
5243
 
                                     rb->pool));
5244
 
          if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
5245
 
            return svn_error_create(SVN_ERR_FS_CORRUPT,
5246
 
                    svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
5247
 
                        rb->pool,
5248
 
                        _("Checksum mismatch while reading representation")),
5249
 
                    NULL);
5250
 
        }
5251
 
    }
5252
 
 
5253
 
  if (rb->off == rb->len && rb->current_fulltext)
5254
 
    {
5255
 
      fs_fs_data_t *ffd = rb->fs->fsap_data;
5256
 
      SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
5257
 
                             rb->current_fulltext, rb->pool));
5258
 
      rb->current_fulltext = NULL;
5259
 
    }
5260
 
 
5261
 
  return SVN_NO_ERROR;
5262
 
}
5263
 
 
5264
 
 
5265
 
/* Return a stream in *CONTENTS_P that will read the contents of a
5266
 
   representation stored at the location given by REP.  Appropriate
5267
 
   for any kind of immutable representation, but only for file
5268
 
   contents (not props or directory contents) in mutable
5269
 
   representations.
5270
 
 
5271
 
   If REP is NULL, the representation is assumed to be empty, and the
5272
 
   empty stream is returned.
5273
 
*/
5274
 
static svn_error_t *
5275
 
read_representation(svn_stream_t **contents_p,
5276
 
                    svn_fs_t *fs,
5277
 
                    representation_t *rep,
5278
 
                    apr_pool_t *pool)
5279
 
{
5280
 
  if (! rep)
5281
 
    {
5282
 
      *contents_p = svn_stream_empty(pool);
5283
 
    }
5284
 
  else
5285
 
    {
5286
 
      fs_fs_data_t *ffd = fs->fsap_data;
5287
 
      pair_cache_key_t fulltext_cache_key = { 0 };
5288
 
      svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
5289
 
      struct rep_read_baton *rb;
5290
 
 
5291
 
      fulltext_cache_key.revision = rep->revision;
5292
 
      fulltext_cache_key.second = rep->offset;
5293
 
      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5294
 
          && fulltext_size_is_cachable(ffd, len))
5295
 
        {
5296
 
          svn_stringbuf_t *fulltext;
5297
 
          svn_boolean_t is_cached;
5298
 
          SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
5299
 
                                 ffd->fulltext_cache, &fulltext_cache_key,
5300
 
                                 pool));
5301
 
          if (is_cached)
5302
 
            {
5303
 
              *contents_p = svn_stream_from_stringbuf(fulltext, pool);
5304
 
              return SVN_NO_ERROR;
5305
 
            }
5306
 
        }
5307
 
      else
5308
 
        fulltext_cache_key.revision = SVN_INVALID_REVNUM;
5309
 
 
5310
 
      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
5311
 
 
5312
 
      *contents_p = svn_stream_create(rb, pool);
5313
 
      svn_stream_set_read(*contents_p, rep_read_contents);
5314
 
      svn_stream_set_close(*contents_p, rep_read_contents_close);
5315
 
    }
5316
 
 
5317
 
  return SVN_NO_ERROR;
5318
 
}
5319
 
 
5320
 
svn_error_t *
5321
 
svn_fs_fs__get_contents(svn_stream_t **contents_p,
5322
 
                        svn_fs_t *fs,
5323
 
                        node_revision_t *noderev,
5324
 
                        apr_pool_t *pool)
5325
 
{
5326
 
  return read_representation(contents_p, fs, noderev->data_rep, pool);
5327
 
}
5328
 
 
5329
 
/* Baton used when reading delta windows. */
5330
 
struct delta_read_baton
5331
 
{
5332
 
  struct rep_state *rs;
5333
 
  svn_checksum_t *checksum;
5334
 
};
5335
 
 
5336
 
/* This implements the svn_txdelta_next_window_fn_t interface. */
5337
 
static svn_error_t *
5338
 
delta_read_next_window(svn_txdelta_window_t **window, void *baton,
5339
 
                       apr_pool_t *pool)
5340
 
{
5341
 
  struct delta_read_baton *drb = baton;
5342
 
 
5343
 
  if (drb->rs->off == drb->rs->end)
5344
 
    {
5345
 
      *window = NULL;
5346
 
      return SVN_NO_ERROR;
5347
 
    }
5348
 
 
5349
 
  return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
5350
 
}
5351
 
 
5352
 
/* This implements the svn_txdelta_md5_digest_fn_t interface. */
5353
 
static const unsigned char *
5354
 
delta_read_md5_digest(void *baton)
5355
 
{
5356
 
  struct delta_read_baton *drb = baton;
5357
 
 
5358
 
  if (drb->checksum->kind == svn_checksum_md5)
5359
 
    return drb->checksum->digest;
5360
 
  else
5361
 
    return NULL;
5362
 
}
5363
 
 
5364
 
svn_error_t *
5365
 
svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
5366
 
                                 svn_fs_t *fs,
5367
 
                                 node_revision_t *source,
5368
 
                                 node_revision_t *target,
5369
 
                                 apr_pool_t *pool)
5370
 
{
5371
 
  svn_stream_t *source_stream, *target_stream;
5372
 
 
5373
 
  /* Try a shortcut: if the target is stored as a delta against the source,
5374
 
     then just use that delta. */
5375
 
  if (source && source->data_rep && target->data_rep)
5376
 
    {
5377
 
      struct rep_state *rep_state;
5378
 
      struct rep_args *rep_args;
5379
 
 
5380
 
      /* Read target's base rep if any. */
5381
 
      SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
5382
 
                               target->data_rep, fs, pool));
5383
 
 
5384
 
      /* If that matches source, then use this delta as is.
5385
 
         Note that we want an actual delta here.  E.g. a self-delta would
5386
 
         not be good enough. */
5387
 
      if (rep_args->is_delta
5388
 
          && rep_args->base_revision == source->data_rep->revision
5389
 
          && rep_args->base_offset == source->data_rep->offset)
5390
 
        {
5391
 
          /* Create the delta read baton. */
5392
 
          struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
5393
 
          drb->rs = rep_state;
5394
 
          drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
5395
 
                                           pool);
5396
 
          *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
5397
 
                                                delta_read_md5_digest, pool);
5398
 
          return SVN_NO_ERROR;
5399
 
        }
5400
 
      else
5401
 
        SVN_ERR(svn_io_file_close(rep_state->file, pool));
5402
 
    }
5403
 
 
5404
 
  /* Read both fulltexts and construct a delta. */
5405
 
  if (source)
5406
 
    SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
5407
 
  else
5408
 
    source_stream = svn_stream_empty(pool);
5409
 
  SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
5410
 
 
5411
 
  /* Because source and target stream will already verify their content,
5412
 
   * there is no need to do this once more.  In particular if the stream
5413
 
   * content is being fetched from cache. */
5414
 
  svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
5415
 
 
5416
 
  return SVN_NO_ERROR;
5417
 
}
5418
 
 
5419
 
/* Baton for cache_access_wrapper. Wraps the original parameters of
5420
 
 * svn_fs_fs__try_process_file_content().
5421
 
 */
5422
 
typedef struct cache_access_wrapper_baton_t
5423
 
{
5424
 
  svn_fs_process_contents_func_t func;
5425
 
  void* baton;
5426
 
} cache_access_wrapper_baton_t;
5427
 
 
5428
 
/* Wrapper to translate between svn_fs_process_contents_func_t and
5429
 
 * svn_cache__partial_getter_func_t.
5430
 
 */
5431
 
static svn_error_t *
5432
 
cache_access_wrapper(void **out,
5433
 
                     const void *data,
5434
 
                     apr_size_t data_len,
5435
 
                     void *baton,
5436
 
                     apr_pool_t *pool)
5437
 
{
5438
 
  cache_access_wrapper_baton_t *wrapper_baton = baton;
5439
 
 
5440
 
  SVN_ERR(wrapper_baton->func((const unsigned char *)data,
5441
 
                              data_len - 1, /* cache adds terminating 0 */
5442
 
                              wrapper_baton->baton,
5443
 
                              pool));
5444
 
 
5445
 
  /* non-NULL value to signal the calling cache that all went well */
5446
 
  *out = baton;
5447
 
 
5448
 
  return SVN_NO_ERROR;
5449
 
}
5450
 
 
5451
 
svn_error_t *
5452
 
svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
5453
 
                                     svn_fs_t *fs,
5454
 
                                     node_revision_t *noderev,
5455
 
                                     svn_fs_process_contents_func_t processor,
5456
 
                                     void* baton,
5457
 
                                     apr_pool_t *pool)
5458
 
{
5459
 
  representation_t *rep = noderev->data_rep;
5460
 
  if (rep)
5461
 
    {
5462
 
      fs_fs_data_t *ffd = fs->fsap_data;
5463
 
      pair_cache_key_t fulltext_cache_key = { 0 };
5464
 
 
5465
 
      fulltext_cache_key.revision = rep->revision;
5466
 
      fulltext_cache_key.second = rep->offset;
5467
 
      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5468
 
          && fulltext_size_is_cachable(ffd, rep->expanded_size))
5469
 
        {
5470
 
          cache_access_wrapper_baton_t wrapper_baton;
5471
 
          void *dummy = NULL;
5472
 
 
5473
 
          wrapper_baton.func = processor;
5474
 
          wrapper_baton.baton = baton;
5475
 
          return svn_cache__get_partial(&dummy, success,
5476
 
                                        ffd->fulltext_cache,
5477
 
                                        &fulltext_cache_key,
5478
 
                                        cache_access_wrapper,
5479
 
                                        &wrapper_baton,
5480
 
                                        pool);
5481
 
        }
5482
 
    }
5483
 
 
5484
 
  *success = FALSE;
5485
 
  return SVN_NO_ERROR;
5486
 
}
5487
 
 
5488
 
/* Fetch the contents of a directory into ENTRIES.  Values are stored
5489
 
   as filename to string mappings; further conversion is necessary to
5490
 
   convert them into svn_fs_dirent_t values. */
5491
 
static svn_error_t *
5492
 
get_dir_contents(apr_hash_t *entries,
5493
 
                 svn_fs_t *fs,
5494
 
                 node_revision_t *noderev,
5495
 
                 apr_pool_t *pool)
5496
 
{
5497
 
  svn_stream_t *contents;
5498
 
 
5499
 
  if (noderev->data_rep && noderev->data_rep->txn_id)
5500
 
    {
5501
 
      const char *filename = path_txn_node_children(fs, noderev->id, pool);
5502
 
 
5503
 
      /* The representation is mutable.  Read the old directory
5504
 
         contents from the mutable children file, followed by the
5505
 
         changes we've made in this transaction. */
5506
 
      SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
5507
 
      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5508
 
      SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
5509
 
      SVN_ERR(svn_stream_close(contents));
5510
 
    }
5511
 
  else if (noderev->data_rep)
5512
 
    {
5513
 
      /* use a temporary pool for temp objects.
5514
 
       * Also undeltify content before parsing it. Otherwise, we could only
5515
 
       * parse it byte-by-byte.
5516
 
       */
5517
 
      apr_pool_t *text_pool = svn_pool_create(pool);
5518
 
      apr_size_t len = noderev->data_rep->expanded_size
5519
 
                     ? (apr_size_t)noderev->data_rep->expanded_size
5520
 
                     : (apr_size_t)noderev->data_rep->size;
5521
 
      svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
5522
 
      text->len = len;
5523
 
 
5524
 
      /* The representation is immutable.  Read it normally. */
5525
 
      SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
5526
 
      SVN_ERR(svn_stream_read(contents, text->data, &text->len));
5527
 
      SVN_ERR(svn_stream_close(contents));
5528
 
 
5529
 
      /* de-serialize hash */
5530
 
      contents = svn_stream_from_stringbuf(text, text_pool);
5531
 
      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5532
 
 
5533
 
      svn_pool_destroy(text_pool);
5534
 
    }
5535
 
 
5536
 
  return SVN_NO_ERROR;
5537
 
}
5538
 
 
5539
 
 
5540
 
static const char *
5541
 
unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
5542
 
                  apr_pool_t *pool)
5543
 
{
5544
 
  return apr_psprintf(pool, "%s %s",
5545
 
                      (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
5546
 
                      svn_fs_fs__id_unparse(id, pool)->data);
5547
 
}
5548
 
 
5549
 
/* Given a hash ENTRIES of dirent structions, return a hash in
5550
 
   *STR_ENTRIES_P, that has svn_string_t as the values in the format
5551
 
   specified by the fs_fs directory contents file.  Perform
5552
 
   allocations in POOL. */
5553
 
static svn_error_t *
5554
 
unparse_dir_entries(apr_hash_t **str_entries_p,
5555
 
                    apr_hash_t *entries,
5556
 
                    apr_pool_t *pool)
5557
 
{
5558
 
  apr_hash_index_t *hi;
5559
 
 
5560
 
  /* For now, we use a our own hash function to ensure that we get a
5561
 
   * (largely) stable order when serializing the data.  It also gives
5562
 
   * us some performance improvement.
5563
 
   *
5564
 
   * ### TODO ###
5565
 
   * Use some sorted or other fixed order data container.
5566
 
   */
5567
 
  *str_entries_p = svn_hash__make(pool);
5568
 
 
5569
 
  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
5570
 
    {
5571
 
      const void *key;
5572
 
      apr_ssize_t klen;
5573
 
      svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
5574
 
      const char *new_val;
5575
 
 
5576
 
      apr_hash_this(hi, &key, &klen, NULL);
5577
 
      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
5578
 
      apr_hash_set(*str_entries_p, key, klen,
5579
 
                   svn_string_create(new_val, pool));
5580
 
    }
5581
 
 
5582
 
  return SVN_NO_ERROR;
5583
 
}
5584
 
 
5585
 
 
5586
 
/* Given a hash STR_ENTRIES with values as svn_string_t as specified
5587
 
   in an FSFS directory contents listing, return a hash of dirents in
5588
 
   *ENTRIES_P.  Perform allocations in POOL. */
5589
 
static svn_error_t *
5590
 
parse_dir_entries(apr_hash_t **entries_p,
5591
 
                  apr_hash_t *str_entries,
5592
 
                  const char *unparsed_id,
5593
 
                  apr_pool_t *pool)
5594
 
{
5595
 
  apr_hash_index_t *hi;
5596
 
 
5597
 
  *entries_p = apr_hash_make(pool);
5598
 
 
5599
 
  /* Translate the string dir entries into real entries. */
5600
 
  for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
5601
 
    {
5602
 
      const char *name = svn__apr_hash_index_key(hi);
5603
 
      svn_string_t *str_val = svn__apr_hash_index_val(hi);
5604
 
      char *str, *last_str;
5605
 
      svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
5606
 
 
5607
 
      last_str = apr_pstrdup(pool, str_val->data);
5608
 
      dirent->name = apr_pstrdup(pool, name);
5609
 
 
5610
 
      str = svn_cstring_tokenize(" ", &last_str);
5611
 
      if (str == NULL)
5612
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5613
 
                                 _("Directory entry corrupt in '%s'"),
5614
 
                                 unparsed_id);
5615
 
 
5616
 
      if (strcmp(str, KIND_FILE) == 0)
5617
 
        {
5618
 
          dirent->kind = svn_node_file;
5619
 
        }
5620
 
      else if (strcmp(str, KIND_DIR) == 0)
5621
 
        {
5622
 
          dirent->kind = svn_node_dir;
5623
 
        }
5624
 
      else
5625
 
        {
5626
 
          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5627
 
                                   _("Directory entry corrupt in '%s'"),
5628
 
                                   unparsed_id);
5629
 
        }
5630
 
 
5631
 
      str = svn_cstring_tokenize(" ", &last_str);
5632
 
      if (str == NULL)
5633
 
          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5634
 
                                   _("Directory entry corrupt in '%s'"),
5635
 
                                   unparsed_id);
5636
 
 
5637
 
      dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
5638
 
 
5639
 
      svn_hash_sets(*entries_p, dirent->name, dirent);
5640
 
    }
5641
 
 
5642
 
  return SVN_NO_ERROR;
5643
 
}
5644
 
 
5645
 
/* Return the cache object in FS responsible to storing the directory
5646
 
 * the NODEREV. If none exists, return NULL. */
5647
 
static svn_cache__t *
5648
 
locate_dir_cache(svn_fs_t *fs,
5649
 
                 node_revision_t *noderev)
5650
 
{
5651
 
  fs_fs_data_t *ffd = fs->fsap_data;
5652
 
  return svn_fs_fs__id_txn_id(noderev->id)
5653
 
      ? ffd->txn_dir_cache
5654
 
      : ffd->dir_cache;
5655
 
}
5656
 
 
5657
 
svn_error_t *
5658
 
svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
5659
 
                            svn_fs_t *fs,
5660
 
                            node_revision_t *noderev,
5661
 
                            apr_pool_t *pool)
5662
 
{
5663
 
  const char *unparsed_id = NULL;
5664
 
  apr_hash_t *unparsed_entries, *parsed_entries;
5665
 
 
5666
 
  /* find the cache we may use */
5667
 
  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5668
 
  if (cache)
5669
 
    {
5670
 
      svn_boolean_t found;
5671
 
 
5672
 
      unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
5673
 
      SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
5674
 
                             unparsed_id, pool));
5675
 
      if (found)
5676
 
        return SVN_NO_ERROR;
5677
 
    }
5678
 
 
5679
 
  /* Read in the directory hash. */
5680
 
  unparsed_entries = apr_hash_make(pool);
5681
 
  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
5682
 
  SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
5683
 
                            unparsed_id, pool));
5684
 
 
5685
 
  /* Update the cache, if we are to use one. */
5686
 
  if (cache)
5687
 
    SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
5688
 
 
5689
 
  *entries_p = parsed_entries;
5690
 
  return SVN_NO_ERROR;
5691
 
}
5692
 
 
5693
 
svn_error_t *
5694
 
svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
5695
 
                                  svn_fs_t *fs,
5696
 
                                  node_revision_t *noderev,
5697
 
                                  const char *name,
5698
 
                                  apr_pool_t *result_pool,
5699
 
                                  apr_pool_t *scratch_pool)
5700
 
{
5701
 
  svn_boolean_t found = FALSE;
5702
 
 
5703
 
  /* find the cache we may use */
5704
 
  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5705
 
  if (cache)
5706
 
    {
5707
 
      const char *unparsed_id =
5708
 
        svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
5709
 
 
5710
 
      /* Cache lookup. */
5711
 
      SVN_ERR(svn_cache__get_partial((void **)dirent,
5712
 
                                     &found,
5713
 
                                     cache,
5714
 
                                     unparsed_id,
5715
 
                                     svn_fs_fs__extract_dir_entry,
5716
 
                                     (void*)name,
5717
 
                                     result_pool));
5718
 
    }
5719
 
 
5720
 
  /* fetch data from disk if we did not find it in the cache */
5721
 
  if (! found)
5722
 
    {
5723
 
      apr_hash_t *entries;
5724
 
      svn_fs_dirent_t *entry;
5725
 
      svn_fs_dirent_t *entry_copy = NULL;
5726
 
 
5727
 
      /* read the dir from the file system. It will probably be put it
5728
 
         into the cache for faster lookup in future calls. */
5729
 
      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
5730
 
                                          scratch_pool));
5731
 
 
5732
 
      /* find desired entry and return a copy in POOL, if found */
5733
 
      entry = svn_hash_gets(entries, name);
5734
 
      if (entry != NULL)
5735
 
        {
5736
 
          entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
5737
 
          entry_copy->name = apr_pstrdup(result_pool, entry->name);
5738
 
          entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
5739
 
          entry_copy->kind = entry->kind;
5740
 
        }
5741
 
 
5742
 
      *dirent = entry_copy;
5743
 
    }
5744
 
 
5745
 
  return SVN_NO_ERROR;
5746
 
}
5747
 
 
5748
 
svn_error_t *
5749
 
svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
5750
 
                        svn_fs_t *fs,
5751
 
                        node_revision_t *noderev,
5752
 
                        apr_pool_t *pool)
5753
 
{
5754
 
  apr_hash_t *proplist;
5755
 
  svn_stream_t *stream;
5756
 
 
5757
 
  if (noderev->prop_rep && noderev->prop_rep->txn_id)
5758
 
    {
5759
 
      const char *filename = path_txn_node_props(fs, noderev->id, pool);
5760
 
      proplist = apr_hash_make(pool);
5761
 
 
5762
 
      SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
5763
 
      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5764
 
      SVN_ERR(svn_stream_close(stream));
5765
 
    }
5766
 
  else if (noderev->prop_rep)
5767
 
    {
5768
 
      fs_fs_data_t *ffd = fs->fsap_data;
5769
 
      representation_t *rep = noderev->prop_rep;
5770
 
      pair_cache_key_t key = { 0 };
5771
 
 
5772
 
      key.revision = rep->revision;
5773
 
      key.second = rep->offset;
5774
 
      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5775
 
        {
5776
 
          svn_boolean_t is_cached;
5777
 
          SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
5778
 
                                 ffd->properties_cache, &key, pool));
5779
 
          if (is_cached)
5780
 
            return SVN_NO_ERROR;
5781
 
        }
5782
 
 
5783
 
      proplist = apr_hash_make(pool);
5784
 
      SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
5785
 
      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5786
 
      SVN_ERR(svn_stream_close(stream));
5787
 
 
5788
 
      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5789
 
        SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
5790
 
    }
5791
 
  else
5792
 
    {
5793
 
      /* return an empty prop list if the node doesn't have any props */
5794
 
      proplist = apr_hash_make(pool);
5795
 
    }
5796
 
 
5797
 
  *proplist_p = proplist;
5798
 
 
5799
 
  return SVN_NO_ERROR;
5800
 
}
5801
 
 
5802
 
svn_error_t *
5803
 
svn_fs_fs__file_length(svn_filesize_t *length,
5804
 
                       node_revision_t *noderev,
5805
 
                       apr_pool_t *pool)
5806
 
{
5807
 
  if (noderev->data_rep)
5808
 
    *length = noderev->data_rep->expanded_size;
5809
 
  else
5810
 
    *length = 0;
5811
 
 
5812
 
  return SVN_NO_ERROR;
5813
 
}
5814
 
 
5815
 
svn_boolean_t
5816
 
svn_fs_fs__noderev_same_rep_key(representation_t *a,
5817
 
                                representation_t *b)
5818
 
{
5819
 
  if (a == b)
5820
 
    return TRUE;
5821
 
 
5822
 
  if (a == NULL || b == NULL)
5823
 
    return FALSE;
5824
 
 
5825
 
  if (a->offset != b->offset)
5826
 
    return FALSE;
5827
 
 
5828
 
  if (a->revision != b->revision)
5829
 
    return FALSE;
5830
 
 
5831
 
  if (a->uniquifier == b->uniquifier)
5832
 
    return TRUE;
5833
 
 
5834
 
  if (a->uniquifier == NULL || b->uniquifier == NULL)
5835
 
    return FALSE;
5836
 
 
5837
 
  return strcmp(a->uniquifier, b->uniquifier) == 0;
5838
 
}
 
1493
                          node_revision_t *a,
 
1494
                          node_revision_t *b,
 
1495
                          svn_boolean_t strict,
 
1496
                          apr_pool_t *scratch_pool)
 
1497
{
 
1498
  representation_t *rep_a = a->prop_rep;
 
1499
  representation_t *rep_b = b->prop_rep;
 
1500
  apr_hash_t *proplist_a;
 
1501
  apr_hash_t *proplist_b;
 
1502
 
 
1503
  /* Mainly for a==b==NULL */
 
1504
  if (rep_a == rep_b)
 
1505
    {
 
1506
      *equal = TRUE;
 
1507
      return SVN_NO_ERROR;
 
1508
    }
 
1509
 
 
1510
  /* Committed property lists can be compared quickly */
 
1511
  if (   rep_a && rep_b
 
1512
      && !svn_fs_fs__id_txn_used(&rep_a->txn_id)
 
1513
      && !svn_fs_fs__id_txn_used(&rep_b->txn_id))
 
1514
    {
 
1515
      /* MD5 must be given. Having the same checksum is good enough for
 
1516
         accepting the prop lists as equal. */
 
1517
      *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
 
1518
                      sizeof(rep_a->md5_digest)) == 0;
 
1519
      return SVN_NO_ERROR;
 
1520
    }
 
1521
 
 
1522
  /* Same path in same txn? */
 
1523
  if (svn_fs_fs__id_eq(a->id, b->id))
 
1524
    {
 
1525
      *equal = TRUE;
 
1526
      return SVN_NO_ERROR;
 
1527
    }
 
1528
 
 
1529
  /* Skip the expensive bits unless we are in strict mode.
 
1530
     Simply assume that there is a difference. */
 
1531
  if (!strict)
 
1532
    {
 
1533
      *equal = FALSE;
 
1534
      return SVN_NO_ERROR;
 
1535
    }
 
1536
 
 
1537
  /* At least one of the reps has been modified in a txn.
 
1538
     Fetch and compare them. */
 
1539
  SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool));
 
1540
  SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool));
 
1541
 
 
1542
  *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
 
1543
  return SVN_NO_ERROR;
 
1544
}
 
1545
 
5839
1546
 
5840
1547
svn_error_t *
5841
1548
svn_fs_fs__file_checksum(svn_checksum_t **checksum,
5843
1550
                         svn_checksum_kind_t kind,
5844
1551
                         apr_pool_t *pool)
5845
1552
{
 
1553
  *checksum = NULL;
 
1554
 
5846
1555
  if (noderev->data_rep)
5847
1556
    {
 
1557
      svn_checksum_t temp;
 
1558
      temp.kind = kind;
 
1559
 
5848
1560
      switch(kind)
5849
1561
        {
5850
1562
          case svn_checksum_md5:
5851
 
            *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
5852
 
                                         pool);
 
1563
            temp.digest = noderev->data_rep->md5_digest;
5853
1564
            break;
 
1565
 
5854
1566
          case svn_checksum_sha1:
5855
 
            *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
5856
 
                                         pool);
 
1567
            if (! noderev->data_rep->has_sha1)
 
1568
              return SVN_NO_ERROR;
 
1569
 
 
1570
            temp.digest = noderev->data_rep->sha1_digest;
5857
1571
            break;
 
1572
 
5858
1573
          default:
5859
 
            *checksum = NULL;
 
1574
            return SVN_NO_ERROR;
5860
1575
        }
 
1576
 
 
1577
      *checksum = svn_checksum_dup(&temp, pool);
5861
1578
    }
5862
 
  else
5863
 
    *checksum = NULL;
5864
1579
 
5865
1580
  return SVN_NO_ERROR;
5866
1581
}
5869
1584
svn_fs_fs__rep_copy(representation_t *rep,
5870
1585
                    apr_pool_t *pool)
5871
1586
{
5872
 
  representation_t *rep_new;
5873
 
 
5874
1587
  if (rep == NULL)
5875
1588
    return NULL;
5876
1589
 
5877
 
  rep_new = apr_pcalloc(pool, sizeof(*rep_new));
5878
 
 
5879
 
  memcpy(rep_new, rep, sizeof(*rep_new));
5880
 
  rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
5881
 
  rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
5882
 
  rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
5883
 
 
5884
 
  return rep_new;
5885
 
}
5886
 
 
5887
 
/* Merge the internal-use-only CHANGE into a hash of public-FS
5888
 
   svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
5889
 
   single summarical (is that real word?) change per path.  Also keep
5890
 
   the COPYFROM_CACHE up to date with new adds and replaces.  */
5891
 
static svn_error_t *
5892
 
fold_change(apr_hash_t *changes,
5893
 
            const change_t *change,
5894
 
            apr_hash_t *copyfrom_cache)
5895
 
{
5896
 
  apr_pool_t *pool = apr_hash_pool_get(changes);
5897
 
  svn_fs_path_change2_t *old_change, *new_change;
5898
 
  const char *path;
5899
 
  apr_size_t path_len = strlen(change->path);
5900
 
 
5901
 
  if ((old_change = apr_hash_get(changes, change->path, path_len)))
5902
 
    {
5903
 
      /* This path already exists in the hash, so we have to merge
5904
 
         this change into the already existing one. */
5905
 
 
5906
 
      /* Sanity check:  only allow NULL node revision ID in the
5907
 
         `reset' case. */
5908
 
      if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
5909
 
        return svn_error_create
5910
 
          (SVN_ERR_FS_CORRUPT, NULL,
5911
 
           _("Missing required node revision ID"));
5912
 
 
5913
 
      /* Sanity check: we should be talking about the same node
5914
 
         revision ID as our last change except where the last change
5915
 
         was a deletion. */
5916
 
      if (change->noderev_id
5917
 
          && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
5918
 
          && (old_change->change_kind != svn_fs_path_change_delete))
5919
 
        return svn_error_create
5920
 
          (SVN_ERR_FS_CORRUPT, NULL,
5921
 
           _("Invalid change ordering: new node revision ID "
5922
 
             "without delete"));
5923
 
 
5924
 
      /* Sanity check: an add, replacement, or reset must be the first
5925
 
         thing to follow a deletion. */
5926
 
      if ((old_change->change_kind == svn_fs_path_change_delete)
5927
 
          && (! ((change->kind == svn_fs_path_change_replace)
5928
 
                 || (change->kind == svn_fs_path_change_reset)
5929
 
                 || (change->kind == svn_fs_path_change_add))))
5930
 
        return svn_error_create
5931
 
          (SVN_ERR_FS_CORRUPT, NULL,
5932
 
           _("Invalid change ordering: non-add change on deleted path"));
5933
 
 
5934
 
      /* Sanity check: an add can't follow anything except
5935
 
         a delete or reset.  */
5936
 
      if ((change->kind == svn_fs_path_change_add)
5937
 
          && (old_change->change_kind != svn_fs_path_change_delete)
5938
 
          && (old_change->change_kind != svn_fs_path_change_reset))
5939
 
        return svn_error_create
5940
 
          (SVN_ERR_FS_CORRUPT, NULL,
5941
 
           _("Invalid change ordering: add change on preexisting path"));
5942
 
 
5943
 
      /* Now, merge that change in. */
5944
 
      switch (change->kind)
5945
 
        {
5946
 
        case svn_fs_path_change_reset:
5947
 
          /* A reset here will simply remove the path change from the
5948
 
             hash. */
5949
 
          old_change = NULL;
5950
 
          break;
5951
 
 
5952
 
        case svn_fs_path_change_delete:
5953
 
          if (old_change->change_kind == svn_fs_path_change_add)
5954
 
            {
5955
 
              /* If the path was introduced in this transaction via an
5956
 
                 add, and we are deleting it, just remove the path
5957
 
                 altogether. */
5958
 
              old_change = NULL;
5959
 
            }
5960
 
          else
5961
 
            {
5962
 
              /* A deletion overrules all previous changes. */
5963
 
              old_change->change_kind = svn_fs_path_change_delete;
5964
 
              old_change->text_mod = change->text_mod;
5965
 
              old_change->prop_mod = change->prop_mod;
5966
 
              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5967
 
              old_change->copyfrom_path = NULL;
5968
 
            }
5969
 
          break;
5970
 
 
5971
 
        case svn_fs_path_change_add:
5972
 
        case svn_fs_path_change_replace:
5973
 
          /* An add at this point must be following a previous delete,
5974
 
             so treat it just like a replace. */
5975
 
          old_change->change_kind = svn_fs_path_change_replace;
5976
 
          old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
5977
 
                                                       pool);
5978
 
          old_change->text_mod = change->text_mod;
5979
 
          old_change->prop_mod = change->prop_mod;
5980
 
          if (change->copyfrom_rev == SVN_INVALID_REVNUM)
5981
 
            {
5982
 
              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5983
 
              old_change->copyfrom_path = NULL;
5984
 
            }
5985
 
          else
5986
 
            {
5987
 
              old_change->copyfrom_rev = change->copyfrom_rev;
5988
 
              old_change->copyfrom_path = apr_pstrdup(pool,
5989
 
                                                      change->copyfrom_path);
5990
 
            }
5991
 
          break;
5992
 
 
5993
 
        case svn_fs_path_change_modify:
5994
 
        default:
5995
 
          if (change->text_mod)
5996
 
            old_change->text_mod = TRUE;
5997
 
          if (change->prop_mod)
5998
 
            old_change->prop_mod = TRUE;
5999
 
          break;
6000
 
        }
6001
 
 
6002
 
      /* Point our new_change to our (possibly modified) old_change. */
6003
 
      new_change = old_change;
6004
 
    }
6005
 
  else
6006
 
    {
6007
 
      /* This change is new to the hash, so make a new public change
6008
 
         structure from the internal one (in the hash's pool), and dup
6009
 
         the path into the hash's pool, too. */
6010
 
      new_change = apr_pcalloc(pool, sizeof(*new_change));
6011
 
      new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
6012
 
      new_change->change_kind = change->kind;
6013
 
      new_change->text_mod = change->text_mod;
6014
 
      new_change->prop_mod = change->prop_mod;
6015
 
      /* In FSFS, copyfrom_known is *always* true, since we've always
6016
 
       * stored copyfroms in changed paths lists. */
6017
 
      new_change->copyfrom_known = TRUE;
6018
 
      if (change->copyfrom_rev != SVN_INVALID_REVNUM)
6019
 
        {
6020
 
          new_change->copyfrom_rev = change->copyfrom_rev;
6021
 
          new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
6022
 
        }
6023
 
      else
6024
 
        {
6025
 
          new_change->copyfrom_rev = SVN_INVALID_REVNUM;
6026
 
          new_change->copyfrom_path = NULL;
6027
 
        }
6028
 
    }
6029
 
 
6030
 
  if (new_change)
6031
 
    new_change->node_kind = change->node_kind;
6032
 
 
6033
 
  /* Add (or update) this path.
6034
 
 
6035
 
     Note: this key might already be present, and it would be nice to
6036
 
     re-use its value, but there is no way to fetch it. The API makes no
6037
 
     guarantees that this (new) key will not be retained. Thus, we (again)
6038
 
     copy the key into the target pool to ensure a proper lifetime.  */
6039
 
  path = apr_pstrmemdup(pool, change->path, path_len);
6040
 
  apr_hash_set(changes, path, path_len, new_change);
6041
 
 
6042
 
  /* Update the copyfrom cache, if any. */
6043
 
  if (copyfrom_cache)
6044
 
    {
6045
 
      apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
6046
 
      const char *copyfrom_string = NULL, *copyfrom_key = path;
6047
 
      if (new_change)
6048
 
        {
6049
 
          if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
6050
 
            copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
6051
 
                                           new_change->copyfrom_rev,
6052
 
                                           new_change->copyfrom_path);
6053
 
          else
6054
 
            copyfrom_string = "";
6055
 
        }
6056
 
      /* We need to allocate a copy of the key in the copyfrom_pool if
6057
 
       * we're not doing a deletion and if it isn't already there. */
6058
 
      if (   copyfrom_string
6059
 
          && (   ! apr_hash_count(copyfrom_cache)
6060
 
              || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
6061
 
        copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
6062
 
 
6063
 
      apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
6064
 
                   copyfrom_string);
6065
 
    }
6066
 
 
6067
 
  return SVN_NO_ERROR;
6068
 
}
6069
 
 
6070
 
/* The 256 is an arbitrary size large enough to hold the node id and the
6071
 
 * various flags. */
6072
 
#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
6073
 
 
6074
 
/* Read the next entry in the changes record from file FILE and store
6075
 
   the resulting change in *CHANGE_P.  If there is no next record,
6076
 
   store NULL there.  Perform all allocations from POOL. */
6077
 
static svn_error_t *
6078
 
read_change(change_t **change_p,
6079
 
            apr_file_t *file,
6080
 
            apr_pool_t *pool)
6081
 
{
6082
 
  char buf[MAX_CHANGE_LINE_LEN];
6083
 
  apr_size_t len = sizeof(buf);
6084
 
  change_t *change;
6085
 
  char *str, *last_str = buf, *kind_str;
6086
 
  svn_error_t *err;
6087
 
 
6088
 
  /* Default return value. */
6089
 
  *change_p = NULL;
6090
 
 
6091
 
  err = svn_io_read_length_line(file, buf, &len, pool);
6092
 
 
6093
 
  /* Check for a blank line. */
6094
 
  if (err || (len == 0))
6095
 
    {
6096
 
      if (err && APR_STATUS_IS_EOF(err->apr_err))
6097
 
        {
6098
 
          svn_error_clear(err);
6099
 
          return SVN_NO_ERROR;
6100
 
        }
6101
 
      if ((len == 0) && (! err))
6102
 
        return SVN_NO_ERROR;
6103
 
      return svn_error_trace(err);
6104
 
    }
6105
 
 
6106
 
  change = apr_pcalloc(pool, sizeof(*change));
6107
 
 
6108
 
  /* Get the node-id of the change. */
6109
 
  str = svn_cstring_tokenize(" ", &last_str);
6110
 
  if (str == NULL)
6111
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6112
 
                            _("Invalid changes line in rev-file"));
6113
 
 
6114
 
  change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
6115
 
  if (change->noderev_id == NULL)
6116
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6117
 
                            _("Invalid changes line in rev-file"));
6118
 
 
6119
 
  /* Get the change type. */
6120
 
  str = svn_cstring_tokenize(" ", &last_str);
6121
 
  if (str == NULL)
6122
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6123
 
                            _("Invalid changes line in rev-file"));
6124
 
 
6125
 
  /* Don't bother to check the format number before looking for
6126
 
   * node-kinds: just read them if you find them. */
6127
 
  change->node_kind = svn_node_unknown;
6128
 
  kind_str = strchr(str, '-');
6129
 
  if (kind_str)
6130
 
    {
6131
 
      /* Cap off the end of "str" (the action). */
6132
 
      *kind_str = '\0';
6133
 
      kind_str++;
6134
 
      if (strcmp(kind_str, KIND_FILE) == 0)
6135
 
        change->node_kind = svn_node_file;
6136
 
      else if (strcmp(kind_str, KIND_DIR) == 0)
6137
 
        change->node_kind = svn_node_dir;
6138
 
      else
6139
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6140
 
                                _("Invalid changes line in rev-file"));
6141
 
    }
6142
 
 
6143
 
  if (strcmp(str, ACTION_MODIFY) == 0)
6144
 
    {
6145
 
      change->kind = svn_fs_path_change_modify;
6146
 
    }
6147
 
  else if (strcmp(str, ACTION_ADD) == 0)
6148
 
    {
6149
 
      change->kind = svn_fs_path_change_add;
6150
 
    }
6151
 
  else if (strcmp(str, ACTION_DELETE) == 0)
6152
 
    {
6153
 
      change->kind = svn_fs_path_change_delete;
6154
 
    }
6155
 
  else if (strcmp(str, ACTION_REPLACE) == 0)
6156
 
    {
6157
 
      change->kind = svn_fs_path_change_replace;
6158
 
    }
6159
 
  else if (strcmp(str, ACTION_RESET) == 0)
6160
 
    {
6161
 
      change->kind = svn_fs_path_change_reset;
6162
 
    }
6163
 
  else
6164
 
    {
6165
 
      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6166
 
                              _("Invalid change kind in rev file"));
6167
 
    }
6168
 
 
6169
 
  /* Get the text-mod flag. */
6170
 
  str = svn_cstring_tokenize(" ", &last_str);
6171
 
  if (str == NULL)
6172
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6173
 
                            _("Invalid changes line in rev-file"));
6174
 
 
6175
 
  if (strcmp(str, FLAG_TRUE) == 0)
6176
 
    {
6177
 
      change->text_mod = TRUE;
6178
 
    }
6179
 
  else if (strcmp(str, FLAG_FALSE) == 0)
6180
 
    {
6181
 
      change->text_mod = FALSE;
6182
 
    }
6183
 
  else
6184
 
    {
6185
 
      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6186
 
                              _("Invalid text-mod flag in rev-file"));
6187
 
    }
6188
 
 
6189
 
  /* Get the prop-mod flag. */
6190
 
  str = svn_cstring_tokenize(" ", &last_str);
6191
 
  if (str == NULL)
6192
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6193
 
                            _("Invalid changes line in rev-file"));
6194
 
 
6195
 
  if (strcmp(str, FLAG_TRUE) == 0)
6196
 
    {
6197
 
      change->prop_mod = TRUE;
6198
 
    }
6199
 
  else if (strcmp(str, FLAG_FALSE) == 0)
6200
 
    {
6201
 
      change->prop_mod = FALSE;
6202
 
    }
6203
 
  else
6204
 
    {
6205
 
      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6206
 
                              _("Invalid prop-mod flag in rev-file"));
6207
 
    }
6208
 
 
6209
 
  /* Get the changed path. */
6210
 
  change->path = apr_pstrdup(pool, last_str);
6211
 
 
6212
 
 
6213
 
  /* Read the next line, the copyfrom line. */
6214
 
  len = sizeof(buf);
6215
 
  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
6216
 
 
6217
 
  if (len == 0)
6218
 
    {
6219
 
      change->copyfrom_rev = SVN_INVALID_REVNUM;
6220
 
      change->copyfrom_path = NULL;
6221
 
    }
6222
 
  else
6223
 
    {
6224
 
      last_str = buf;
6225
 
      str = svn_cstring_tokenize(" ", &last_str);
6226
 
      if (! str)
6227
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6228
 
                                _("Invalid changes line in rev-file"));
6229
 
      change->copyfrom_rev = SVN_STR_TO_REV(str);
6230
 
 
6231
 
      if (! last_str)
6232
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6233
 
                                _("Invalid changes line in rev-file"));
6234
 
 
6235
 
      change->copyfrom_path = apr_pstrdup(pool, last_str);
6236
 
    }
6237
 
 
6238
 
  *change_p = change;
6239
 
 
6240
 
  return SVN_NO_ERROR;
6241
 
}
6242
 
 
6243
 
/* Examine all the changed path entries in CHANGES and store them in
6244
 
   *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
6245
 
   *data.  Store a hash of paths to copyfrom "REV PATH" strings in
6246
 
   COPYFROM_HASH if it is non-NULL.  If PREFOLDED is true, assume that
6247
 
   the changed-path entries have already been folded (by
6248
 
   write_final_changed_path_info) and may be out of order, so we shouldn't
6249
 
   remove children of replaced or deleted directories.  Do all
6250
 
   allocations in POOL. */
6251
 
static svn_error_t *
6252
 
process_changes(apr_hash_t *changed_paths,
6253
 
                apr_hash_t *copyfrom_cache,
6254
 
                apr_array_header_t *changes,
6255
 
                svn_boolean_t prefolded,
6256
 
                apr_pool_t *pool)
6257
 
{
6258
 
  apr_pool_t *iterpool = svn_pool_create(pool);
6259
 
  int i;
6260
 
 
6261
 
  /* Read in the changes one by one, folding them into our local hash
6262
 
     as necessary. */
6263
 
 
6264
 
  for (i = 0; i < changes->nelts; ++i)
6265
 
    {
6266
 
      change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
6267
 
 
6268
 
      SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
6269
 
 
6270
 
      /* Now, if our change was a deletion or replacement, we have to
6271
 
         blow away any changes thus far on paths that are (or, were)
6272
 
         children of this path.
6273
 
         ### i won't bother with another iteration pool here -- at
6274
 
         most we talking about a few extra dups of paths into what
6275
 
         is already a temporary subpool.
6276
 
      */
6277
 
 
6278
 
      if (((change->kind == svn_fs_path_change_delete)
6279
 
           || (change->kind == svn_fs_path_change_replace))
6280
 
          && ! prefolded)
6281
 
        {
6282
 
          apr_hash_index_t *hi;
6283
 
 
6284
 
          /* a potential child path must contain at least 2 more chars
6285
 
             (the path separator plus at least one char for the name).
6286
 
             Also, we should not assume that all paths have been normalized
6287
 
             i.e. some might have trailing path separators.
6288
 
          */
6289
 
          apr_ssize_t change_path_len = strlen(change->path);
6290
 
          apr_ssize_t min_child_len = change_path_len == 0
6291
 
                                    ? 1
6292
 
                                    : change->path[change_path_len-1] == '/'
6293
 
                                        ? change_path_len + 1
6294
 
                                        : change_path_len + 2;
6295
 
 
6296
 
          /* CAUTION: This is the inner loop of an O(n^2) algorithm.
6297
 
             The number of changes to process may be >> 1000.
6298
 
             Therefore, keep the inner loop as tight as possible.
6299
 
          */
6300
 
          for (hi = apr_hash_first(iterpool, changed_paths);
6301
 
               hi;
6302
 
               hi = apr_hash_next(hi))
6303
 
            {
6304
 
              /* KEY is the path. */
6305
 
              const void *path;
6306
 
              apr_ssize_t klen;
6307
 
              apr_hash_this(hi, &path, &klen, NULL);
6308
 
 
6309
 
              /* If we come across a child of our path, remove it.
6310
 
                 Call svn_dirent_is_child only if there is a chance that
6311
 
                 this is actually a sub-path.
6312
 
               */
6313
 
              if (   klen >= min_child_len
6314
 
                  && svn_dirent_is_child(change->path, path, iterpool))
6315
 
                apr_hash_set(changed_paths, path, klen, NULL);
6316
 
            }
6317
 
        }
6318
 
 
6319
 
      /* Clear the per-iteration subpool. */
6320
 
      svn_pool_clear(iterpool);
6321
 
    }
6322
 
 
6323
 
  /* Destroy the per-iteration subpool. */
6324
 
  svn_pool_destroy(iterpool);
6325
 
 
6326
 
  return SVN_NO_ERROR;
6327
 
}
6328
 
 
6329
 
/* Fetch all the changes from FILE and store them in *CHANGES.  Do all
6330
 
   allocations in POOL. */
6331
 
static svn_error_t *
6332
 
read_all_changes(apr_array_header_t **changes,
6333
 
                 apr_file_t *file,
6334
 
                 apr_pool_t *pool)
6335
 
{
6336
 
  change_t *change;
6337
 
 
6338
 
  /* pre-allocate enough room for most change lists
6339
 
     (will be auto-expanded as necessary) */
6340
 
  *changes = apr_array_make(pool, 30, sizeof(change_t *));
6341
 
 
6342
 
  SVN_ERR(read_change(&change, file, pool));
6343
 
  while (change)
6344
 
    {
6345
 
      APR_ARRAY_PUSH(*changes, change_t*) = change;
6346
 
      SVN_ERR(read_change(&change, file, pool));
6347
 
    }
6348
 
 
6349
 
  return SVN_NO_ERROR;
6350
 
}
6351
 
 
6352
 
svn_error_t *
6353
 
svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
6354
 
                             svn_fs_t *fs,
6355
 
                             const char *txn_id,
6356
 
                             apr_pool_t *pool)
6357
 
{
6358
 
  apr_file_t *file;
6359
 
  apr_hash_t *changed_paths = apr_hash_make(pool);
6360
 
  apr_array_header_t *changes;
6361
 
  apr_pool_t *scratch_pool = svn_pool_create(pool);
6362
 
 
6363
 
  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
6364
 
                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6365
 
 
6366
 
  SVN_ERR(read_all_changes(&changes, file, scratch_pool));
6367
 
  SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
6368
 
  svn_pool_destroy(scratch_pool);
6369
 
 
6370
 
  SVN_ERR(svn_io_file_close(file, pool));
6371
 
 
6372
 
  *changed_paths_p = changed_paths;
6373
 
 
6374
 
  return SVN_NO_ERROR;
6375
 
}
6376
 
 
6377
 
/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
6378
 
 * Allocate the result in POOL.
6379
 
 */
6380
 
static svn_error_t *
6381
 
get_changes(apr_array_header_t **changes,
6382
 
            svn_fs_t *fs,
6383
 
            svn_revnum_t rev,
6384
 
            apr_pool_t *pool)
6385
 
{
6386
 
  apr_off_t changes_offset;
6387
 
  apr_file_t *revision_file;
6388
 
  svn_boolean_t found;
6389
 
  fs_fs_data_t *ffd = fs->fsap_data;
6390
 
 
6391
 
  /* try cache lookup first */
6392
 
 
6393
 
  if (ffd->changes_cache)
6394
 
    {
6395
 
      SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
6396
 
                             &rev, pool));
6397
 
      if (found)
6398
 
        return SVN_NO_ERROR;
6399
 
    }
6400
 
 
6401
 
  /* read changes from revision file */
6402
 
 
6403
 
  SVN_ERR(ensure_revision_exists(fs, rev, pool));
6404
 
 
6405
 
  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
6406
 
 
6407
 
  SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
6408
 
                                  rev, pool));
6409
 
 
6410
 
  SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
6411
 
  SVN_ERR(read_all_changes(changes, revision_file, pool));
6412
 
 
6413
 
  SVN_ERR(svn_io_file_close(revision_file, pool));
6414
 
 
6415
 
  /* cache for future reference */
6416
 
 
6417
 
  if (ffd->changes_cache)
6418
 
    SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
6419
 
 
6420
 
  return SVN_NO_ERROR;
6421
 
}
6422
 
 
6423
 
 
6424
 
svn_error_t *
6425
 
svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
6426
 
                         svn_fs_t *fs,
6427
 
                         svn_revnum_t rev,
6428
 
                         apr_hash_t *copyfrom_cache,
6429
 
                         apr_pool_t *pool)
6430
 
{
6431
 
  apr_hash_t *changed_paths;
6432
 
  apr_array_header_t *changes;
6433
 
  apr_pool_t *scratch_pool = svn_pool_create(pool);
6434
 
 
6435
 
  SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
6436
 
 
6437
 
  changed_paths = svn_hash__make(pool);
6438
 
 
6439
 
  SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
6440
 
                          TRUE, pool));
6441
 
  svn_pool_destroy(scratch_pool);
6442
 
 
6443
 
  *changed_paths_p = changed_paths;
6444
 
 
6445
 
  return SVN_NO_ERROR;
6446
 
}
6447
 
 
6448
 
/* Copy a revision node-rev SRC into the current transaction TXN_ID in
6449
 
   the filesystem FS.  This is only used to create the root of a transaction.
6450
 
   Allocations are from POOL.  */
6451
 
static svn_error_t *
6452
 
create_new_txn_noderev_from_rev(svn_fs_t *fs,
6453
 
                                const char *txn_id,
6454
 
                                svn_fs_id_t *src,
6455
 
                                apr_pool_t *pool)
6456
 
{
6457
 
  node_revision_t *noderev;
6458
 
  const char *node_id, *copy_id;
6459
 
 
6460
 
  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
6461
 
 
6462
 
  if (svn_fs_fs__id_txn_id(noderev->id))
6463
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6464
 
                            _("Copying from transactions not allowed"));
6465
 
 
6466
 
  noderev->predecessor_id = noderev->id;
6467
 
  noderev->predecessor_count++;
6468
 
  noderev->copyfrom_path = NULL;
6469
 
  noderev->copyfrom_rev = SVN_INVALID_REVNUM;
6470
 
 
6471
 
  /* For the transaction root, the copyroot never changes. */
6472
 
 
6473
 
  node_id = svn_fs_fs__id_node_id(noderev->id);
6474
 
  copy_id = svn_fs_fs__id_copy_id(noderev->id);
6475
 
  noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6476
 
 
6477
 
  return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
6478
 
}
6479
 
 
6480
 
/* A structure used by get_and_increment_txn_key_body(). */
6481
 
struct get_and_increment_txn_key_baton {
6482
 
  svn_fs_t *fs;
6483
 
  char *txn_id;
6484
 
  apr_pool_t *pool;
6485
 
};
6486
 
 
6487
 
/* Callback used in the implementation of create_txn_dir().  This gets
6488
 
   the current base 36 value in PATH_TXN_CURRENT and increments it.
6489
 
   It returns the original value by the baton. */
6490
 
static svn_error_t *
6491
 
get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
6492
 
{
6493
 
  struct get_and_increment_txn_key_baton *cb = baton;
6494
 
  const char *txn_current_filename = path_txn_current(cb->fs, pool);
6495
 
  const char *tmp_filename;
6496
 
  char next_txn_id[MAX_KEY_SIZE+3];
6497
 
  apr_size_t len;
6498
 
 
6499
 
  svn_stringbuf_t *buf;
6500
 
  SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
6501
 
 
6502
 
  /* remove trailing newlines */
6503
 
  svn_stringbuf_strip_whitespace(buf);
6504
 
  cb->txn_id = buf->data;
6505
 
  len = buf->len;
6506
 
 
6507
 
  /* Increment the key and add a trailing \n to the string so the
6508
 
     txn-current file has a newline in it. */
6509
 
  svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
6510
 
  next_txn_id[len] = '\n';
6511
 
  ++len;
6512
 
  next_txn_id[len] = '\0';
6513
 
 
6514
 
  SVN_ERR(svn_io_write_unique(&tmp_filename,
6515
 
                              svn_dirent_dirname(txn_current_filename, pool),
6516
 
                              next_txn_id, len, svn_io_file_del_none, pool));
6517
 
  SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
6518
 
                          txn_current_filename, pool));
6519
 
 
6520
 
  return SVN_NO_ERROR;
6521
 
}
6522
 
 
6523
 
/* Create a unique directory for a transaction in FS based on revision
6524
 
   REV.  Return the ID for this transaction in *ID_P.  Use a sequence
6525
 
   value in the transaction ID to prevent reuse of transaction IDs. */
6526
 
static svn_error_t *
6527
 
create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6528
 
               apr_pool_t *pool)
6529
 
{
6530
 
  struct get_and_increment_txn_key_baton cb;
6531
 
  const char *txn_dir;
6532
 
 
6533
 
  /* Get the current transaction sequence value, which is a base-36
6534
 
     number, from the txn-current file, and write an
6535
 
     incremented value back out to the file.  Place the revision
6536
 
     number the transaction is based off into the transaction id. */
6537
 
  cb.pool = pool;
6538
 
  cb.fs = fs;
6539
 
  SVN_ERR(with_txn_current_lock(fs,
6540
 
                                get_and_increment_txn_key_body,
6541
 
                                &cb,
6542
 
                                pool));
6543
 
  *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
6544
 
 
6545
 
  txn_dir = svn_dirent_join_many(pool,
6546
 
                                 fs->path,
6547
 
                                 PATH_TXNS_DIR,
6548
 
                                 apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
6549
 
                                             (char *)NULL),
6550
 
                                 NULL);
6551
 
 
6552
 
  return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
6553
 
}
6554
 
 
6555
 
/* Create a unique directory for a transaction in FS based on revision
6556
 
   REV.  Return the ID for this transaction in *ID_P.  This
6557
 
   implementation is used in svn 1.4 and earlier repositories and is
6558
 
   kept in 1.5 and greater to support the --pre-1.4-compatible and
6559
 
   --pre-1.5-compatible repository creation options.  Reused
6560
 
   transaction IDs are possible with this implementation. */
6561
 
static svn_error_t *
6562
 
create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6563
 
                       apr_pool_t *pool)
6564
 
{
6565
 
  unsigned int i;
6566
 
  apr_pool_t *subpool;
6567
 
  const char *unique_path, *prefix;
6568
 
 
6569
 
  /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
6570
 
  prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
6571
 
                                apr_psprintf(pool, "%ld", rev), NULL);
6572
 
 
6573
 
  subpool = svn_pool_create(pool);
6574
 
  for (i = 1; i <= 99999; i++)
6575
 
    {
6576
 
      svn_error_t *err;
6577
 
 
6578
 
      svn_pool_clear(subpool);
6579
 
      unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
6580
 
      err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
6581
 
      if (! err)
6582
 
        {
6583
 
          /* We succeeded.  Return the basename minus the ".txn" extension. */
6584
 
          const char *name = svn_dirent_basename(unique_path, subpool);
6585
 
          *id_p = apr_pstrndup(pool, name,
6586
 
                               strlen(name) - strlen(PATH_EXT_TXN));
6587
 
          svn_pool_destroy(subpool);
6588
 
          return SVN_NO_ERROR;
6589
 
        }
6590
 
      if (! APR_STATUS_IS_EEXIST(err->apr_err))
6591
 
        return svn_error_trace(err);
6592
 
      svn_error_clear(err);
6593
 
    }
6594
 
 
6595
 
  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
6596
 
                           NULL,
6597
 
                           _("Unable to create transaction directory "
6598
 
                             "in '%s' for revision %ld"),
6599
 
                           svn_dirent_local_style(fs->path, pool),
6600
 
                           rev);
6601
 
}
6602
 
 
6603
 
svn_error_t *
6604
 
svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
6605
 
                      svn_fs_t *fs,
6606
 
                      svn_revnum_t rev,
6607
 
                      apr_pool_t *pool)
6608
 
{
6609
 
  fs_fs_data_t *ffd = fs->fsap_data;
6610
 
  svn_fs_txn_t *txn;
6611
 
  svn_fs_id_t *root_id;
6612
 
 
6613
 
  txn = apr_pcalloc(pool, sizeof(*txn));
6614
 
 
6615
 
  /* Get the txn_id. */
6616
 
  if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
6617
 
    SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
6618
 
  else
6619
 
    SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
6620
 
 
6621
 
  txn->fs = fs;
6622
 
  txn->base_rev = rev;
6623
 
 
6624
 
  txn->vtable = &txn_vtable;
6625
 
  *txn_p = txn;
6626
 
 
6627
 
  /* Create a new root node for this transaction. */
6628
 
  SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
6629
 
  SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
6630
 
 
6631
 
  /* Create an empty rev file. */
6632
 
  SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
6633
 
                             pool));
6634
 
 
6635
 
  /* Create an empty rev-lock file. */
6636
 
  SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
6637
 
                             pool));
6638
 
 
6639
 
  /* Create an empty changes file. */
6640
 
  SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
6641
 
                             pool));
6642
 
 
6643
 
  /* Create the next-ids file. */
6644
 
  return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
6645
 
                            pool);
6646
 
}
6647
 
 
6648
 
/* Store the property list for transaction TXN_ID in PROPLIST.
6649
 
   Perform temporary allocations in POOL. */
6650
 
static svn_error_t *
6651
 
get_txn_proplist(apr_hash_t *proplist,
6652
 
                 svn_fs_t *fs,
6653
 
                 const char *txn_id,
6654
 
                 apr_pool_t *pool)
6655
 
{
6656
 
  svn_stream_t *stream;
6657
 
 
6658
 
  /* Check for issue #3696. (When we find and fix the cause, we can change
6659
 
   * this to an assertion.) */
6660
 
  if (txn_id == NULL)
6661
 
    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
6662
 
                            _("Internal error: a null transaction id was "
6663
 
                              "passed to get_txn_proplist()"));
6664
 
 
6665
 
  /* Open the transaction properties file. */
6666
 
  SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
6667
 
                                   pool, pool));
6668
 
 
6669
 
  /* Read in the property list. */
6670
 
  SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
6671
 
 
6672
 
  return svn_stream_close(stream);
6673
 
}
6674
 
 
6675
 
svn_error_t *
6676
 
svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
6677
 
                           const char *name,
6678
 
                           const svn_string_t *value,
6679
 
                           apr_pool_t *pool)
6680
 
{
6681
 
  apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
6682
 
  svn_prop_t prop;
6683
 
 
6684
 
  prop.name = name;
6685
 
  prop.value = value;
6686
 
  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6687
 
 
6688
 
  return svn_fs_fs__change_txn_props(txn, props, pool);
6689
 
}
6690
 
 
6691
 
svn_error_t *
6692
 
svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
6693
 
                            const apr_array_header_t *props,
6694
 
                            apr_pool_t *pool)
6695
 
{
6696
 
  const char *txn_prop_filename;
6697
 
  svn_stringbuf_t *buf;
6698
 
  svn_stream_t *stream;
6699
 
  apr_hash_t *txn_prop = apr_hash_make(pool);
6700
 
  int i;
6701
 
  svn_error_t *err;
6702
 
 
6703
 
  err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
6704
 
  /* Here - and here only - we need to deal with the possibility that the
6705
 
     transaction property file doesn't yet exist.  The rest of the
6706
 
     implementation assumes that the file exists, but we're called to set the
6707
 
     initial transaction properties as the transaction is being created. */
6708
 
  if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
6709
 
    svn_error_clear(err);
6710
 
  else if (err)
6711
 
    return svn_error_trace(err);
6712
 
 
6713
 
  for (i = 0; i < props->nelts; i++)
6714
 
    {
6715
 
      svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
6716
 
 
6717
 
      svn_hash_sets(txn_prop, prop->name, prop->value);
6718
 
    }
6719
 
 
6720
 
  /* Create a new version of the file and write out the new props. */
6721
 
  /* Open the transaction properties file. */
6722
 
  buf = svn_stringbuf_create_ensure(1024, pool);
6723
 
  stream = svn_stream_from_stringbuf(buf, pool);
6724
 
  SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
6725
 
  SVN_ERR(svn_stream_close(stream));
6726
 
  SVN_ERR(svn_io_write_unique(&txn_prop_filename,
6727
 
                              path_txn_dir(txn->fs, txn->id, pool),
6728
 
                              buf->data,
6729
 
                              buf->len,
6730
 
                              svn_io_file_del_none,
6731
 
                              pool));
6732
 
  return svn_io_file_rename(txn_prop_filename,
6733
 
                            path_txn_props(txn->fs, txn->id, pool),
6734
 
                            pool);
6735
 
}
6736
 
 
6737
 
svn_error_t *
6738
 
svn_fs_fs__get_txn(transaction_t **txn_p,
6739
 
                   svn_fs_t *fs,
6740
 
                   const char *txn_id,
6741
 
                   apr_pool_t *pool)
6742
 
{
6743
 
  transaction_t *txn;
6744
 
  node_revision_t *noderev;
6745
 
  svn_fs_id_t *root_id;
6746
 
 
6747
 
  txn = apr_pcalloc(pool, sizeof(*txn));
6748
 
  txn->proplist = apr_hash_make(pool);
6749
 
 
6750
 
  SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
6751
 
  root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
6752
 
 
6753
 
  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
6754
 
 
6755
 
  txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
6756
 
  txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
6757
 
  txn->copies = NULL;
6758
 
 
6759
 
  *txn_p = txn;
6760
 
 
6761
 
  return SVN_NO_ERROR;
6762
 
}
6763
 
 
6764
 
/* Write out the currently available next node_id NODE_ID and copy_id
6765
 
   COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
6766
 
   used both for creating new unique nodes for the given transaction, as
6767
 
   well as uniquifying representations.  Perform temporary allocations in
6768
 
   POOL. */
6769
 
static svn_error_t *
6770
 
write_next_ids(svn_fs_t *fs,
6771
 
               const char *txn_id,
6772
 
               const char *node_id,
6773
 
               const char *copy_id,
6774
 
               apr_pool_t *pool)
6775
 
{
6776
 
  apr_file_t *file;
6777
 
  svn_stream_t *out_stream;
6778
 
 
6779
 
  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6780
 
                           APR_WRITE | APR_TRUNCATE,
6781
 
                           APR_OS_DEFAULT, pool));
6782
 
 
6783
 
  out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
6784
 
 
6785
 
  SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
6786
 
 
6787
 
  SVN_ERR(svn_stream_close(out_stream));
6788
 
  return svn_io_file_close(file, pool);
6789
 
}
6790
 
 
6791
 
/* Find out what the next unique node-id and copy-id are for
6792
 
   transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
6793
 
   and *COPY_ID.  The next node-id is used both for creating new unique
6794
 
   nodes for the given transaction, as well as uniquifying representations.
6795
 
   Perform all allocations in POOL. */
6796
 
static svn_error_t *
6797
 
read_next_ids(const char **node_id,
6798
 
              const char **copy_id,
6799
 
              svn_fs_t *fs,
6800
 
              const char *txn_id,
6801
 
              apr_pool_t *pool)
6802
 
{
6803
 
  apr_file_t *file;
6804
 
  char buf[MAX_KEY_SIZE*2+3];
6805
 
  apr_size_t limit;
6806
 
  char *str, *last_str = buf;
6807
 
 
6808
 
  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6809
 
                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6810
 
 
6811
 
  limit = sizeof(buf);
6812
 
  SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
6813
 
 
6814
 
  SVN_ERR(svn_io_file_close(file, pool));
6815
 
 
6816
 
  /* Parse this into two separate strings. */
6817
 
 
6818
 
  str = svn_cstring_tokenize(" ", &last_str);
6819
 
  if (! str)
6820
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6821
 
                            _("next-id file corrupt"));
6822
 
 
6823
 
  *node_id = apr_pstrdup(pool, str);
6824
 
 
6825
 
  str = svn_cstring_tokenize(" ", &last_str);
6826
 
  if (! str)
6827
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6828
 
                            _("next-id file corrupt"));
6829
 
 
6830
 
  *copy_id = apr_pstrdup(pool, str);
6831
 
 
6832
 
  return SVN_NO_ERROR;
6833
 
}
6834
 
 
6835
 
/* Get a new and unique to this transaction node-id for transaction
6836
 
   TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
6837
 
   Node-ids are guaranteed to be unique to this transction, but may
6838
 
   not necessarily be sequential.  Perform all allocations in POOL. */
6839
 
static svn_error_t *
6840
 
get_new_txn_node_id(const char **node_id_p,
6841
 
                    svn_fs_t *fs,
6842
 
                    const char *txn_id,
6843
 
                    apr_pool_t *pool)
6844
 
{
6845
 
  const char *cur_node_id, *cur_copy_id;
6846
 
  char *node_id;
6847
 
  apr_size_t len;
6848
 
 
6849
 
  /* First read in the current next-ids file. */
6850
 
  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
6851
 
 
6852
 
  node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
6853
 
 
6854
 
  len = strlen(cur_node_id);
6855
 
  svn_fs_fs__next_key(cur_node_id, &len, node_id);
6856
 
 
6857
 
  SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
6858
 
 
6859
 
  *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
6860
 
 
6861
 
  return SVN_NO_ERROR;
6862
 
}
6863
 
 
6864
 
svn_error_t *
6865
 
svn_fs_fs__create_node(const svn_fs_id_t **id_p,
6866
 
                       svn_fs_t *fs,
6867
 
                       node_revision_t *noderev,
6868
 
                       const char *copy_id,
6869
 
                       const char *txn_id,
6870
 
                       apr_pool_t *pool)
6871
 
{
6872
 
  const char *node_id;
6873
 
  const svn_fs_id_t *id;
6874
 
 
6875
 
  /* Get a new node-id for this node. */
6876
 
  SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
6877
 
 
6878
 
  id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6879
 
 
6880
 
  noderev->id = id;
6881
 
 
6882
 
  SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
6883
 
 
6884
 
  *id_p = id;
6885
 
 
6886
 
  return SVN_NO_ERROR;
6887
 
}
6888
 
 
6889
 
svn_error_t *
6890
 
svn_fs_fs__purge_txn(svn_fs_t *fs,
6891
 
                     const char *txn_id,
6892
 
                     apr_pool_t *pool)
6893
 
{
6894
 
  fs_fs_data_t *ffd = fs->fsap_data;
6895
 
 
6896
 
  /* Remove the shared transaction object associated with this transaction. */
6897
 
  SVN_ERR(purge_shared_txn(fs, txn_id, pool));
6898
 
  /* Remove the directory associated with this transaction. */
6899
 
  SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
6900
 
                             NULL, NULL, pool));
6901
 
  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
6902
 
    {
6903
 
      /* Delete protorev and its lock, which aren't in the txn
6904
 
         directory.  It's OK if they don't exist (for example, if this
6905
 
         is post-commit and the proto-rev has been moved into
6906
 
         place). */
6907
 
      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
6908
 
                                  TRUE, pool));
6909
 
      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
6910
 
                                  TRUE, pool));
6911
 
    }
6912
 
  return SVN_NO_ERROR;
6913
 
}
6914
 
 
6915
 
 
6916
 
svn_error_t *
6917
 
svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
6918
 
                     apr_pool_t *pool)
6919
 
{
6920
 
  SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
6921
 
 
6922
 
  /* Now, purge the transaction. */
6923
 
  SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
6924
 
            apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
6925
 
                         txn->id));
6926
 
 
6927
 
  return SVN_NO_ERROR;
6928
 
}
6929
 
 
6930
 
 
6931
 
svn_error_t *
6932
 
svn_fs_fs__set_entry(svn_fs_t *fs,
6933
 
                     const char *txn_id,
6934
 
                     node_revision_t *parent_noderev,
6935
 
                     const char *name,
6936
 
                     const svn_fs_id_t *id,
6937
 
                     svn_node_kind_t kind,
6938
 
                     apr_pool_t *pool)
6939
 
{
6940
 
  representation_t *rep = parent_noderev->data_rep;
6941
 
  const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
6942
 
  apr_file_t *file;
6943
 
  svn_stream_t *out;
6944
 
  fs_fs_data_t *ffd = fs->fsap_data;
6945
 
  apr_pool_t *subpool = svn_pool_create(pool);
6946
 
 
6947
 
  if (!rep || !rep->txn_id)
6948
 
    {
6949
 
      const char *unique_suffix;
6950
 
      apr_hash_t *entries;
6951
 
 
6952
 
      /* Before we can modify the directory, we need to dump its old
6953
 
         contents into a mutable representation file. */
6954
 
      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
6955
 
                                          subpool));
6956
 
      SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
6957
 
      SVN_ERR(svn_io_file_open(&file, filename,
6958
 
                               APR_WRITE | APR_CREATE | APR_BUFFERED,
6959
 
                               APR_OS_DEFAULT, pool));
6960
 
      out = svn_stream_from_aprfile2(file, TRUE, pool);
6961
 
      SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
6962
 
 
6963
 
      svn_pool_clear(subpool);
6964
 
 
6965
 
      /* Mark the node-rev's data rep as mutable. */
6966
 
      rep = apr_pcalloc(pool, sizeof(*rep));
6967
 
      rep->revision = SVN_INVALID_REVNUM;
6968
 
      rep->txn_id = txn_id;
6969
 
 
6970
 
      if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
6971
 
        {
6972
 
          SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
6973
 
          rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
6974
 
        }
6975
 
 
6976
 
      parent_noderev->data_rep = rep;
6977
 
      SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
6978
 
                                           parent_noderev, FALSE, pool));
6979
 
    }
6980
 
  else
6981
 
    {
6982
 
      /* The directory rep is already mutable, so just open it for append. */
6983
 
      SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
6984
 
                               APR_OS_DEFAULT, pool));
6985
 
      out = svn_stream_from_aprfile2(file, TRUE, pool);
6986
 
    }
6987
 
 
6988
 
  /* if we have a directory cache for this transaction, update it */
6989
 
  if (ffd->txn_dir_cache)
6990
 
    {
6991
 
      /* build parameters: (name, new entry) pair */
6992
 
      const char *key =
6993
 
          svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
6994
 
      replace_baton_t baton;
6995
 
 
6996
 
      baton.name = name;
6997
 
      baton.new_entry = NULL;
6998
 
 
6999
 
      if (id)
7000
 
        {
7001
 
          baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
7002
 
          baton.new_entry->name = name;
7003
 
          baton.new_entry->kind = kind;
7004
 
          baton.new_entry->id = id;
7005
 
        }
7006
 
 
7007
 
      /* actually update the cached directory (if cached) */
7008
 
      SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
7009
 
                                     svn_fs_fs__replace_dir_entry, &baton,
7010
 
                                     subpool));
7011
 
    }
7012
 
  svn_pool_clear(subpool);
7013
 
 
7014
 
  /* Append an incremental hash entry for the entry change. */
7015
 
  if (id)
7016
 
    {
7017
 
      const char *val = unparse_dir_entry(kind, id, subpool);
7018
 
 
7019
 
      SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
7020
 
                                "V %" APR_SIZE_T_FMT "\n%s\n",
7021
 
                                strlen(name), name,
7022
 
                                strlen(val), val));
7023
 
    }
7024
 
  else
7025
 
    {
7026
 
      SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
7027
 
                                strlen(name), name));
7028
 
    }
7029
 
 
7030
 
  SVN_ERR(svn_io_file_close(file, subpool));
7031
 
  svn_pool_destroy(subpool);
7032
 
  return SVN_NO_ERROR;
7033
 
}
7034
 
 
7035
 
/* Write a single change entry, path PATH, change CHANGE, and copyfrom
7036
 
   string COPYFROM, into the file specified by FILE.  Only include the
7037
 
   node kind field if INCLUDE_NODE_KIND is true.  All temporary
7038
 
   allocations are in POOL. */
7039
 
static svn_error_t *
7040
 
write_change_entry(apr_file_t *file,
7041
 
                   const char *path,
7042
 
                   svn_fs_path_change2_t *change,
7043
 
                   svn_boolean_t include_node_kind,
7044
 
                   apr_pool_t *pool)
7045
 
{
7046
 
  const char *idstr, *buf;
7047
 
  const char *change_string = NULL;
7048
 
  const char *kind_string = "";
7049
 
 
7050
 
  switch (change->change_kind)
7051
 
    {
7052
 
    case svn_fs_path_change_modify:
7053
 
      change_string = ACTION_MODIFY;
7054
 
      break;
7055
 
    case svn_fs_path_change_add:
7056
 
      change_string = ACTION_ADD;
7057
 
      break;
7058
 
    case svn_fs_path_change_delete:
7059
 
      change_string = ACTION_DELETE;
7060
 
      break;
7061
 
    case svn_fs_path_change_replace:
7062
 
      change_string = ACTION_REPLACE;
7063
 
      break;
7064
 
    case svn_fs_path_change_reset:
7065
 
      change_string = ACTION_RESET;
7066
 
      break;
7067
 
    default:
7068
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7069
 
                               _("Invalid change type %d"),
7070
 
                               change->change_kind);
7071
 
    }
7072
 
 
7073
 
  if (change->node_rev_id)
7074
 
    idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
7075
 
  else
7076
 
    idstr = ACTION_RESET;
7077
 
 
7078
 
  if (include_node_kind)
7079
 
    {
7080
 
      SVN_ERR_ASSERT(change->node_kind == svn_node_dir
7081
 
                     || change->node_kind == svn_node_file);
7082
 
      kind_string = apr_psprintf(pool, "-%s",
7083
 
                                 change->node_kind == svn_node_dir
7084
 
                                 ? KIND_DIR : KIND_FILE);
7085
 
    }
7086
 
  buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
7087
 
                     idstr, change_string, kind_string,
7088
 
                     change->text_mod ? FLAG_TRUE : FLAG_FALSE,
7089
 
                     change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
7090
 
                     path);
7091
 
 
7092
 
  SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7093
 
 
7094
 
  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
7095
 
    {
7096
 
      buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
7097
 
                         change->copyfrom_path);
7098
 
      SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7099
 
    }
7100
 
 
7101
 
  return svn_io_file_write_full(file, "\n", 1, NULL, pool);
7102
 
}
7103
 
 
7104
 
svn_error_t *
7105
 
svn_fs_fs__add_change(svn_fs_t *fs,
7106
 
                      const char *txn_id,
7107
 
                      const char *path,
7108
 
                      const svn_fs_id_t *id,
7109
 
                      svn_fs_path_change_kind_t change_kind,
7110
 
                      svn_boolean_t text_mod,
7111
 
                      svn_boolean_t prop_mod,
7112
 
                      svn_node_kind_t node_kind,
7113
 
                      svn_revnum_t copyfrom_rev,
7114
 
                      const char *copyfrom_path,
7115
 
                      apr_pool_t *pool)
7116
 
{
7117
 
  apr_file_t *file;
7118
 
  svn_fs_path_change2_t *change;
7119
 
 
7120
 
  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
7121
 
                           APR_APPEND | APR_WRITE | APR_CREATE
7122
 
                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7123
 
 
7124
 
  change = svn_fs__path_change_create_internal(id, change_kind, pool);
7125
 
  change->text_mod = text_mod;
7126
 
  change->prop_mod = prop_mod;
7127
 
  change->node_kind = node_kind;
7128
 
  change->copyfrom_rev = copyfrom_rev;
7129
 
  change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
7130
 
 
7131
 
  SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
7132
 
 
7133
 
  return svn_io_file_close(file, pool);
7134
 
}
7135
 
 
7136
 
/* This baton is used by the representation writing streams.  It keeps
7137
 
   track of the checksum information as well as the total size of the
7138
 
   representation so far. */
7139
 
struct rep_write_baton
7140
 
{
7141
 
  /* The FS we are writing to. */
7142
 
  svn_fs_t *fs;
7143
 
 
7144
 
  /* Actual file to which we are writing. */
7145
 
  svn_stream_t *rep_stream;
7146
 
 
7147
 
  /* A stream from the delta combiner.  Data written here gets
7148
 
     deltified, then eventually written to rep_stream. */
7149
 
  svn_stream_t *delta_stream;
7150
 
 
7151
 
  /* Where is this representation header stored. */
7152
 
  apr_off_t rep_offset;
7153
 
 
7154
 
  /* Start of the actual data. */
7155
 
  apr_off_t delta_start;
7156
 
 
7157
 
  /* How many bytes have been written to this rep already. */
7158
 
  svn_filesize_t rep_size;
7159
 
 
7160
 
  /* The node revision for which we're writing out info. */
7161
 
  node_revision_t *noderev;
7162
 
 
7163
 
  /* Actual output file. */
7164
 
  apr_file_t *file;
7165
 
  /* Lock 'cookie' used to unlock the output file once we've finished
7166
 
     writing to it. */
7167
 
  void *lockcookie;
7168
 
 
7169
 
  svn_checksum_ctx_t *md5_checksum_ctx;
7170
 
  svn_checksum_ctx_t *sha1_checksum_ctx;
7171
 
 
7172
 
  apr_pool_t *pool;
7173
 
 
7174
 
  apr_pool_t *parent_pool;
7175
 
};
7176
 
 
7177
 
/* Handler for the write method of the representation writable stream.
7178
 
   BATON is a rep_write_baton, DATA is the data to write, and *LEN is
7179
 
   the length of this data. */
7180
 
static svn_error_t *
7181
 
rep_write_contents(void *baton,
7182
 
                   const char *data,
7183
 
                   apr_size_t *len)
7184
 
{
7185
 
  struct rep_write_baton *b = baton;
7186
 
 
7187
 
  SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
7188
 
  SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
7189
 
  b->rep_size += *len;
7190
 
 
7191
 
  /* If we are writing a delta, use that stream. */
7192
 
  if (b->delta_stream)
7193
 
    return svn_stream_write(b->delta_stream, data, len);
7194
 
  else
7195
 
    return svn_stream_write(b->rep_stream, data, len);
7196
 
}
7197
 
 
7198
 
/* Given a node-revision NODEREV in filesystem FS, return the
7199
 
   representation in *REP to use as the base for a text representation
7200
 
   delta if PROPS is FALSE.  If PROPS has been set, a suitable props
7201
 
   base representation will be returned.  Perform temporary allocations
7202
 
   in *POOL. */
7203
 
static svn_error_t *
7204
 
choose_delta_base(representation_t **rep,
7205
 
                  svn_fs_t *fs,
7206
 
                  node_revision_t *noderev,
7207
 
                  svn_boolean_t props,
7208
 
                  apr_pool_t *pool)
7209
 
{
7210
 
  int count;
7211
 
  int walk;
7212
 
  node_revision_t *base;
7213
 
  fs_fs_data_t *ffd = fs->fsap_data;
7214
 
  svn_boolean_t maybe_shared_rep = FALSE;
7215
 
 
7216
 
  /* If we have no predecessors, then use the empty stream as a
7217
 
     base. */
7218
 
  if (! noderev->predecessor_count)
7219
 
    {
7220
 
      *rep = NULL;
7221
 
      return SVN_NO_ERROR;
7222
 
    }
7223
 
 
7224
 
  /* Flip the rightmost '1' bit of the predecessor count to determine
7225
 
     which file rev (counting from 0) we want to use.  (To see why
7226
 
     count & (count - 1) unsets the rightmost set bit, think about how
7227
 
     you decrement a binary number.) */
7228
 
  count = noderev->predecessor_count;
7229
 
  count = count & (count - 1);
7230
 
 
7231
 
  /* We use skip delta for limiting the number of delta operations
7232
 
     along very long node histories.  Close to HEAD however, we create
7233
 
     a linear history to minimize delta size.  */
7234
 
  walk = noderev->predecessor_count - count;
7235
 
  if (walk < (int)ffd->max_linear_deltification)
7236
 
    count = noderev->predecessor_count - 1;
7237
 
 
7238
 
  /* Finding the delta base over a very long distance can become extremely
7239
 
     expensive for very deep histories, possibly causing client timeouts etc.
7240
 
     OTOH, this is a rare operation and its gains are minimal. Lets simply
7241
 
     start deltification anew close every other 1000 changes or so.  */
7242
 
  if (walk > (int)ffd->max_deltification_walk)
7243
 
    {
7244
 
      *rep = NULL;
7245
 
      return SVN_NO_ERROR;
7246
 
    }
7247
 
 
7248
 
  /* Walk back a number of predecessors equal to the difference
7249
 
     between count and the original predecessor count.  (For example,
7250
 
     if noderev has ten predecessors and we want the eighth file rev,
7251
 
     walk back two predecessors.) */
7252
 
  base = noderev;
7253
 
  while ((count++) < noderev->predecessor_count)
7254
 
    {
7255
 
      SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
7256
 
                                           base->predecessor_id, pool));
7257
 
 
7258
 
      /* If there is a shared rep along the way, we need to limit the
7259
 
       * length of the deltification chain.
7260
 
       *
7261
 
       * Please note that copied nodes - such as branch directories - will
7262
 
       * look the same (false positive) while reps shared within the same
7263
 
       * revision will not be caught (false negative).
7264
 
       */
7265
 
      if (props)
7266
 
        {
7267
 
          if (   base->prop_rep
7268
 
              && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
7269
 
            maybe_shared_rep = TRUE;
7270
 
        }
7271
 
      else
7272
 
        {
7273
 
          if (   base->data_rep
7274
 
              && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
7275
 
            maybe_shared_rep = TRUE;
7276
 
        }
7277
 
    }
7278
 
 
7279
 
  /* return a suitable base representation */
7280
 
  *rep = props ? base->prop_rep : base->data_rep;
7281
 
 
7282
 
  /* if we encountered a shared rep, it's parent chain may be different
7283
 
   * from the node-rev parent chain. */
7284
 
  if (*rep && maybe_shared_rep)
7285
 
    {
7286
 
      /* Check whether the length of the deltification chain is acceptable.
7287
 
       * Otherwise, shared reps may form a non-skipping delta chain in
7288
 
       * extreme cases. */
7289
 
      apr_pool_t *sub_pool = svn_pool_create(pool);
7290
 
      representation_t base_rep = **rep;
7291
 
 
7292
 
      /* Some reasonable limit, depending on how acceptable longer linear
7293
 
       * chains are in this repo.  Also, allow for some minimal chain. */
7294
 
      int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
7295
 
 
7296
 
      /* re-use open files between iterations */
7297
 
      svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
7298
 
      apr_file_t *file_hint = NULL;
7299
 
 
7300
 
      /* follow the delta chain towards the end but for at most
7301
 
       * MAX_CHAIN_LENGTH steps. */
7302
 
      for (; max_chain_length; --max_chain_length)
7303
 
        {
7304
 
          struct rep_state *rep_state;
7305
 
          struct rep_args *rep_args;
7306
 
 
7307
 
          SVN_ERR(create_rep_state_body(&rep_state,
7308
 
                                        &rep_args,
7309
 
                                        &file_hint,
7310
 
                                        &rev_hint,
7311
 
                                        &base_rep,
7312
 
                                        fs,
7313
 
                                        sub_pool));
7314
 
          if (!rep_args->is_delta  || !rep_args->base_revision)
7315
 
            break;
7316
 
 
7317
 
          base_rep.revision = rep_args->base_revision;
7318
 
          base_rep.offset = rep_args->base_offset;
7319
 
          base_rep.size = rep_args->base_length;
7320
 
          base_rep.txn_id = NULL;
7321
 
        }
7322
 
 
7323
 
      /* start new delta chain if the current one has grown too long */
7324
 
      if (max_chain_length == 0)
7325
 
        *rep = NULL;
7326
 
 
7327
 
      svn_pool_destroy(sub_pool);
7328
 
    }
7329
 
 
7330
 
  /* verify that the reps don't form a degenerated '*/
7331
 
  return SVN_NO_ERROR;
7332
 
}
7333
 
 
7334
 
/* Something went wrong and the pool for the rep write is being
7335
 
   cleared before we've finished writing the rep.  So we need
7336
 
   to remove the rep from the protorevfile and we need to unlock
7337
 
   the protorevfile. */
7338
 
static apr_status_t
7339
 
rep_write_cleanup(void *data)
7340
 
{
7341
 
  struct rep_write_baton *b = data;
7342
 
  const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7343
 
  svn_error_t *err;
7344
 
 
7345
 
  /* Truncate and close the protorevfile. */
7346
 
  err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
7347
 
  err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
7348
 
 
7349
 
  /* Remove our lock regardless of any preceeding errors so that the
7350
 
     being_written flag is always removed and stays consistent with the
7351
 
     file lock which will be removed no matter what since the pool is
7352
 
     going away. */
7353
 
  err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
7354
 
                                                       b->lockcookie, b->pool));
7355
 
  if (err)
7356
 
    {
7357
 
      apr_status_t rc = err->apr_err;
7358
 
      svn_error_clear(err);
7359
 
      return rc;
7360
 
    }
7361
 
 
7362
 
  return APR_SUCCESS;
7363
 
}
7364
 
 
7365
 
 
7366
 
/* Get a rep_write_baton and store it in *WB_P for the representation
7367
 
   indicated by NODEREV in filesystem FS.  Perform allocations in
7368
 
   POOL.  Only appropriate for file contents, not for props or
7369
 
   directory contents. */
7370
 
static svn_error_t *
7371
 
rep_write_get_baton(struct rep_write_baton **wb_p,
7372
 
                    svn_fs_t *fs,
7373
 
                    node_revision_t *noderev,
7374
 
                    apr_pool_t *pool)
7375
 
{
7376
 
  struct rep_write_baton *b;
7377
 
  apr_file_t *file;
7378
 
  representation_t *base_rep;
7379
 
  svn_stream_t *source;
7380
 
  const char *header;
7381
 
  svn_txdelta_window_handler_t wh;
7382
 
  void *whb;
7383
 
  fs_fs_data_t *ffd = fs->fsap_data;
7384
 
  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7385
 
 
7386
 
  b = apr_pcalloc(pool, sizeof(*b));
7387
 
 
7388
 
  b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7389
 
  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7390
 
 
7391
 
  b->fs = fs;
7392
 
  b->parent_pool = pool;
7393
 
  b->pool = svn_pool_create(pool);
7394
 
  b->rep_size = 0;
7395
 
  b->noderev = noderev;
7396
 
 
7397
 
  /* Open the prototype rev file and seek to its end. */
7398
 
  SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
7399
 
                                 fs, svn_fs_fs__id_txn_id(noderev->id),
7400
 
                                 b->pool));
7401
 
 
7402
 
  b->file = file;
7403
 
  b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
7404
 
 
7405
 
  SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
7406
 
 
7407
 
  /* Get the base for this delta. */
7408
 
  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
7409
 
  SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
7410
 
 
7411
 
  /* Write out the rep header. */
7412
 
  if (base_rep)
7413
 
    {
7414
 
      header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7415
 
                            SVN_FILESIZE_T_FMT "\n",
7416
 
                            base_rep->revision, base_rep->offset,
7417
 
                            base_rep->size);
7418
 
    }
7419
 
  else
7420
 
    {
7421
 
      header = REP_DELTA "\n";
7422
 
    }
7423
 
  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7424
 
                                 b->pool));
7425
 
 
7426
 
  /* Now determine the offset of the actual svndiff data. */
7427
 
  SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
7428
 
 
7429
 
  /* Cleanup in case something goes wrong. */
7430
 
  apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
7431
 
                            apr_pool_cleanup_null);
7432
 
 
7433
 
  /* Prepare to write the svndiff data. */
7434
 
  svn_txdelta_to_svndiff3(&wh,
7435
 
                          &whb,
7436
 
                          b->rep_stream,
7437
 
                          diff_version,
7438
 
                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7439
 
                          pool);
7440
 
 
7441
 
  b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
7442
 
 
7443
 
  *wb_p = b;
7444
 
 
7445
 
  return SVN_NO_ERROR;
7446
 
}
7447
 
 
7448
 
/* For the hash REP->SHA1, try to find an already existing representation
7449
 
   in FS and return it in *OUT_REP.  If no such representation exists or
7450
 
   if rep sharing has been disabled for FS, NULL will be returned.  Since
7451
 
   there may be new duplicate representations within the same uncommitted
7452
 
   revision, those can be passed in REPS_HASH (maps a sha1 digest onto
7453
 
   representation_t*), otherwise pass in NULL for REPS_HASH.
7454
 
   POOL will be used for allocations. The lifetime of the returned rep is
7455
 
   limited by both, POOL and REP lifetime.
7456
 
 */
7457
 
static svn_error_t *
7458
 
get_shared_rep(representation_t **old_rep,
7459
 
               svn_fs_t *fs,
7460
 
               representation_t *rep,
7461
 
               apr_hash_t *reps_hash,
7462
 
               apr_pool_t *pool)
7463
 
{
7464
 
  svn_error_t *err;
7465
 
  fs_fs_data_t *ffd = fs->fsap_data;
7466
 
 
7467
 
  /* Return NULL, if rep sharing has been disabled. */
7468
 
  *old_rep = NULL;
7469
 
  if (!ffd->rep_sharing_allowed)
7470
 
    return SVN_NO_ERROR;
7471
 
 
7472
 
  /* Check and see if we already have a representation somewhere that's
7473
 
     identical to the one we just wrote out.  Start with the hash lookup
7474
 
     because it is cheepest. */
7475
 
  if (reps_hash)
7476
 
    *old_rep = apr_hash_get(reps_hash,
7477
 
                            rep->sha1_checksum->digest,
7478
 
                            APR_SHA1_DIGESTSIZE);
7479
 
 
7480
 
  /* If we haven't found anything yet, try harder and consult our DB. */
7481
 
  if (*old_rep == NULL)
7482
 
    {
7483
 
      err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
7484
 
                                         pool);
7485
 
      /* ### Other error codes that we shouldn't mask out? */
7486
 
      if (err == SVN_NO_ERROR)
7487
 
        {
7488
 
          if (*old_rep)
7489
 
            SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
7490
 
        }
7491
 
      else if (err->apr_err == SVN_ERR_FS_CORRUPT
7492
 
               || SVN_ERROR_IN_CATEGORY(err->apr_err,
7493
 
                                        SVN_ERR_MALFUNC_CATEGORY_START))
7494
 
        {
7495
 
          /* Fatal error; don't mask it.
7496
 
 
7497
 
             In particular, this block is triggered when the rep-cache refers
7498
 
             to revisions in the future.  We signal that as a corruption situation
7499
 
             since, once those revisions are less than youngest (because of more
7500
 
             commits), the rep-cache would be invalid.
7501
 
           */
7502
 
          SVN_ERR(err);
7503
 
        }
7504
 
      else
7505
 
        {
7506
 
          /* Something's wrong with the rep-sharing index.  We can continue
7507
 
             without rep-sharing, but warn.
7508
 
           */
7509
 
          (fs->warning)(fs->warning_baton, err);
7510
 
          svn_error_clear(err);
7511
 
          *old_rep = NULL;
7512
 
        }
7513
 
    }
7514
 
 
7515
 
  /* look for intra-revision matches (usually data reps but not limited
7516
 
     to them in case props happen to look like some data rep)
7517
 
   */
7518
 
  if (*old_rep == NULL && rep->txn_id)
7519
 
    {
7520
 
      svn_node_kind_t kind;
7521
 
      const char *file_name
7522
 
        = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
7523
 
 
7524
 
      /* in our txn, is there a rep file named with the wanted SHA1?
7525
 
         If so, read it and use that rep.
7526
 
       */
7527
 
      SVN_ERR(svn_io_check_path(file_name, &kind, pool));
7528
 
      if (kind == svn_node_file)
7529
 
        {
7530
 
          svn_stringbuf_t *rep_string;
7531
 
          SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
7532
 
          SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
7533
 
                                        rep->txn_id, FALSE, pool));
7534
 
        }
7535
 
    }
7536
 
 
7537
 
  /* Add information that is missing in the cached data. */
7538
 
  if (*old_rep)
7539
 
    {
7540
 
      /* Use the old rep for this content. */
7541
 
      (*old_rep)->md5_checksum = rep->md5_checksum;
7542
 
      (*old_rep)->uniquifier = rep->uniquifier;
7543
 
    }
7544
 
 
7545
 
  return SVN_NO_ERROR;
7546
 
}
7547
 
 
7548
 
/* Close handler for the representation write stream.  BATON is a
7549
 
   rep_write_baton.  Writes out a new node-rev that correctly
7550
 
   references the representation we just finished writing. */
7551
 
static svn_error_t *
7552
 
rep_write_contents_close(void *baton)
7553
 
{
7554
 
  struct rep_write_baton *b = baton;
7555
 
  const char *unique_suffix;
7556
 
  representation_t *rep;
7557
 
  representation_t *old_rep;
7558
 
  apr_off_t offset;
7559
 
  fs_fs_data_t *ffd = b->fs->fsap_data;
7560
 
 
7561
 
  rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
7562
 
  rep->offset = b->rep_offset;
7563
 
 
7564
 
  /* Close our delta stream so the last bits of svndiff are written
7565
 
     out. */
7566
 
  if (b->delta_stream)
7567
 
    SVN_ERR(svn_stream_close(b->delta_stream));
7568
 
 
7569
 
  /* Determine the length of the svndiff data. */
7570
 
  SVN_ERR(get_file_offset(&offset, b->file, b->pool));
7571
 
  rep->size = offset - b->delta_start;
7572
 
 
7573
 
  /* Fill in the rest of the representation field. */
7574
 
  rep->expanded_size = b->rep_size;
7575
 
  rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7576
 
 
7577
 
  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
7578
 
    {
7579
 
      SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
7580
 
      rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
7581
 
                                     unique_suffix);
7582
 
    }
7583
 
  rep->revision = SVN_INVALID_REVNUM;
7584
 
 
7585
 
  /* Finalize the checksum. */
7586
 
  SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
7587
 
                              b->parent_pool));
7588
 
  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
7589
 
                              b->parent_pool));
7590
 
 
7591
 
  /* Check and see if we already have a representation somewhere that's
7592
 
     identical to the one we just wrote out. */
7593
 
  SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
7594
 
 
7595
 
  if (old_rep)
7596
 
    {
7597
 
      /* We need to erase from the protorev the data we just wrote. */
7598
 
      SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
7599
 
 
7600
 
      /* Use the old rep for this content. */
7601
 
      b->noderev->data_rep = old_rep;
7602
 
    }
7603
 
  else
7604
 
    {
7605
 
      /* Write out our cosmetic end marker. */
7606
 
      SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
7607
 
 
7608
 
      b->noderev->data_rep = rep;
7609
 
    }
7610
 
 
7611
 
  /* Remove cleanup callback. */
7612
 
  apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
7613
 
 
7614
 
  /* Write out the new node-rev information. */
7615
 
  SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
7616
 
                                       b->pool));
7617
 
  if (!old_rep)
7618
 
    SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
7619
 
 
7620
 
  SVN_ERR(svn_io_file_close(b->file, b->pool));
7621
 
  SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
7622
 
  svn_pool_destroy(b->pool);
7623
 
 
7624
 
  return SVN_NO_ERROR;
7625
 
}
7626
 
 
7627
 
/* Store a writable stream in *CONTENTS_P that will receive all data
7628
 
   written and store it as the file data representation referenced by
7629
 
   NODEREV in filesystem FS.  Perform temporary allocations in
7630
 
   POOL.  Only appropriate for file data, not props or directory
7631
 
   contents. */
7632
 
static svn_error_t *
7633
 
set_representation(svn_stream_t **contents_p,
7634
 
                   svn_fs_t *fs,
7635
 
                   node_revision_t *noderev,
7636
 
                   apr_pool_t *pool)
7637
 
{
7638
 
  struct rep_write_baton *wb;
7639
 
 
7640
 
  if (! svn_fs_fs__id_txn_id(noderev->id))
7641
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7642
 
                             _("Attempted to write to non-transaction '%s'"),
7643
 
                             svn_fs_fs__id_unparse(noderev->id, pool)->data);
7644
 
 
7645
 
  SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
7646
 
 
7647
 
  *contents_p = svn_stream_create(wb, pool);
7648
 
  svn_stream_set_write(*contents_p, rep_write_contents);
7649
 
  svn_stream_set_close(*contents_p, rep_write_contents_close);
7650
 
 
7651
 
  return SVN_NO_ERROR;
7652
 
}
7653
 
 
7654
 
svn_error_t *
7655
 
svn_fs_fs__set_contents(svn_stream_t **stream,
7656
 
                        svn_fs_t *fs,
7657
 
                        node_revision_t *noderev,
7658
 
                        apr_pool_t *pool)
7659
 
{
7660
 
  if (noderev->kind != svn_node_file)
7661
 
    return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
7662
 
                            _("Can't set text contents of a directory"));
7663
 
 
7664
 
  return set_representation(stream, fs, noderev, pool);
7665
 
}
7666
 
 
7667
 
svn_error_t *
7668
 
svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
7669
 
                            svn_fs_t *fs,
7670
 
                            const svn_fs_id_t *old_idp,
7671
 
                            node_revision_t *new_noderev,
7672
 
                            const char *copy_id,
7673
 
                            const char *txn_id,
7674
 
                            apr_pool_t *pool)
7675
 
{
7676
 
  const svn_fs_id_t *id;
7677
 
 
7678
 
  if (! copy_id)
7679
 
    copy_id = svn_fs_fs__id_copy_id(old_idp);
7680
 
  id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
7681
 
                                txn_id, pool);
7682
 
 
7683
 
  new_noderev->id = id;
7684
 
 
7685
 
  if (! new_noderev->copyroot_path)
7686
 
    {
7687
 
      new_noderev->copyroot_path = apr_pstrdup(pool,
7688
 
                                               new_noderev->created_path);
7689
 
      new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
7690
 
    }
7691
 
 
7692
 
  SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
7693
 
                                       pool));
7694
 
 
7695
 
  *new_id_p = id;
7696
 
 
7697
 
  return SVN_NO_ERROR;
7698
 
}
7699
 
 
7700
 
svn_error_t *
7701
 
svn_fs_fs__set_proplist(svn_fs_t *fs,
7702
 
                        node_revision_t *noderev,
7703
 
                        apr_hash_t *proplist,
7704
 
                        apr_pool_t *pool)
7705
 
{
7706
 
  const char *filename = path_txn_node_props(fs, noderev->id, pool);
7707
 
  apr_file_t *file;
7708
 
  svn_stream_t *out;
7709
 
 
7710
 
  /* Dump the property list to the mutable property file. */
7711
 
  SVN_ERR(svn_io_file_open(&file, filename,
7712
 
                           APR_WRITE | APR_CREATE | APR_TRUNCATE
7713
 
                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7714
 
  out = svn_stream_from_aprfile2(file, TRUE, pool);
7715
 
  SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
7716
 
  SVN_ERR(svn_io_file_close(file, pool));
7717
 
 
7718
 
  /* Mark the node-rev's prop rep as mutable, if not already done. */
7719
 
  if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
7720
 
    {
7721
 
      noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
7722
 
      noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
7723
 
      SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
7724
 
    }
7725
 
 
7726
 
  return SVN_NO_ERROR;
7727
 
}
7728
 
 
7729
 
/* Read the 'current' file for filesystem FS and store the next
7730
 
   available node id in *NODE_ID, and the next available copy id in
7731
 
   *COPY_ID.  Allocations are performed from POOL. */
7732
 
static svn_error_t *
7733
 
get_next_revision_ids(const char **node_id,
7734
 
                      const char **copy_id,
7735
 
                      svn_fs_t *fs,
7736
 
                      apr_pool_t *pool)
7737
 
{
7738
 
  char *buf;
7739
 
  char *str;
7740
 
  svn_stringbuf_t *content;
7741
 
 
7742
 
  SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
7743
 
  buf = content->data;
7744
 
 
7745
 
  str = svn_cstring_tokenize(" ", &buf);
7746
 
  if (! str)
7747
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7748
 
                            _("Corrupt 'current' file"));
7749
 
 
7750
 
  str = svn_cstring_tokenize(" ", &buf);
7751
 
  if (! str)
7752
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7753
 
                            _("Corrupt 'current' file"));
7754
 
 
7755
 
  *node_id = apr_pstrdup(pool, str);
7756
 
 
7757
 
  str = svn_cstring_tokenize(" \n", &buf);
7758
 
  if (! str)
7759
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7760
 
                            _("Corrupt 'current' file"));
7761
 
 
7762
 
  *copy_id = apr_pstrdup(pool, str);
7763
 
 
7764
 
  return SVN_NO_ERROR;
7765
 
}
7766
 
 
7767
 
/* This baton is used by the stream created for write_hash_rep. */
7768
 
struct write_hash_baton
7769
 
{
7770
 
  svn_stream_t *stream;
7771
 
 
7772
 
  apr_size_t size;
7773
 
 
7774
 
  svn_checksum_ctx_t *md5_ctx;
7775
 
  svn_checksum_ctx_t *sha1_ctx;
7776
 
};
7777
 
 
7778
 
/* The handler for the write_hash_rep stream.  BATON is a
7779
 
   write_hash_baton, DATA has the data to write and *LEN is the number
7780
 
   of bytes to write. */
7781
 
static svn_error_t *
7782
 
write_hash_handler(void *baton,
7783
 
                   const char *data,
7784
 
                   apr_size_t *len)
7785
 
{
7786
 
  struct write_hash_baton *whb = baton;
7787
 
 
7788
 
  SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
7789
 
  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
7790
 
 
7791
 
  SVN_ERR(svn_stream_write(whb->stream, data, len));
7792
 
  whb->size += *len;
7793
 
 
7794
 
  return SVN_NO_ERROR;
7795
 
}
7796
 
 
7797
 
/* Write out the hash HASH as a text representation to file FILE.  In
7798
 
   the process, record position, the total size of the dump and MD5 as
7799
 
   well as SHA1 in REP.   If rep sharing has been enabled and REPS_HASH
7800
 
   is not NULL, it will be used in addition to the on-disk cache to find
7801
 
   earlier reps with the same content.  When such existing reps can be
7802
 
   found, we will truncate the one just written from the file and return
7803
 
   the existing rep.  Perform temporary allocations in POOL. */
7804
 
static svn_error_t *
7805
 
write_hash_rep(representation_t *rep,
7806
 
               apr_file_t *file,
7807
 
               apr_hash_t *hash,
7808
 
               svn_fs_t *fs,
7809
 
               apr_hash_t *reps_hash,
7810
 
               apr_pool_t *pool)
7811
 
{
7812
 
  svn_stream_t *stream;
7813
 
  struct write_hash_baton *whb;
7814
 
  representation_t *old_rep;
7815
 
 
7816
 
  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7817
 
 
7818
 
  whb = apr_pcalloc(pool, sizeof(*whb));
7819
 
 
7820
 
  whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
7821
 
  whb->size = 0;
7822
 
  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7823
 
  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7824
 
 
7825
 
  stream = svn_stream_create(whb, pool);
7826
 
  svn_stream_set_write(stream, write_hash_handler);
7827
 
 
7828
 
  SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
7829
 
 
7830
 
  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7831
 
 
7832
 
  /* Store the results. */
7833
 
  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7834
 
  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7835
 
 
7836
 
  /* Check and see if we already have a representation somewhere that's
7837
 
     identical to the one we just wrote out. */
7838
 
  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7839
 
 
7840
 
  if (old_rep)
7841
 
    {
7842
 
      /* We need to erase from the protorev the data we just wrote. */
7843
 
      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7844
 
 
7845
 
      /* Use the old rep for this content. */
7846
 
      memcpy(rep, old_rep, sizeof (*rep));
7847
 
    }
7848
 
  else
7849
 
    {
7850
 
      /* Write out our cosmetic end marker. */
7851
 
      SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
7852
 
 
7853
 
      /* update the representation */
7854
 
      rep->size = whb->size;
7855
 
      rep->expanded_size = whb->size;
7856
 
    }
7857
 
 
7858
 
  return SVN_NO_ERROR;
7859
 
}
7860
 
 
7861
 
/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
7862
 
   text representation to file FILE.  In the process, record the total size
7863
 
   and the md5 digest in REP.  If rep sharing has been enabled and REPS_HASH
7864
 
   is not NULL, it will be used in addition to the on-disk cache to find
7865
 
   earlier reps with the same content.  When such existing reps can be found,
7866
 
   we will truncate the one just written from the file and return the existing
7867
 
   rep.  If PROPS is set, assume that we want to a props representation as
7868
 
   the base for our delta.  Perform temporary allocations in POOL. */
7869
 
static svn_error_t *
7870
 
write_hash_delta_rep(representation_t *rep,
7871
 
                     apr_file_t *file,
7872
 
                     apr_hash_t *hash,
7873
 
                     svn_fs_t *fs,
7874
 
                     node_revision_t *noderev,
7875
 
                     apr_hash_t *reps_hash,
7876
 
                     svn_boolean_t props,
7877
 
                     apr_pool_t *pool)
7878
 
{
7879
 
  svn_txdelta_window_handler_t diff_wh;
7880
 
  void *diff_whb;
7881
 
 
7882
 
  svn_stream_t *file_stream;
7883
 
  svn_stream_t *stream;
7884
 
  representation_t *base_rep;
7885
 
  representation_t *old_rep;
7886
 
  svn_stream_t *source;
7887
 
  const char *header;
7888
 
 
7889
 
  apr_off_t rep_end = 0;
7890
 
  apr_off_t delta_start = 0;
7891
 
 
7892
 
  struct write_hash_baton *whb;
7893
 
  fs_fs_data_t *ffd = fs->fsap_data;
7894
 
  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7895
 
 
7896
 
  /* Get the base for this delta. */
7897
 
  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
7898
 
  SVN_ERR(read_representation(&source, fs, base_rep, pool));
7899
 
 
7900
 
  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7901
 
 
7902
 
  /* Write out the rep header. */
7903
 
  if (base_rep)
7904
 
    {
7905
 
      header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7906
 
                            SVN_FILESIZE_T_FMT "\n",
7907
 
                            base_rep->revision, base_rep->offset,
7908
 
                            base_rep->size);
7909
 
    }
7910
 
  else
7911
 
    {
7912
 
      header = REP_DELTA "\n";
7913
 
    }
7914
 
  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7915
 
                                 pool));
7916
 
 
7917
 
  SVN_ERR(get_file_offset(&delta_start, file, pool));
7918
 
  file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
7919
 
 
7920
 
  /* Prepare to write the svndiff data. */
7921
 
  svn_txdelta_to_svndiff3(&diff_wh,
7922
 
                          &diff_whb,
7923
 
                          file_stream,
7924
 
                          diff_version,
7925
 
                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7926
 
                          pool);
7927
 
 
7928
 
  whb = apr_pcalloc(pool, sizeof(*whb));
7929
 
  whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
7930
 
  whb->size = 0;
7931
 
  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7932
 
  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7933
 
 
7934
 
  /* serialize the hash */
7935
 
  stream = svn_stream_create(whb, pool);
7936
 
  svn_stream_set_write(stream, write_hash_handler);
7937
 
 
7938
 
  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7939
 
  SVN_ERR(svn_stream_close(whb->stream));
7940
 
 
7941
 
  /* Store the results. */
7942
 
  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7943
 
  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7944
 
 
7945
 
  /* Check and see if we already have a representation somewhere that's
7946
 
     identical to the one we just wrote out. */
7947
 
  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7948
 
 
7949
 
  if (old_rep)
7950
 
    {
7951
 
      /* We need to erase from the protorev the data we just wrote. */
7952
 
      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7953
 
 
7954
 
      /* Use the old rep for this content. */
7955
 
      memcpy(rep, old_rep, sizeof (*rep));
7956
 
    }
7957
 
  else
7958
 
    {
7959
 
      /* Write out our cosmetic end marker. */
7960
 
      SVN_ERR(get_file_offset(&rep_end, file, pool));
7961
 
      SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
7962
 
 
7963
 
      /* update the representation */
7964
 
      rep->expanded_size = whb->size;
7965
 
      rep->size = rep_end - delta_start;
7966
 
    }
7967
 
 
7968
 
  return SVN_NO_ERROR;
7969
 
}
7970
 
 
7971
 
/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
7972
 
   of (not yet committed) revision REV in FS.  Use POOL for temporary
7973
 
   allocations.
7974
 
 
7975
 
   If you change this function, consider updating svn_fs_fs__verify() too.
7976
 
 */
7977
 
static svn_error_t *
7978
 
validate_root_noderev(svn_fs_t *fs,
7979
 
                      node_revision_t *root_noderev,
7980
 
                      svn_revnum_t rev,
7981
 
                      apr_pool_t *pool)
7982
 
{
7983
 
  svn_revnum_t head_revnum = rev-1;
7984
 
  int head_predecessor_count;
7985
 
 
7986
 
  SVN_ERR_ASSERT(rev > 0);
7987
 
 
7988
 
  /* Compute HEAD_PREDECESSOR_COUNT. */
7989
 
  {
7990
 
    svn_fs_root_t *head_revision;
7991
 
    const svn_fs_id_t *head_root_id;
7992
 
    node_revision_t *head_root_noderev;
7993
 
 
7994
 
    /* Get /@HEAD's noderev. */
7995
 
    SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
7996
 
    SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
7997
 
    SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
7998
 
                                         pool));
7999
 
 
8000
 
    head_predecessor_count = head_root_noderev->predecessor_count;
8001
 
  }
8002
 
 
8003
 
  /* Check that the root noderev's predecessor count equals REV.
8004
 
 
8005
 
     This kind of corruption was seen on svn.apache.org (both on
8006
 
     the root noderev and on other fspaths' noderevs); see
8007
 
     issue #4129.
8008
 
 
8009
 
     Normally (rev == root_noderev->predecessor_count), but here we
8010
 
     use a more roundabout check that should only trigger on new instances
8011
 
     of the corruption, rather then trigger on each and every new commit
8012
 
     to a repository that has triggered the bug somewhere in its root
8013
 
     noderev's history.
8014
 
   */
8015
 
  if (root_noderev->predecessor_count != -1
8016
 
      && (root_noderev->predecessor_count - head_predecessor_count)
8017
 
         != (rev - head_revnum))
8018
 
    {
8019
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
8020
 
                               _("predecessor count for "
8021
 
                                 "the root node-revision is wrong: "
8022
 
                                 "found (%d+%ld != %d), committing r%ld"),
8023
 
                                 head_predecessor_count,
8024
 
                                 rev - head_revnum, /* This is equal to 1. */
8025
 
                                 root_noderev->predecessor_count,
8026
 
                                 rev);
8027
 
    }
8028
 
 
8029
 
  return SVN_NO_ERROR;
8030
 
}
8031
 
 
8032
 
/* Copy a node-revision specified by id ID in fileystem FS from a
8033
 
   transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
8034
 
   pointer to the new node-id which will be allocated in POOL.
8035
 
   If this is a directory, copy all children as well.
8036
 
 
8037
 
   START_NODE_ID and START_COPY_ID are
8038
 
   the first available node and copy ids for this filesystem, for older
8039
 
   FS formats.
8040
 
 
8041
 
   REV is the revision number that this proto-rev-file will represent.
8042
 
 
8043
 
   INITIAL_OFFSET is the offset of the proto-rev-file on entry to
8044
 
   commit_body.
8045
 
 
8046
 
   If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
8047
 
   REPS_POOL) of each data rep that is new in this revision.
8048
 
 
8049
 
   If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
8050
 
   of the representations of each property rep that is new in this
8051
 
   revision.
8052
 
 
8053
 
   AT_ROOT is true if the node revision being written is the root
8054
 
   node-revision.  It is only controls additional sanity checking
8055
 
   logic.
8056
 
 
8057
 
   Temporary allocations are also from POOL. */
8058
 
static svn_error_t *
8059
 
write_final_rev(const svn_fs_id_t **new_id_p,
8060
 
                apr_file_t *file,
8061
 
                svn_revnum_t rev,
8062
 
                svn_fs_t *fs,
8063
 
                const svn_fs_id_t *id,
8064
 
                const char *start_node_id,
8065
 
                const char *start_copy_id,
8066
 
                apr_off_t initial_offset,
8067
 
                apr_array_header_t *reps_to_cache,
8068
 
                apr_hash_t *reps_hash,
8069
 
                apr_pool_t *reps_pool,
8070
 
                svn_boolean_t at_root,
8071
 
                apr_pool_t *pool)
8072
 
{
8073
 
  node_revision_t *noderev;
8074
 
  apr_off_t my_offset;
8075
 
  char my_node_id_buf[MAX_KEY_SIZE + 2];
8076
 
  char my_copy_id_buf[MAX_KEY_SIZE + 2];
8077
 
  const svn_fs_id_t *new_id;
8078
 
  const char *node_id, *copy_id, *my_node_id, *my_copy_id;
8079
 
  fs_fs_data_t *ffd = fs->fsap_data;
8080
 
 
8081
 
  *new_id_p = NULL;
8082
 
 
8083
 
  /* Check to see if this is a transaction node. */
8084
 
  if (! svn_fs_fs__id_txn_id(id))
8085
 
    return SVN_NO_ERROR;
8086
 
 
8087
 
  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
8088
 
 
8089
 
  if (noderev->kind == svn_node_dir)
8090
 
    {
8091
 
      apr_pool_t *subpool;
8092
 
      apr_hash_t *entries, *str_entries;
8093
 
      apr_array_header_t *sorted_entries;
8094
 
      int i;
8095
 
 
8096
 
      /* This is a directory.  Write out all the children first. */
8097
 
      subpool = svn_pool_create(pool);
8098
 
 
8099
 
      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
8100
 
      /* For the sake of the repository administrator sort the entries
8101
 
         so that the final file is deterministic and repeatable,
8102
 
         however the rest of the FSFS code doesn't require any
8103
 
         particular order here. */
8104
 
      sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
8105
 
                                      pool);
8106
 
      for (i = 0; i < sorted_entries->nelts; ++i)
8107
 
        {
8108
 
          svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
8109
 
                                                  svn_sort__item_t).value;
8110
 
 
8111
 
          svn_pool_clear(subpool);
8112
 
          SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
8113
 
                                  start_node_id, start_copy_id, initial_offset,
8114
 
                                  reps_to_cache, reps_hash, reps_pool, FALSE,
8115
 
                                  subpool));
8116
 
          if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
8117
 
            dirent->id = svn_fs_fs__id_copy(new_id, pool);
8118
 
        }
8119
 
      svn_pool_destroy(subpool);
8120
 
 
8121
 
      if (noderev->data_rep && noderev->data_rep->txn_id)
8122
 
        {
8123
 
          /* Write out the contents of this directory as a text rep. */
8124
 
          SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
8125
 
 
8126
 
          noderev->data_rep->txn_id = NULL;
8127
 
          noderev->data_rep->revision = rev;
8128
 
 
8129
 
          if (ffd->deltify_directories)
8130
 
            SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
8131
 
                                         str_entries, fs, noderev, NULL,
8132
 
                                         FALSE, pool));
8133
 
          else
8134
 
            SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
8135
 
                                   fs, NULL, pool));
8136
 
        }
8137
 
    }
8138
 
  else
8139
 
    {
8140
 
      /* This is a file.  We should make sure the data rep, if it
8141
 
         exists in a "this" state, gets rewritten to our new revision
8142
 
         num. */
8143
 
 
8144
 
      if (noderev->data_rep && noderev->data_rep->txn_id)
8145
 
        {
8146
 
          noderev->data_rep->txn_id = NULL;
8147
 
          noderev->data_rep->revision = rev;
8148
 
 
8149
 
          /* See issue 3845.  Some unknown mechanism caused the
8150
 
             protorev file to get truncated, so check for that
8151
 
             here.  */
8152
 
          if (noderev->data_rep->offset + noderev->data_rep->size
8153
 
              > initial_offset)
8154
 
            return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
8155
 
                                    _("Truncated protorev file detected"));
8156
 
        }
8157
 
    }
8158
 
 
8159
 
  /* Fix up the property reps. */
8160
 
  if (noderev->prop_rep && noderev->prop_rep->txn_id)
8161
 
    {
8162
 
      apr_hash_t *proplist;
8163
 
      SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
8164
 
 
8165
 
      noderev->prop_rep->txn_id = NULL;
8166
 
      noderev->prop_rep->revision = rev;
8167
 
 
8168
 
      if (ffd->deltify_properties)
8169
 
        SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
8170
 
                                     proplist, fs, noderev, reps_hash,
8171
 
                                     TRUE, pool));
8172
 
      else
8173
 
        SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
8174
 
                               fs, reps_hash, pool));
8175
 
    }
8176
 
 
8177
 
 
8178
 
  /* Convert our temporary ID into a permanent revision one. */
8179
 
  SVN_ERR(get_file_offset(&my_offset, file, pool));
8180
 
 
8181
 
  node_id = svn_fs_fs__id_node_id(noderev->id);
8182
 
  if (*node_id == '_')
8183
 
    {
8184
 
      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8185
 
        my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
8186
 
      else
8187
 
        {
8188
 
          svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
8189
 
          my_node_id = my_node_id_buf;
8190
 
        }
8191
 
    }
8192
 
  else
8193
 
    my_node_id = node_id;
8194
 
 
8195
 
  copy_id = svn_fs_fs__id_copy_id(noderev->id);
8196
 
  if (*copy_id == '_')
8197
 
    {
8198
 
      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8199
 
        my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
8200
 
      else
8201
 
        {
8202
 
          svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
8203
 
          my_copy_id = my_copy_id_buf;
8204
 
        }
8205
 
    }
8206
 
  else
8207
 
    my_copy_id = copy_id;
8208
 
 
8209
 
  if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
8210
 
    noderev->copyroot_rev = rev;
8211
 
 
8212
 
  new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
8213
 
                                    pool);
8214
 
 
8215
 
  noderev->id = new_id;
8216
 
 
8217
 
  if (ffd->rep_sharing_allowed)
8218
 
    {
8219
 
      /* Save the data representation's hash in the rep cache. */
8220
 
      if (   noderev->data_rep && noderev->kind == svn_node_file
8221
 
          && noderev->data_rep->revision == rev)
8222
 
        {
8223
 
          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8224
 
          APR_ARRAY_PUSH(reps_to_cache, representation_t *)
8225
 
            = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
8226
 
        }
8227
 
 
8228
 
      if (noderev->prop_rep && noderev->prop_rep->revision == rev)
8229
 
        {
8230
 
          /* Add new property reps to hash and on-disk cache. */
8231
 
          representation_t *copy
8232
 
            = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
8233
 
 
8234
 
          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8235
 
          APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
8236
 
 
8237
 
          apr_hash_set(reps_hash,
8238
 
                        copy->sha1_checksum->digest,
8239
 
                        APR_SHA1_DIGESTSIZE,
8240
 
                        copy);
8241
 
        }
8242
 
    }
8243
 
 
8244
 
  /* don't serialize SHA1 for dirs to disk (waste of space) */
8245
 
  if (noderev->data_rep && noderev->kind == svn_node_dir)
8246
 
    noderev->data_rep->sha1_checksum = NULL;
8247
 
 
8248
 
  /* don't serialize SHA1 for props to disk (waste of space) */
8249
 
  if (noderev->prop_rep)
8250
 
    noderev->prop_rep->sha1_checksum = NULL;
8251
 
 
8252
 
  /* Workaround issue #4031: is-fresh-txn-root in revision files. */
8253
 
  noderev->is_fresh_txn_root = FALSE;
8254
 
 
8255
 
  /* Write out our new node-revision. */
8256
 
  if (at_root)
8257
 
    SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
8258
 
 
8259
 
  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
8260
 
                                   noderev, ffd->format,
8261
 
                                   svn_fs_fs__fs_supports_mergeinfo(fs),
8262
 
                                   pool));
8263
 
 
8264
 
  /* Return our ID that references the revision file. */
8265
 
  *new_id_p = noderev->id;
8266
 
 
8267
 
  return SVN_NO_ERROR;
8268
 
}
8269
 
 
8270
 
/* Write the changed path info from transaction TXN_ID in filesystem
8271
 
   FS to the permanent rev-file FILE.  *OFFSET_P is set the to offset
8272
 
   in the file of the beginning of this information.  Perform
8273
 
   temporary allocations in POOL. */
8274
 
static svn_error_t *
8275
 
write_final_changed_path_info(apr_off_t *offset_p,
8276
 
                              apr_file_t *file,
8277
 
                              svn_fs_t *fs,
8278
 
                              const char *txn_id,
8279
 
                              apr_pool_t *pool)
8280
 
{
8281
 
  apr_hash_t *changed_paths;
8282
 
  apr_off_t offset;
8283
 
  apr_pool_t *iterpool = svn_pool_create(pool);
8284
 
  fs_fs_data_t *ffd = fs->fsap_data;
8285
 
  svn_boolean_t include_node_kinds =
8286
 
      ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
8287
 
  apr_array_header_t *sorted_changed_paths;
8288
 
  int i;
8289
 
 
8290
 
  SVN_ERR(get_file_offset(&offset, file, pool));
8291
 
 
8292
 
  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
8293
 
  /* For the sake of the repository administrator sort the changes so
8294
 
     that the final file is deterministic and repeatable, however the
8295
 
     rest of the FSFS code doesn't require any particular order here. */
8296
 
  sorted_changed_paths = svn_sort__hash(changed_paths,
8297
 
                                        svn_sort_compare_items_lexically, pool);
8298
 
 
8299
 
  /* Iterate through the changed paths one at a time, and convert the
8300
 
     temporary node-id into a permanent one for each change entry. */
8301
 
  for (i = 0; i < sorted_changed_paths->nelts; ++i)
8302
 
    {
8303
 
      node_revision_t *noderev;
8304
 
      const svn_fs_id_t *id;
8305
 
      svn_fs_path_change2_t *change;
8306
 
      const char *path;
8307
 
 
8308
 
      svn_pool_clear(iterpool);
8309
 
 
8310
 
      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
8311
 
      path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
8312
 
 
8313
 
      id = change->node_rev_id;
8314
 
 
8315
 
      /* If this was a delete of a mutable node, then it is OK to
8316
 
         leave the change entry pointing to the non-existent temporary
8317
 
         node, since it will never be used. */
8318
 
      if ((change->change_kind != svn_fs_path_change_delete) &&
8319
 
          (! svn_fs_fs__id_txn_id(id)))
8320
 
        {
8321
 
          SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
8322
 
 
8323
 
          /* noderev has the permanent node-id at this point, so we just
8324
 
             substitute it for the temporary one. */
8325
 
          change->node_rev_id = noderev->id;
8326
 
        }
8327
 
 
8328
 
      /* Write out the new entry into the final rev-file. */
8329
 
      SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
8330
 
                                 iterpool));
8331
 
    }
8332
 
 
8333
 
  svn_pool_destroy(iterpool);
8334
 
 
8335
 
  *offset_p = offset;
8336
 
 
8337
 
  return SVN_NO_ERROR;
8338
 
}
8339
 
 
8340
 
/* Atomically update the 'current' file to hold the specifed REV,
8341
 
   NEXT_NODE_ID, and NEXT_COPY_ID.  (The two next-ID parameters are
8342
 
   ignored and may be NULL if the FS format does not use them.)
8343
 
   Perform temporary allocations in POOL. */
8344
 
static svn_error_t *
8345
 
write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
8346
 
              const char *next_copy_id, apr_pool_t *pool)
8347
 
{
8348
 
  char *buf;
8349
 
  const char *tmp_name, *name;
8350
 
  fs_fs_data_t *ffd = fs->fsap_data;
8351
 
 
8352
 
  /* Now we can just write out this line. */
8353
 
  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8354
 
    buf = apr_psprintf(pool, "%ld\n", rev);
8355
 
  else
8356
 
    buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
8357
 
 
8358
 
  name = svn_fs_fs__path_current(fs, pool);
8359
 
  SVN_ERR(svn_io_write_unique(&tmp_name,
8360
 
                              svn_dirent_dirname(name, pool),
8361
 
                              buf, strlen(buf),
8362
 
                              svn_io_file_del_none, pool));
8363
 
 
8364
 
  return move_into_place(tmp_name, name, name, pool);
8365
 
}
8366
 
 
8367
 
/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
8368
 
   youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
8369
 
   NEW_REV's revision root.
8370
 
 
8371
 
   Intended to be called as the very last step in a commit before 'current'
8372
 
   is bumped.  This implies that we are holding the write lock. */
8373
 
static svn_error_t *
8374
 
verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
8375
 
                                            svn_revnum_t new_rev,
8376
 
                                            apr_pool_t *pool)
8377
 
{
8378
 
#ifdef SVN_DEBUG
8379
 
  fs_fs_data_t *ffd = fs->fsap_data;
8380
 
  svn_fs_t *ft; /* fs++ == ft */
8381
 
  svn_fs_root_t *root;
8382
 
  fs_fs_data_t *ft_ffd;
8383
 
  apr_hash_t *fs_config;
8384
 
 
8385
 
  SVN_ERR_ASSERT(ffd->svn_fs_open_);
8386
 
 
8387
 
  /* make sure FT does not simply return data cached by other instances
8388
 
   * but actually retrieves it from disk at least once.
8389
 
   */
8390
 
  fs_config = apr_hash_make(pool);
8391
 
  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
8392
 
                           svn_uuid_generate(pool));
8393
 
  SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
8394
 
                            fs_config,
8395
 
                            pool));
8396
 
  ft_ffd = ft->fsap_data;
8397
 
  /* Don't let FT consult rep-cache.db, either. */
8398
 
  ft_ffd->rep_sharing_allowed = FALSE;
8399
 
 
8400
 
  /* Time travel! */
8401
 
  ft_ffd->youngest_rev_cache = new_rev;
8402
 
 
8403
 
  SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
8404
 
  SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
8405
 
  SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
8406
 
  SVN_ERR(svn_fs_fs__verify_root(root, pool));
8407
 
#endif /* SVN_DEBUG */
8408
 
 
8409
 
  return SVN_NO_ERROR;
8410
 
}
8411
 
 
8412
 
/* Update the 'current' file to hold the correct next node and copy_ids
8413
 
   from transaction TXN_ID in filesystem FS.  The current revision is
8414
 
   set to REV.  Perform temporary allocations in POOL. */
8415
 
static svn_error_t *
8416
 
write_final_current(svn_fs_t *fs,
8417
 
                    const char *txn_id,
8418
 
                    svn_revnum_t rev,
8419
 
                    const char *start_node_id,
8420
 
                    const char *start_copy_id,
8421
 
                    apr_pool_t *pool)
8422
 
{
8423
 
  const char *txn_node_id, *txn_copy_id;
8424
 
  char new_node_id[MAX_KEY_SIZE + 2];
8425
 
  char new_copy_id[MAX_KEY_SIZE + 2];
8426
 
  fs_fs_data_t *ffd = fs->fsap_data;
8427
 
 
8428
 
  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8429
 
    return write_current(fs, rev, NULL, NULL, pool);
8430
 
 
8431
 
  /* To find the next available ids, we add the id that used to be in
8432
 
     the 'current' file, to the next ids from the transaction file. */
8433
 
  SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
8434
 
 
8435
 
  svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
8436
 
  svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
8437
 
 
8438
 
  return write_current(fs, rev, new_node_id, new_copy_id, pool);
8439
 
}
8440
 
 
8441
 
/* Verify that the user registed with FS has all the locks necessary to
8442
 
   permit all the changes associate with TXN_NAME.
8443
 
   The FS write lock is assumed to be held by the caller. */
8444
 
static svn_error_t *
8445
 
verify_locks(svn_fs_t *fs,
8446
 
             const char *txn_name,
8447
 
             apr_pool_t *pool)
8448
 
{
8449
 
  apr_pool_t *subpool = svn_pool_create(pool);
8450
 
  apr_hash_t *changes;
8451
 
  apr_hash_index_t *hi;
8452
 
  apr_array_header_t *changed_paths;
8453
 
  svn_stringbuf_t *last_recursed = NULL;
8454
 
  int i;
8455
 
 
8456
 
  /* Fetch the changes for this transaction. */
8457
 
  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
8458
 
 
8459
 
  /* Make an array of the changed paths, and sort them depth-first-ily.  */
8460
 
  changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
8461
 
                                 sizeof(const char *));
8462
 
  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
8463
 
    APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
8464
 
  qsort(changed_paths->elts, changed_paths->nelts,
8465
 
        changed_paths->elt_size, svn_sort_compare_paths);
8466
 
 
8467
 
  /* Now, traverse the array of changed paths, verify locks.  Note
8468
 
     that if we need to do a recursive verification a path, we'll skip
8469
 
     over children of that path when we get to them. */
8470
 
  for (i = 0; i < changed_paths->nelts; i++)
8471
 
    {
8472
 
      const char *path;
8473
 
      svn_fs_path_change2_t *change;
8474
 
      svn_boolean_t recurse = TRUE;
8475
 
 
8476
 
      svn_pool_clear(subpool);
8477
 
      path = APR_ARRAY_IDX(changed_paths, i, const char *);
8478
 
 
8479
 
      /* If this path has already been verified as part of a recursive
8480
 
         check of one of its parents, no need to do it again.  */
8481
 
      if (last_recursed
8482
 
          && svn_dirent_is_child(last_recursed->data, path, subpool))
8483
 
        continue;
8484
 
 
8485
 
      /* Fetch the change associated with our path.  */
8486
 
      change = svn_hash_gets(changes, path);
8487
 
 
8488
 
      /* What does it mean to succeed at lock verification for a given
8489
 
         path?  For an existing file or directory getting modified
8490
 
         (text, props), it means we hold the lock on the file or
8491
 
         directory.  For paths being added or removed, we need to hold
8492
 
         the locks for that path and any children of that path.
8493
 
 
8494
 
         WHEW!  We have no reliable way to determine the node kind
8495
 
         of deleted items, but fortunately we are going to do a
8496
 
         recursive check on deleted paths regardless of their kind.  */
8497
 
      if (change->change_kind == svn_fs_path_change_modify)
8498
 
        recurse = FALSE;
8499
 
      SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
8500
 
                                                subpool));
8501
 
 
8502
 
      /* If we just did a recursive check, remember the path we
8503
 
         checked (so children can be skipped).  */
8504
 
      if (recurse)
8505
 
        {
8506
 
          if (! last_recursed)
8507
 
            last_recursed = svn_stringbuf_create(path, pool);
8508
 
          else
8509
 
            svn_stringbuf_set(last_recursed, path);
8510
 
        }
8511
 
    }
8512
 
  svn_pool_destroy(subpool);
8513
 
  return SVN_NO_ERROR;
8514
 
}
8515
 
 
8516
 
/* Baton used for commit_body below. */
8517
 
struct commit_baton {
8518
 
  svn_revnum_t *new_rev_p;
8519
 
  svn_fs_t *fs;
8520
 
  svn_fs_txn_t *txn;
8521
 
  apr_array_header_t *reps_to_cache;
8522
 
  apr_hash_t *reps_hash;
8523
 
  apr_pool_t *reps_pool;
8524
 
};
8525
 
 
8526
 
/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
8527
 
   This implements the svn_fs_fs__with_write_lock() 'body' callback
8528
 
   type.  BATON is a 'struct commit_baton *'. */
8529
 
static svn_error_t *
8530
 
commit_body(void *baton, apr_pool_t *pool)
8531
 
{
8532
 
  struct commit_baton *cb = baton;
8533
 
  fs_fs_data_t *ffd = cb->fs->fsap_data;
8534
 
  const char *old_rev_filename, *rev_filename, *proto_filename;
8535
 
  const char *revprop_filename, *final_revprop;
8536
 
  const svn_fs_id_t *root_id, *new_root_id;
8537
 
  const char *start_node_id = NULL, *start_copy_id = NULL;
8538
 
  svn_revnum_t old_rev, new_rev;
8539
 
  apr_file_t *proto_file;
8540
 
  void *proto_file_lockcookie;
8541
 
  apr_off_t initial_offset, changed_path_offset;
8542
 
  char *buf;
8543
 
  apr_hash_t *txnprops;
8544
 
  apr_array_header_t *txnprop_list;
8545
 
  svn_prop_t prop;
8546
 
  svn_string_t date;
8547
 
 
8548
 
  /* Get the current youngest revision. */
8549
 
  SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
8550
 
 
8551
 
  /* Check to make sure this transaction is based off the most recent
8552
 
     revision. */
8553
 
  if (cb->txn->base_rev != old_rev)
8554
 
    return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
8555
 
                            _("Transaction out of date"));
8556
 
 
8557
 
  /* Locks may have been added (or stolen) between the calling of
8558
 
     previous svn_fs.h functions and svn_fs_commit_txn(), so we need
8559
 
     to re-examine every changed-path in the txn and re-verify all
8560
 
     discovered locks. */
8561
 
  SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
8562
 
 
8563
 
  /* Get the next node_id and copy_id to use. */
8564
 
  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8565
 
    SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
8566
 
                                  pool));
8567
 
 
8568
 
  /* We are going to be one better than this puny old revision. */
8569
 
  new_rev = old_rev + 1;
8570
 
 
8571
 
  /* Get a write handle on the proto revision file. */
8572
 
  SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
8573
 
                                 cb->fs, cb->txn->id, pool));
8574
 
  SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
8575
 
 
8576
 
  /* Write out all the node-revisions and directory contents. */
8577
 
  root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
8578
 
  SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
8579
 
                          start_node_id, start_copy_id, initial_offset,
8580
 
                          cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
8581
 
                          TRUE, pool));
8582
 
 
8583
 
  /* Write the changed-path information. */
8584
 
  SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
8585
 
                                        cb->fs, cb->txn->id, pool));
8586
 
 
8587
 
  /* Write the final line. */
8588
 
  buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
8589
 
                     svn_fs_fs__id_offset(new_root_id),
8590
 
                     changed_path_offset);
8591
 
  SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
8592
 
                                 pool));
8593
 
  SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
8594
 
  SVN_ERR(svn_io_file_close(proto_file, pool));
8595
 
 
8596
 
  /* We don't unlock the prototype revision file immediately to avoid a
8597
 
     race with another caller writing to the prototype revision file
8598
 
     before we commit it. */
8599
 
 
8600
 
  /* Remove any temporary txn props representing 'flags'. */
8601
 
  SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
8602
 
  txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
8603
 
  prop.value = NULL;
8604
 
 
8605
 
  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
8606
 
    {
8607
 
      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
8608
 
      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8609
 
    }
8610
 
 
8611
 
  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
8612
 
    {
8613
 
      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
8614
 
      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8615
 
    }
8616
 
 
8617
 
  if (! apr_is_empty_array(txnprop_list))
8618
 
    SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
8619
 
 
8620
 
  /* Create the shard for the rev and revprop file, if we're sharding and
8621
 
     this is the first revision of a new shard.  We don't care if this
8622
 
     fails because the shard already existed for some reason. */
8623
 
  if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
8624
 
    {
8625
 
      /* Create the revs shard. */
8626
 
        {
8627
 
          const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
8628
 
          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8629
 
          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8630
 
            return svn_error_trace(err);
8631
 
          svn_error_clear(err);
8632
 
          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8633
 
                                                    PATH_REVS_DIR,
8634
 
                                                    pool),
8635
 
                                    new_dir, pool));
8636
 
        }
8637
 
 
8638
 
      /* Create the revprops shard. */
8639
 
      SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8640
 
        {
8641
 
          const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
8642
 
          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8643
 
          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8644
 
            return svn_error_trace(err);
8645
 
          svn_error_clear(err);
8646
 
          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8647
 
                                                    PATH_REVPROPS_DIR,
8648
 
                                                    pool),
8649
 
                                    new_dir, pool));
8650
 
        }
8651
 
    }
8652
 
 
8653
 
  /* Move the finished rev file into place. */
8654
 
  SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
8655
 
                                       cb->fs, old_rev, pool));
8656
 
  rev_filename = path_rev(cb->fs, new_rev, pool);
8657
 
  proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
8658
 
  SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
8659
 
                          pool));
8660
 
 
8661
 
  /* Now that we've moved the prototype revision file out of the way,
8662
 
     we can unlock it (since further attempts to write to the file
8663
 
     will fail as it no longer exists).  We must do this so that we can
8664
 
     remove the transaction directory later. */
8665
 
  SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
8666
 
 
8667
 
  /* Update commit time to ensure that svn:date revprops remain ordered. */
8668
 
  date.data = svn_time_to_cstring(apr_time_now(), pool);
8669
 
  date.len = strlen(date.data);
8670
 
 
8671
 
  SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
8672
 
                                     &date, pool));
8673
 
 
8674
 
  /* Move the revprops file into place. */
8675
 
  SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8676
 
  revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
8677
 
  final_revprop = path_revprops(cb->fs, new_rev, pool);
8678
 
  SVN_ERR(move_into_place(revprop_filename, final_revprop,
8679
 
                          old_rev_filename, pool));
8680
 
 
8681
 
  /* Update the 'current' file. */
8682
 
  SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
8683
 
  SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
8684
 
                              start_copy_id, pool));
8685
 
 
8686
 
  /* At this point the new revision is committed and globally visible
8687
 
     so let the caller know it succeeded by giving it the new revision
8688
 
     number, which fulfills svn_fs_commit_txn() contract.  Any errors
8689
 
     after this point do not change the fact that a new revision was
8690
 
     created. */
8691
 
  *cb->new_rev_p = new_rev;
8692
 
 
8693
 
  ffd->youngest_rev_cache = new_rev;
8694
 
 
8695
 
  /* Remove this transaction directory. */
8696
 
  SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
8697
 
 
8698
 
  return SVN_NO_ERROR;
8699
 
}
8700
 
 
8701
 
/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
8702
 
 * to the rep-cache database of FS. */
8703
 
static svn_error_t *
8704
 
write_reps_to_cache(svn_fs_t *fs,
8705
 
                    const apr_array_header_t *reps_to_cache,
 
1590
  return apr_pmemdup(pool, rep, sizeof(*rep));
 
1591
}
 
1592
 
 
1593
 
 
1594
/* Write out the zeroth revision for filesystem FS.
 
1595
   Perform temporary allocations in SCRATCH_POOL. */
 
1596
static svn_error_t *
 
1597
write_revision_zero(svn_fs_t *fs,
8706
1598
                    apr_pool_t *scratch_pool)
8707
1599
{
8708
 
  int i;
8709
 
 
8710
 
  for (i = 0; i < reps_to_cache->nelts; i++)
8711
 
    {
8712
 
      representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
8713
 
 
8714
 
      /* FALSE because we don't care if another parallel commit happened to
8715
 
       * collide with us.  (Non-parallel collisions will not be detected.) */
8716
 
      SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
8717
 
    }
8718
 
 
8719
 
  return SVN_NO_ERROR;
8720
 
}
8721
 
 
8722
 
svn_error_t *
8723
 
svn_fs_fs__commit(svn_revnum_t *new_rev_p,
8724
 
                  svn_fs_t *fs,
8725
 
                  svn_fs_txn_t *txn,
8726
 
                  apr_pool_t *pool)
8727
 
{
8728
 
  struct commit_baton cb;
8729
 
  fs_fs_data_t *ffd = fs->fsap_data;
8730
 
 
8731
 
  cb.new_rev_p = new_rev_p;
8732
 
  cb.fs = fs;
8733
 
  cb.txn = txn;
8734
 
 
8735
 
  if (ffd->rep_sharing_allowed)
8736
 
    {
8737
 
      cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
8738
 
      cb.reps_hash = apr_hash_make(pool);
8739
 
      cb.reps_pool = pool;
8740
 
    }
8741
 
  else
8742
 
    {
8743
 
      cb.reps_to_cache = NULL;
8744
 
      cb.reps_hash = NULL;
8745
 
      cb.reps_pool = NULL;
8746
 
    }
8747
 
 
8748
 
  SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
8749
 
 
8750
 
  /* At this point, *NEW_REV_P has been set, so errors below won't affect
8751
 
     the success of the commit.  (See svn_fs_commit_txn().)  */
8752
 
 
8753
 
  if (ffd->rep_sharing_allowed)
8754
 
    {
8755
 
      SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
8756
 
 
8757
 
      /* Write new entries to the rep-sharing database.
8758
 
       *
8759
 
       * We use an sqlite transaction to speed things up;
8760
 
       * see <http://www.sqlite.org/faq.html#q19>.
8761
 
       */
8762
 
      SVN_SQLITE__WITH_TXN(
8763
 
        write_reps_to_cache(fs, cb.reps_to_cache, pool),
8764
 
        ffd->rep_cache_db);
8765
 
    }
8766
 
 
8767
 
  return SVN_NO_ERROR;
8768
 
}
8769
 
 
8770
 
 
8771
 
svn_error_t *
8772
 
svn_fs_fs__reserve_copy_id(const char **copy_id_p,
8773
 
                           svn_fs_t *fs,
8774
 
                           const char *txn_id,
8775
 
                           apr_pool_t *pool)
8776
 
{
8777
 
  const char *cur_node_id, *cur_copy_id;
8778
 
  char *copy_id;
8779
 
  apr_size_t len;
8780
 
 
8781
 
  /* First read in the current next-ids file. */
8782
 
  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
8783
 
 
8784
 
  copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
8785
 
 
8786
 
  len = strlen(cur_copy_id);
8787
 
  svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
8788
 
 
8789
 
  SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
8790
 
 
8791
 
  *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
8792
 
 
8793
 
  return SVN_NO_ERROR;
8794
 
}
8795
 
 
8796
 
/* Write out the zeroth revision for filesystem FS. */
8797
 
static svn_error_t *
8798
 
write_revision_zero(svn_fs_t *fs)
8799
 
{
8800
 
  const char *path_revision_zero = path_rev(fs, 0, fs->pool);
 
1600
  /* Use an explicit sub-pool to have full control over temp file lifetimes.
 
1601
   * Since we have it, use it for everything else as well. */
 
1602
  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
1603
  const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool);
8801
1604
  apr_hash_t *proplist;
8802
1605
  svn_string_t date;
8803
1606
 
8804
1607
  /* Write out a rev file for revision 0. */
8805
 
  SVN_ERR(svn_io_file_create(path_revision_zero,
8806
 
                             "PLAIN\nEND\nENDREP\n"
8807
 
                             "id: 0.0.r0/17\n"
8808
 
                             "type: dir\n"
8809
 
                             "count: 0\n"
8810
 
                             "text: 0 0 4 4 "
8811
 
                             "2d2977d1c96f487abe4a1e202dd03b4e\n"
8812
 
                             "cpath: /\n"
8813
 
                             "\n\n17 107\n", fs->pool));
8814
 
  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
 
1608
  if (svn_fs_fs__use_log_addressing(fs))
 
1609
    {
 
1610
      apr_array_header_t *index_entries;
 
1611
      svn_fs_fs__p2l_entry_t *entry;
 
1612
      svn_fs_fs__revision_file_t *rev_file;
 
1613
      const char *l2p_proto_index, *p2l_proto_index;
 
1614
 
 
1615
      /* Write a skeleton r0 with no indexes. */
 
1616
      SVN_ERR(svn_io_file_create(path_revision_zero,
 
1617
                    "PLAIN\nEND\nENDREP\n"
 
1618
                    "id: 0.0.r0/2\n"
 
1619
                    "type: dir\n"
 
1620
                    "count: 0\n"
 
1621
                    "text: 0 3 4 4 "
 
1622
                    "2d2977d1c96f487abe4a1e202dd03b4e\n"
 
1623
                    "cpath: /\n"
 
1624
                    "\n\n", subpool));
 
1625
 
 
1626
      /* Construct the index P2L contents: describe the 3 items we have.
 
1627
         Be sure to create them in on-disk order. */
 
1628
      index_entries = apr_array_make(subpool, 3, sizeof(entry));
 
1629
 
 
1630
      entry = apr_pcalloc(subpool, sizeof(*entry));
 
1631
      entry->offset = 0;
 
1632
      entry->size = 17;
 
1633
      entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP;
 
1634
      entry->item.revision = 0;
 
1635
      entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
 
1636
      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
 
1637
 
 
1638
      entry = apr_pcalloc(subpool, sizeof(*entry));
 
1639
      entry->offset = 17;
 
1640
      entry->size = 89;
 
1641
      entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV;
 
1642
      entry->item.revision = 0;
 
1643
      entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
 
1644
      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
 
1645
 
 
1646
      entry = apr_pcalloc(subpool, sizeof(*entry));
 
1647
      entry->offset = 106;
 
1648
      entry->size = 1;
 
1649
      entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES;
 
1650
      entry->item.revision = 0;
 
1651
      entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
 
1652
      APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
 
1653
 
 
1654
      /* Now re-open r0, create proto-index files from our entries and
 
1655
         rewrite the index section of r0. */
 
1656
      SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0,
 
1657
                                                        subpool, subpool));
 
1658
      SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
 
1659
                                                    rev_file, index_entries,
 
1660
                                                    subpool, subpool));
 
1661
      SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
 
1662
                                                    index_entries,
 
1663
                                                    subpool, subpool));
 
1664
      SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
 
1665
                                        p2l_proto_index, 0, subpool));
 
1666
      SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
 
1667
    }
 
1668
  else
 
1669
    SVN_ERR(svn_io_file_create(path_revision_zero,
 
1670
                               "PLAIN\nEND\nENDREP\n"
 
1671
                               "id: 0.0.r0/17\n"
 
1672
                               "type: dir\n"
 
1673
                               "count: 0\n"
 
1674
                               "text: 0 0 4 4 "
 
1675
                               "2d2977d1c96f487abe4a1e202dd03b4e\n"
 
1676
                               "cpath: /\n"
 
1677
                               "\n\n17 107\n", subpool));
 
1678
 
 
1679
  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool));
8815
1680
 
8816
1681
  /* Set a date on revision 0. */
8817
 
  date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
 
1682
  date.data = svn_time_to_cstring(apr_time_now(), subpool);
8818
1683
  date.len = strlen(date.data);
8819
 
  proplist = apr_hash_make(fs->pool);
 
1684
  proplist = apr_hash_make(subpool);
8820
1685
  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
8821
 
  return set_revision_proplist(fs, 0, proplist, fs->pool);
 
1686
  SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool));
 
1687
 
 
1688
  svn_pool_destroy(subpool);
 
1689
  return SVN_NO_ERROR;
8822
1690
}
8823
1691
 
8824
1692
svn_error_t *
8825
 
svn_fs_fs__create(svn_fs_t *fs,
8826
 
                  const char *path,
8827
 
                  apr_pool_t *pool)
 
1693
svn_fs_fs__create_file_tree(svn_fs_t *fs,
 
1694
                            const char *path,
 
1695
                            int format,
 
1696
                            int shard_size,
 
1697
                            svn_boolean_t use_log_addressing,
 
1698
                            apr_pool_t *pool)
8828
1699
{
8829
 
  int format = SVN_FS_FS__FORMAT_NUMBER;
8830
1700
  fs_fs_data_t *ffd = fs->fsap_data;
8831
1701
 
8832
 
  fs->path = apr_pstrdup(pool, path);
8833
 
  /* See if compatibility with older versions was explicitly requested. */
8834
 
  if (fs->config)
8835
 
    {
8836
 
      if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
8837
 
        format = 1;
8838
 
      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
8839
 
        format = 2;
8840
 
      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
8841
 
        format = 3;
8842
 
      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
8843
 
        format = 4;
8844
 
    }
 
1702
  fs->path = apr_pstrdup(fs->pool, path);
8845
1703
  ffd->format = format;
8846
1704
 
8847
 
  /* Override the default linear layout if this is a new-enough format. */
 
1705
  /* Use an appropriate sharding mode if supported by the format. */
8848
1706
  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
8849
 
    ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
 
1707
    ffd->max_files_per_dir = shard_size;
 
1708
  else
 
1709
    ffd->max_files_per_dir = 0;
 
1710
 
 
1711
  /* Select the addressing mode depending on the format. */
 
1712
  if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
 
1713
    ffd->use_log_addressing = use_log_addressing;
 
1714
  else
 
1715
    ffd->use_log_addressing = FALSE;
8850
1716
 
8851
1717
  /* Create the revision data directories. */
8852
1718
  if (ffd->max_files_per_dir)
8853
 
    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
 
1719
    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
 
1720
                                                                  pool),
 
1721
                                        pool));
8854
1722
  else
8855
1723
    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
8856
1724
                                                        pool),
8858
1726
 
8859
1727
  /* Create the revprops directory. */
8860
1728
  if (ffd->max_files_per_dir)
8861
 
    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
 
1729
    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0,
 
1730
                                                                       pool),
8862
1731
                                        pool));
8863
1732
  else
8864
1733
    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
8867
1736
                                        pool));
8868
1737
 
8869
1738
  /* Create the transaction directory. */
8870
 
  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
8871
 
                                                      pool),
 
1739
  SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txns_dir(fs, pool),
8872
1740
                                      pool));
8873
1741
 
8874
1742
  /* Create the protorevs directory. */
8875
1743
  if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
8876
 
    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
8877
 
                                                      pool),
 
1744
    SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txn_proto_revs(fs,
 
1745
                                                                       pool),
8878
1746
                                        pool));
8879
1747
 
8880
1748
  /* Create the 'current' file. */
8881
 
  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
8882
 
                             (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
8883
 
                              ? "0\n" : "0 1 1\n"),
8884
 
                             pool));
8885
 
  SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
8886
 
  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
 
1749
  SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_current(fs, pool), pool));
 
1750
  SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, pool));
8887
1751
 
8888
 
  SVN_ERR(write_revision_zero(fs));
 
1752
  /* Create the 'uuid' file. */
 
1753
  SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_lock(fs, pool), pool));
 
1754
  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, NULL, pool));
8889
1755
 
8890
1756
  /* Create the fsfs.conf file if supported.  Older server versions would
8891
1757
     simply ignore the file but that might result in a different behavior
8894
1760
  if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
8895
1761
    SVN_ERR(write_config(fs, pool));
8896
1762
 
8897
 
  SVN_ERR(read_config(ffd, fs->path, pool));
 
1763
  SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
 
1764
 
 
1765
  /* Global configuration options. */
 
1766
  SVN_ERR(read_global_config(fs));
 
1767
 
 
1768
  /* Add revision 0. */
 
1769
  SVN_ERR(write_revision_zero(fs, pool));
8898
1770
 
8899
1771
  /* Create the min unpacked rev file. */
8900
1772
  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
8901
 
    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
 
1773
    SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
 
1774
                               "0\n", pool));
8902
1775
 
8903
1776
  /* Create the txn-current file if the repository supports
8904
1777
     the transaction sequence file. */
8905
1778
  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
8906
1779
    {
8907
 
      SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
 
1780
      SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool),
8908
1781
                                 "0\n", pool));
8909
 
      SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
8910
 
                                 "", pool));
8911
 
    }
 
1782
      SVN_ERR(svn_io_file_create_empty(
 
1783
                                 svn_fs_fs__path_txn_current_lock(fs, pool),
 
1784
                                 pool));
 
1785
    }
 
1786
 
 
1787
  ffd->youngest_rev_cache = 0;
 
1788
  return SVN_NO_ERROR;
 
1789
}
 
1790
 
 
1791
svn_error_t *
 
1792
svn_fs_fs__create(svn_fs_t *fs,
 
1793
                  const char *path,
 
1794
                  apr_pool_t *pool)
 
1795
{
 
1796
  int format = SVN_FS_FS__FORMAT_NUMBER;
 
1797
  int shard_size = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
 
1798
  svn_boolean_t log_addressing;
 
1799
 
 
1800
  /* Process the given filesystem config. */
 
1801
  if (fs->config)
 
1802
    {
 
1803
      svn_version_t *compatible_version;
 
1804
      const char *shard_size_str;
 
1805
      SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
 
1806
                                         pool));
 
1807
 
 
1808
      /* select format number */
 
1809
      switch(compatible_version->minor)
 
1810
        {
 
1811
          case 0: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
 
1812
                 _("FSFS is not compatible with Subversion prior to 1.1"));
 
1813
 
 
1814
          case 1:
 
1815
          case 2:
 
1816
          case 3: format = 1;
 
1817
                  break;
 
1818
 
 
1819
          case 4: format = 2;
 
1820
                  break;
 
1821
 
 
1822
          case 5: format = 3;
 
1823
                  break;
 
1824
 
 
1825
          case 6:
 
1826
          case 7: format = 4;
 
1827
                  break;
 
1828
 
 
1829
          case 8: format = 6;
 
1830
                  break;
 
1831
 
 
1832
          default:format = SVN_FS_FS__FORMAT_NUMBER;
 
1833
        }
 
1834
 
 
1835
      shard_size_str = svn_hash_gets(fs->config, SVN_FS_CONFIG_FSFS_SHARD_SIZE);
 
1836
      if (shard_size_str)
 
1837
        {
 
1838
          apr_int64_t val;
 
1839
          SVN_ERR(svn_cstring_strtoi64(&val, shard_size_str, 0,
 
1840
                                       APR_INT32_MAX, 10));
 
1841
 
 
1842
          shard_size = (int) val;
 
1843
        }
 
1844
    }
 
1845
 
 
1846
  log_addressing = svn_hash__get_bool(fs->config,
 
1847
                                      SVN_FS_CONFIG_FSFS_LOG_ADDRESSING,
 
1848
                                      TRUE);
 
1849
 
 
1850
  /* Actual FS creation. */
 
1851
  SVN_ERR(svn_fs_fs__create_file_tree(fs, path, format, shard_size,
 
1852
                                      log_addressing, pool));
8912
1853
 
8913
1854
  /* This filesystem is ready.  Stamp it with a format number. */
8914
 
  SVN_ERR(write_format(path_format(fs, pool),
8915
 
                       ffd->format, ffd->max_files_per_dir, FALSE, pool));
8916
 
 
8917
 
  ffd->youngest_rev_cache = 0;
8918
 
  return SVN_NO_ERROR;
8919
 
}
8920
 
 
8921
 
/* Part of the recovery procedure.  Return the largest revision *REV in
8922
 
   filesystem FS.  Use POOL for temporary allocation. */
8923
 
static svn_error_t *
8924
 
recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
8925
 
{
8926
 
  /* Discovering the largest revision in the filesystem would be an
8927
 
     expensive operation if we did a readdir() or searched linearly,
8928
 
     so we'll do a form of binary search.  left is a revision that we
8929
 
     know exists, right a revision that we know does not exist. */
8930
 
  apr_pool_t *iterpool;
8931
 
  svn_revnum_t left, right = 1;
8932
 
 
8933
 
  iterpool = svn_pool_create(pool);
8934
 
  /* Keep doubling right, until we find a revision that doesn't exist. */
8935
 
  while (1)
8936
 
    {
8937
 
      svn_error_t *err;
8938
 
      apr_file_t *file;
8939
 
 
8940
 
      err = open_pack_or_rev_file(&file, fs, right, iterpool);
8941
 
      svn_pool_clear(iterpool);
8942
 
 
8943
 
      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8944
 
        {
8945
 
          svn_error_clear(err);
8946
 
          break;
8947
 
        }
8948
 
      else
8949
 
        SVN_ERR(err);
8950
 
 
8951
 
      right <<= 1;
8952
 
    }
8953
 
 
8954
 
  left = right >> 1;
8955
 
 
8956
 
  /* We know that left exists and right doesn't.  Do a normal bsearch to find
8957
 
     the last revision. */
8958
 
  while (left + 1 < right)
8959
 
    {
8960
 
      svn_revnum_t probe = left + ((right - left) / 2);
8961
 
      svn_error_t *err;
8962
 
      apr_file_t *file;
8963
 
 
8964
 
      err = open_pack_or_rev_file(&file, fs, probe, iterpool);
8965
 
      svn_pool_clear(iterpool);
8966
 
 
8967
 
      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8968
 
        {
8969
 
          svn_error_clear(err);
8970
 
          right = probe;
8971
 
        }
8972
 
      else
8973
 
        {
8974
 
          SVN_ERR(err);
8975
 
          left = probe;
8976
 
        }
8977
 
    }
8978
 
 
8979
 
  svn_pool_destroy(iterpool);
8980
 
 
8981
 
  /* left is now the largest revision that exists. */
8982
 
  *rev = left;
8983
 
  return SVN_NO_ERROR;
8984
 
}
8985
 
 
8986
 
/* A baton for reading a fixed amount from an open file.  For
8987
 
   recover_find_max_ids() below. */
8988
 
struct recover_read_from_file_baton
8989
 
{
8990
 
  apr_file_t *file;
8991
 
  apr_pool_t *pool;
8992
 
  apr_off_t remaining;
8993
 
};
8994
 
 
8995
 
/* A stream read handler used by recover_find_max_ids() below.
8996
 
   Read and return at most BATON->REMAINING bytes from the stream,
8997
 
   returning nothing after that to indicate EOF. */
8998
 
static svn_error_t *
8999
 
read_handler_recover(void *baton, char *buffer, apr_size_t *len)
9000
 
{
9001
 
  struct recover_read_from_file_baton *b = baton;
9002
 
  svn_filesize_t bytes_to_read = *len;
9003
 
 
9004
 
  if (b->remaining == 0)
9005
 
    {
9006
 
      /* Return a successful read of zero bytes to signal EOF. */
9007
 
      *len = 0;
9008
 
      return SVN_NO_ERROR;
9009
 
    }
9010
 
 
9011
 
  if (bytes_to_read > b->remaining)
9012
 
    bytes_to_read = b->remaining;
9013
 
  b->remaining -= bytes_to_read;
9014
 
 
9015
 
  return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
9016
 
                                len, NULL, b->pool);
9017
 
}
9018
 
 
9019
 
/* Part of the recovery procedure.  Read the directory noderev at offset
9020
 
   OFFSET of file REV_FILE (the revision file of revision REV of
9021
 
   filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
9022
 
   and copy-id of that node, if greater than the current value stored
9023
 
   in either.  Recurse into any child directories that were modified in
9024
 
   this revision.
9025
 
 
9026
 
   MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
9027
 
 
9028
 
   Perform temporary allocation in POOL. */
9029
 
static svn_error_t *
9030
 
recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
9031
 
                     apr_file_t *rev_file, apr_off_t offset,
9032
 
                     char *max_node_id, char *max_copy_id,
9033
 
                     apr_pool_t *pool)
9034
 
{
9035
 
  apr_hash_t *headers;
9036
 
  char *value;
9037
 
  representation_t *data_rep;
9038
 
  struct rep_args *ra;
9039
 
  struct recover_read_from_file_baton baton;
9040
 
  svn_stream_t *stream;
9041
 
  apr_hash_t *entries;
9042
 
  apr_hash_index_t *hi;
9043
 
  apr_pool_t *iterpool;
9044
 
 
9045
 
  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9046
 
  SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
9047
 
                                                               pool),
9048
 
                            pool));
9049
 
 
9050
 
  /* Check that this is a directory.  It should be. */
9051
 
  value = svn_hash_gets(headers, HEADER_TYPE);
9052
 
  if (value == NULL || strcmp(value, KIND_DIR) != 0)
9053
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9054
 
                            _("Recovery encountered a non-directory node"));
9055
 
 
9056
 
  /* Get the data location.  No data location indicates an empty directory. */
9057
 
  value = svn_hash_gets(headers, HEADER_TEXT);
9058
 
  if (!value)
9059
 
    return SVN_NO_ERROR;
9060
 
  SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
9061
 
 
9062
 
  /* If the directory's data representation wasn't changed in this revision,
9063
 
     we've already scanned the directory's contents for noderevs, so we don't
9064
 
     need to again.  This will occur if a property is changed on a directory
9065
 
     without changing the directory's contents. */
9066
 
  if (data_rep->revision != rev)
9067
 
    return SVN_NO_ERROR;
9068
 
 
9069
 
  /* We could use get_dir_contents(), but this is much cheaper.  It does
9070
 
     rely on directory entries being stored as PLAIN reps, though. */
9071
 
  offset = data_rep->offset;
9072
 
  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9073
 
  SVN_ERR(read_rep_line(&ra, rev_file, pool));
9074
 
  if (ra->is_delta)
9075
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9076
 
                            _("Recovery encountered a deltified directory "
9077
 
                              "representation"));
9078
 
 
9079
 
  /* Now create a stream that's allowed to read only as much data as is
9080
 
     stored in the representation. */
9081
 
  baton.file = rev_file;
9082
 
  baton.pool = pool;
9083
 
  baton.remaining = data_rep->expanded_size
9084
 
                  ? data_rep->expanded_size
9085
 
                  : data_rep->size;
9086
 
  stream = svn_stream_create(&baton, pool);
9087
 
  svn_stream_set_read(stream, read_handler_recover);
9088
 
 
9089
 
  /* Now read the entries from that stream. */
9090
 
  entries = apr_hash_make(pool);
9091
 
  SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
9092
 
  SVN_ERR(svn_stream_close(stream));
9093
 
 
9094
 
  /* Now check each of the entries in our directory to find new node and
9095
 
     copy ids, and recurse into new subdirectories. */
9096
 
  iterpool = svn_pool_create(pool);
9097
 
  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
9098
 
    {
9099
 
      char *str_val;
9100
 
      char *str;
9101
 
      svn_node_kind_t kind;
9102
 
      svn_fs_id_t *id;
9103
 
      const char *node_id, *copy_id;
9104
 
      apr_off_t child_dir_offset;
9105
 
      const svn_string_t *path = svn__apr_hash_index_val(hi);
9106
 
 
9107
 
      svn_pool_clear(iterpool);
9108
 
 
9109
 
      str_val = apr_pstrdup(iterpool, path->data);
9110
 
 
9111
 
      str = svn_cstring_tokenize(" ", &str_val);
9112
 
      if (str == NULL)
9113
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9114
 
                                _("Directory entry corrupt"));
9115
 
 
9116
 
      if (strcmp(str, KIND_FILE) == 0)
9117
 
        kind = svn_node_file;
9118
 
      else if (strcmp(str, KIND_DIR) == 0)
9119
 
        kind = svn_node_dir;
9120
 
      else
9121
 
        {
9122
 
          return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9123
 
                                  _("Directory entry corrupt"));
9124
 
        }
9125
 
 
9126
 
      str = svn_cstring_tokenize(" ", &str_val);
9127
 
      if (str == NULL)
9128
 
        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9129
 
                                _("Directory entry corrupt"));
9130
 
 
9131
 
      id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
9132
 
 
9133
 
      if (svn_fs_fs__id_rev(id) != rev)
9134
 
        {
9135
 
          /* If the node wasn't modified in this revision, we've already
9136
 
             checked the node and copy id. */
9137
 
          continue;
9138
 
        }
9139
 
 
9140
 
      node_id = svn_fs_fs__id_node_id(id);
9141
 
      copy_id = svn_fs_fs__id_copy_id(id);
9142
 
 
9143
 
      if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
9144
 
        {
9145
 
          SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
9146
 
          apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
9147
 
        }
9148
 
      if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
9149
 
        {
9150
 
          SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
9151
 
          apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
9152
 
        }
9153
 
 
9154
 
      if (kind == svn_node_file)
9155
 
        continue;
9156
 
 
9157
 
      child_dir_offset = svn_fs_fs__id_offset(id);
9158
 
      SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
9159
 
                                   max_node_id, max_copy_id, iterpool));
9160
 
    }
9161
 
  svn_pool_destroy(iterpool);
9162
 
 
9163
 
  return SVN_NO_ERROR;
9164
 
}
9165
 
 
9166
 
/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
9167
 
 * Use POOL for temporary allocations.
9168
 
 * Set *MISSING, if the reason is a missing manifest or pack file.
9169
 
 */
9170
 
static svn_boolean_t
9171
 
packed_revprop_available(svn_boolean_t *missing,
9172
 
                         svn_fs_t *fs,
9173
 
                         svn_revnum_t revision,
9174
 
                         apr_pool_t *pool)
9175
 
{
9176
 
  fs_fs_data_t *ffd = fs->fsap_data;
9177
 
  svn_stringbuf_t *content = NULL;
9178
 
 
9179
 
  /* try to read the manifest file */
9180
 
  const char *folder = path_revprops_pack_shard(fs, revision, pool);
9181
 
  const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
9182
 
 
9183
 
  svn_error_t *err = try_stringbuf_from_file(&content,
9184
 
                                             missing,
9185
 
                                             manifest_path,
9186
 
                                             FALSE,
9187
 
                                             pool);
9188
 
 
9189
 
  /* if the manifest cannot be read, consider the pack files inaccessible
9190
 
   * even if the file itself exists. */
9191
 
  if (err)
9192
 
    {
9193
 
      svn_error_clear(err);
9194
 
      return FALSE;
9195
 
    }
9196
 
 
9197
 
  if (*missing)
9198
 
    return FALSE;
9199
 
 
9200
 
  /* parse manifest content until we find the entry for REVISION.
9201
 
   * Revision 0 is never packed. */
9202
 
  revision = revision < ffd->max_files_per_dir
9203
 
           ? revision - 1
9204
 
           : revision % ffd->max_files_per_dir;
9205
 
  while (content->data)
9206
 
    {
9207
 
      char *next = strchr(content->data, '\n');
9208
 
      if (next)
9209
 
        {
9210
 
          *next = 0;
9211
 
          ++next;
9212
 
        }
9213
 
 
9214
 
      if (revision-- == 0)
9215
 
        {
9216
 
          /* the respective pack file must exist (and be a file) */
9217
 
          svn_node_kind_t kind;
9218
 
          err = svn_io_check_path(svn_dirent_join(folder, content->data,
9219
 
                                                  pool),
9220
 
                                  &kind, pool);
9221
 
          if (err)
9222
 
            {
9223
 
              svn_error_clear(err);
9224
 
              return FALSE;
9225
 
            }
9226
 
 
9227
 
          *missing = kind == svn_node_none;
9228
 
          return kind == svn_node_file;
9229
 
        }
9230
 
 
9231
 
      content->data = next;
9232
 
    }
9233
 
 
9234
 
  return FALSE;
9235
 
}
9236
 
 
9237
 
/* Baton used for recover_body below. */
9238
 
struct recover_baton {
9239
 
  svn_fs_t *fs;
9240
 
  svn_cancel_func_t cancel_func;
9241
 
  void *cancel_baton;
9242
 
};
9243
 
 
9244
 
/* The work-horse for svn_fs_fs__recover, called with the FS
9245
 
   write lock.  This implements the svn_fs_fs__with_write_lock()
9246
 
   'body' callback type.  BATON is a 'struct recover_baton *'. */
9247
 
static svn_error_t *
9248
 
recover_body(void *baton, apr_pool_t *pool)
9249
 
{
9250
 
  struct recover_baton *b = baton;
9251
 
  svn_fs_t *fs = b->fs;
9252
 
  fs_fs_data_t *ffd = fs->fsap_data;
9253
 
  svn_revnum_t max_rev;
9254
 
  char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
9255
 
  char *next_node_id = NULL, *next_copy_id = NULL;
9256
 
  svn_revnum_t youngest_rev;
9257
 
  svn_node_kind_t youngest_revprops_kind;
9258
 
 
9259
 
  /* Lose potentially corrupted data in temp files */
9260
 
  SVN_ERR(cleanup_revprop_namespace(fs));
9261
 
 
9262
 
  /* We need to know the largest revision in the filesystem. */
9263
 
  SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
9264
 
 
9265
 
  /* Get the expected youngest revision */
9266
 
  SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
9267
 
 
9268
 
  /* Policy note:
9269
 
 
9270
 
     Since the revprops file is written after the revs file, the true
9271
 
     maximum available revision is the youngest one for which both are
9272
 
     present.  That's probably the same as the max_rev we just found,
9273
 
     but if it's not, we could, in theory, repeatedly decrement
9274
 
     max_rev until we find a revision that has both a revs and
9275
 
     revprops file, then write db/current with that.
9276
 
 
9277
 
     But we choose not to.  If a repository is so corrupt that it's
9278
 
     missing at least one revprops file, we shouldn't assume that the
9279
 
     youngest revision for which both the revs and revprops files are
9280
 
     present is healthy.  In other words, we're willing to recover
9281
 
     from a missing or out-of-date db/current file, because db/current
9282
 
     is truly redundant -- it's basically a cache so we don't have to
9283
 
     find max_rev each time, albeit a cache with unusual semantics,
9284
 
     since it also officially defines when a revision goes live.  But
9285
 
     if we're missing more than the cache, it's time to back out and
9286
 
     let the admin reconstruct things by hand: correctness at that
9287
 
     point may depend on external things like checking a commit email
9288
 
     list, looking in particular working copies, etc.
9289
 
 
9290
 
     This policy matches well with a typical naive backup scenario.
9291
 
     Say you're rsyncing your FSFS repository nightly to the same
9292
 
     location.  Once revs and revprops are written, you've got the
9293
 
     maximum rev; if the backup should bomb before db/current is
9294
 
     written, then db/current could stay arbitrarily out-of-date, but
9295
 
     we can still recover.  It's a small window, but we might as well
9296
 
     do what we can. */
9297
 
 
9298
 
  /* Even if db/current were missing, it would be created with 0 by
9299
 
     get_youngest(), so this conditional remains valid. */
9300
 
  if (youngest_rev > max_rev)
9301
 
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9302
 
                             _("Expected current rev to be <= %ld "
9303
 
                               "but found %ld"), max_rev, youngest_rev);
9304
 
 
9305
 
  /* We only need to search for maximum IDs for old FS formats which
9306
 
     se global ID counters. */
9307
 
  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
9308
 
    {
9309
 
      /* Next we need to find the maximum node id and copy id in use across the
9310
 
         filesystem.  Unfortunately, the only way we can get this information
9311
 
         is to scan all the noderevs of all the revisions and keep track as
9312
 
         we go along. */
9313
 
      svn_revnum_t rev;
9314
 
      apr_pool_t *iterpool = svn_pool_create(pool);
9315
 
      char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
9316
 
      apr_size_t len;
9317
 
 
9318
 
      for (rev = 0; rev <= max_rev; rev++)
9319
 
        {
9320
 
          apr_file_t *rev_file;
9321
 
          apr_off_t root_offset;
9322
 
 
9323
 
          svn_pool_clear(iterpool);
9324
 
 
9325
 
          if (b->cancel_func)
9326
 
            SVN_ERR(b->cancel_func(b->cancel_baton));
9327
 
 
9328
 
          SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
9329
 
          SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
9330
 
                                          iterpool));
9331
 
          SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
9332
 
                                       max_node_id, max_copy_id, iterpool));
9333
 
          SVN_ERR(svn_io_file_close(rev_file, iterpool));
9334
 
        }
9335
 
      svn_pool_destroy(iterpool);
9336
 
 
9337
 
      /* Now that we finally have the maximum revision, node-id and copy-id, we
9338
 
         can bump the two ids to get the next of each. */
9339
 
      len = strlen(max_node_id);
9340
 
      svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
9341
 
      next_node_id = next_node_id_buf;
9342
 
      len = strlen(max_copy_id);
9343
 
      svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
9344
 
      next_copy_id = next_copy_id_buf;
9345
 
    }
9346
 
 
9347
 
  /* Before setting current, verify that there is a revprops file
9348
 
     for the youngest revision.  (Issue #2992) */
9349
 
  SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
9350
 
                            &youngest_revprops_kind, pool));
9351
 
  if (youngest_revprops_kind == svn_node_none)
9352
 
    {
9353
 
      svn_boolean_t missing = TRUE;
9354
 
      if (!packed_revprop_available(&missing, fs, max_rev, pool))
9355
 
        {
9356
 
          if (missing)
9357
 
            {
9358
 
              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9359
 
                                      _("Revision %ld has a revs file but no "
9360
 
                                        "revprops file"),
9361
 
                                      max_rev);
9362
 
            }
9363
 
          else
9364
 
            {
9365
 
              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9366
 
                                      _("Revision %ld has a revs file but the "
9367
 
                                        "revprops file is inaccessible"),
9368
 
                                      max_rev);
9369
 
            }
9370
 
          }
9371
 
    }
9372
 
  else if (youngest_revprops_kind != svn_node_file)
9373
 
    {
9374
 
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9375
 
                               _("Revision %ld has a non-file where its "
9376
 
                                 "revprops file should be"),
9377
 
                               max_rev);
9378
 
    }
9379
 
 
9380
 
  /* Prune younger-than-(newfound-youngest) revisions from the rep
9381
 
     cache if sharing is enabled taking care not to create the cache
9382
 
     if it does not exist. */
9383
 
  if (ffd->rep_sharing_allowed)
9384
 
    {
9385
 
      svn_boolean_t rep_cache_exists;
9386
 
 
9387
 
      SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
9388
 
      if (rep_cache_exists)
9389
 
        SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
9390
 
    }
9391
 
 
9392
 
  /* Now store the discovered youngest revision, and the next IDs if
9393
 
     relevant, in a new 'current' file. */
9394
 
  return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
9395
 
}
9396
 
 
9397
 
/* This implements the fs_library_vtable_t.recover() API. */
9398
 
svn_error_t *
9399
 
svn_fs_fs__recover(svn_fs_t *fs,
9400
 
                   svn_cancel_func_t cancel_func, void *cancel_baton,
9401
 
                   apr_pool_t *pool)
9402
 
{
9403
 
  struct recover_baton b;
9404
 
 
9405
 
  /* We have no way to take out an exclusive lock in FSFS, so we're
9406
 
     restricted as to the types of recovery we can do.  Luckily,
9407
 
     we just want to recreate the 'current' file, and we can do that just
9408
 
     by blocking other writers. */
9409
 
  b.fs = fs;
9410
 
  b.cancel_func = cancel_func;
9411
 
  b.cancel_baton = cancel_baton;
9412
 
  return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
 
1855
  SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
 
1856
 
 
1857
  return SVN_NO_ERROR;
9413
1858
}
9414
1859
 
9415
1860
svn_error_t *
9416
1861
svn_fs_fs__set_uuid(svn_fs_t *fs,
9417
1862
                    const char *uuid,
 
1863
                    const char *instance_id,
9418
1864
                    apr_pool_t *pool)
9419
1865
{
9420
 
  char *my_uuid;
9421
 
  apr_size_t my_uuid_len;
9422
 
  const char *tmp_path;
 
1866
  fs_fs_data_t *ffd = fs->fsap_data;
9423
1867
  const char *uuid_path = path_uuid(fs, pool);
 
1868
  svn_stringbuf_t *contents = svn_stringbuf_create_empty(pool);
9424
1869
 
9425
1870
  if (! uuid)
9426
1871
    uuid = svn_uuid_generate(pool);
9427
1872
 
9428
 
  /* Make sure we have a copy in FS->POOL, and append a newline. */
9429
 
  my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
9430
 
  my_uuid_len = strlen(my_uuid);
9431
 
 
9432
 
  SVN_ERR(svn_io_write_unique(&tmp_path,
9433
 
                              svn_dirent_dirname(uuid_path, pool),
9434
 
                              my_uuid, my_uuid_len,
9435
 
                              svn_io_file_del_none, pool));
 
1873
  if (! instance_id)
 
1874
    instance_id = svn_uuid_generate(pool);
 
1875
 
 
1876
  svn_stringbuf_appendcstr(contents, uuid);
 
1877
  svn_stringbuf_appendcstr(contents, "\n");
 
1878
 
 
1879
  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
 
1880
    {
 
1881
      svn_stringbuf_appendcstr(contents, instance_id);
 
1882
      svn_stringbuf_appendcstr(contents, "\n");
 
1883
    }
9436
1884
 
9437
1885
  /* We use the permissions of the 'current' file, because the 'uuid'
9438
1886
     file does not exist during repository creation. */
9439
 
  SVN_ERR(move_into_place(tmp_path, uuid_path,
9440
 
                          svn_fs_fs__path_current(fs, pool), pool));
9441
 
 
9442
 
  /* Remove the newline we added, and stash the UUID. */
9443
 
  my_uuid[my_uuid_len - 1] = '\0';
9444
 
  fs->uuid = my_uuid;
 
1887
  SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len,
 
1888
                              svn_fs_fs__path_current(fs, pool) /* perms */,
 
1889
                              pool));
 
1890
 
 
1891
  fs->uuid = apr_pstrdup(fs->pool, uuid);
 
1892
 
 
1893
  if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
 
1894
    ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
 
1895
  else
 
1896
    ffd->instance_id = fs->uuid;
9445
1897
 
9446
1898
  return SVN_NO_ERROR;
9447
1899
}
9492
1944
 
9493
1945
  stream = svn_stream_from_aprfile2(fd, FALSE, pool);
9494
1946
  *node_origins = apr_hash_make(pool);
9495
 
  SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
 
1947
  err = svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool);
 
1948
  if (err)
 
1949
    return svn_error_quick_wrapf(err, _("malformed node origin data in '%s'"),
 
1950
                                 node_origins_file);
9496
1951
  return svn_stream_close(stream);
9497
1952
}
9498
1953
 
9499
1954
svn_error_t *
9500
1955
svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
9501
1956
                           svn_fs_t *fs,
9502
 
                           const char *node_id,
 
1957
                           const svn_fs_fs__id_part_t *node_id,
9503
1958
                           apr_pool_t *pool)
9504
1959
{
9505
1960
  apr_hash_t *node_origins;
9506
1961
 
9507
1962
  *origin_id = NULL;
9508
1963
  SVN_ERR(get_node_origins_from_file(fs, &node_origins,
9509
 
                                     path_node_origin(fs, node_id, pool),
 
1964
                                     svn_fs_fs__path_node_origin(fs, node_id,
 
1965
                                                                 pool),
9510
1966
                                     pool));
9511
1967
  if (node_origins)
9512
1968
    {
9513
 
      svn_string_t *origin_id_str =
9514
 
        svn_hash_gets(node_origins, node_id);
 
1969
      char node_id_ptr[SVN_INT64_BUFFER_SIZE];
 
1970
      apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
 
1971
      svn_string_t *origin_id_str
 
1972
        = apr_hash_get(node_origins, node_id_ptr, len);
 
1973
 
9515
1974
      if (origin_id_str)
9516
 
        *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
9517
 
                                         origin_id_str->len, pool);
 
1975
        SVN_ERR(svn_fs_fs__id_parse(origin_id,
 
1976
                                    apr_pstrdup(pool, origin_id_str->data),
 
1977
                                    pool));
9518
1978
    }
9519
1979
  return SVN_NO_ERROR;
9520
1980
}
9525
1985
static svn_error_t *
9526
1986
set_node_origins_for_file(svn_fs_t *fs,
9527
1987
                          const char *node_origins_path,
9528
 
                          const char *node_id,
 
1988
                          const svn_fs_fs__id_part_t *node_id,
9529
1989
                          svn_string_t *node_rev_id,
9530
1990
                          apr_pool_t *pool)
9531
1991
{
9534
1994
  apr_hash_t *origins_hash;
9535
1995
  svn_string_t *old_node_rev_id;
9536
1996
 
 
1997
  /* the hash serialization functions require strings as keys */
 
1998
  char node_id_ptr[SVN_INT64_BUFFER_SIZE];
 
1999
  apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
 
2000
 
9537
2001
  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
9538
2002
                                                       PATH_NODE_ORIGINS_DIR,
9539
2003
                                                       pool),
9546
2010
  if (! origins_hash)
9547
2011
    origins_hash = apr_hash_make(pool);
9548
2012
 
9549
 
  old_node_rev_id = svn_hash_gets(origins_hash, node_id);
 
2013
  old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
9550
2014
 
9551
2015
  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
9552
2016
    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9553
2017
                             _("Node origin for '%s' exists with a different "
9554
2018
                               "value (%s) than what we were about to store "
9555
2019
                               "(%s)"),
9556
 
                             node_id, old_node_rev_id->data, node_rev_id->data);
 
2020
                             node_id_ptr, old_node_rev_id->data,
 
2021
                             node_rev_id->data);
9557
2022
 
9558
 
  svn_hash_sets(origins_hash, node_id, node_rev_id);
 
2023
  apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id);
9559
2024
 
9560
2025
  /* Sure, there's a race condition here.  Two processes could be
9561
2026
     trying to add different cache elements to the same file at the
9578
2043
 
9579
2044
svn_error_t *
9580
2045
svn_fs_fs__set_node_origin(svn_fs_t *fs,
9581
 
                           const char *node_id,
 
2046
                           const svn_fs_fs__id_part_t *node_id,
9582
2047
                           const svn_fs_id_t *node_rev_id,
9583
2048
                           apr_pool_t *pool)
9584
2049
{
9585
2050
  svn_error_t *err;
9586
 
  const char *filename = path_node_origin(fs, node_id, pool);
 
2051
  const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool);
9587
2052
 
9588
2053
  err = set_node_origins_for_file(fs, filename,
9589
2054
                                  node_id,
9599
2064
}
9600
2065
 
9601
2066
 
9602
 
svn_error_t *
9603
 
svn_fs_fs__list_transactions(apr_array_header_t **names_p,
9604
 
                             svn_fs_t *fs,
9605
 
                             apr_pool_t *pool)
9606
 
{
9607
 
  const char *txn_dir;
9608
 
  apr_hash_t *dirents;
9609
 
  apr_hash_index_t *hi;
9610
 
  apr_array_header_t *names;
9611
 
  apr_size_t ext_len = strlen(PATH_EXT_TXN);
9612
 
 
9613
 
  names = apr_array_make(pool, 1, sizeof(const char *));
9614
 
 
9615
 
  /* Get the transactions directory. */
9616
 
  txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
9617
 
 
9618
 
  /* Now find a listing of this directory. */
9619
 
  SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
9620
 
 
9621
 
  /* Loop through all the entries and return anything that ends with '.txn'. */
9622
 
  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
9623
 
    {
9624
 
      const char *name = svn__apr_hash_index_key(hi);
9625
 
      apr_ssize_t klen = svn__apr_hash_index_klen(hi);
9626
 
      const char *id;
9627
 
 
9628
 
      /* The name must end with ".txn" to be considered a transaction. */
9629
 
      if ((apr_size_t) klen <= ext_len
9630
 
          || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
9631
 
        continue;
9632
 
 
9633
 
      /* Truncate the ".txn" extension and store the ID. */
9634
 
      id = apr_pstrndup(pool, name, strlen(name) - ext_len);
9635
 
      APR_ARRAY_PUSH(names, const char *) = id;
9636
 
    }
9637
 
 
9638
 
  *names_p = names;
9639
 
 
9640
 
  return SVN_NO_ERROR;
9641
 
}
9642
 
 
9643
 
svn_error_t *
9644
 
svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
9645
 
                    svn_fs_t *fs,
9646
 
                    const char *name,
9647
 
                    apr_pool_t *pool)
9648
 
{
9649
 
  svn_fs_txn_t *txn;
9650
 
  svn_node_kind_t kind;
9651
 
  transaction_t *local_txn;
9652
 
 
9653
 
  /* First check to see if the directory exists. */
9654
 
  SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
9655
 
 
9656
 
  /* Did we find it? */
9657
 
  if (kind != svn_node_dir)
9658
 
    return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
9659
 
                             _("No such transaction '%s'"),
9660
 
                             name);
9661
 
 
9662
 
  txn = apr_pcalloc(pool, sizeof(*txn));
9663
 
 
9664
 
  /* Read in the root node of this transaction. */
9665
 
  txn->id = apr_pstrdup(pool, name);
9666
 
  txn->fs = fs;
9667
 
 
9668
 
  SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
9669
 
 
9670
 
  txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
9671
 
 
9672
 
  txn->vtable = &txn_vtable;
9673
 
  *txn_p = txn;
9674
 
 
9675
 
  return SVN_NO_ERROR;
9676
 
}
9677
 
 
9678
 
svn_error_t *
9679
 
svn_fs_fs__txn_proplist(apr_hash_t **table_p,
9680
 
                        svn_fs_txn_t *txn,
9681
 
                        apr_pool_t *pool)
9682
 
{
9683
 
  apr_hash_t *proplist = apr_hash_make(pool);
9684
 
  SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
9685
 
  *table_p = proplist;
9686
 
 
9687
 
  return SVN_NO_ERROR;
9688
 
}
9689
 
 
9690
 
svn_error_t *
9691
 
svn_fs_fs__delete_node_revision(svn_fs_t *fs,
9692
 
                                const svn_fs_id_t *id,
9693
 
                                apr_pool_t *pool)
9694
 
{
9695
 
  node_revision_t *noderev;
9696
 
 
9697
 
  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
9698
 
 
9699
 
  /* Delete any mutable property representation. */
9700
 
  if (noderev->prop_rep && noderev->prop_rep->txn_id)
9701
 
    SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
9702
 
                                pool));
9703
 
 
9704
 
  /* Delete any mutable data representation. */
9705
 
  if (noderev->data_rep && noderev->data_rep->txn_id
9706
 
      && noderev->kind == svn_node_dir)
9707
 
    {
9708
 
      fs_fs_data_t *ffd = fs->fsap_data;
9709
 
      SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
9710
 
                                  pool));
9711
 
 
9712
 
      /* remove the corresponding entry from the cache, if such exists */
9713
 
      if (ffd->txn_dir_cache)
9714
 
        {
9715
 
          const char *key = svn_fs_fs__id_unparse(id, pool)->data;
9716
 
          SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
9717
 
        }
9718
 
    }
9719
 
 
9720
 
  return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
9721
 
}
9722
 
 
9723
 
 
9724
2067
 
9725
2068
/*** Revisions ***/
9726
2069
 
9734
2077
  apr_hash_t *table;
9735
2078
 
9736
2079
  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9737
 
  SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
 
2080
  SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, pool));
9738
2081
 
9739
2082
  *value_p = svn_hash_gets(table, propname);
9740
2083
 
9760
2103
  struct change_rev_prop_baton *cb = baton;
9761
2104
  apr_hash_t *table;
9762
2105
 
9763
 
  SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
 
2106
  SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, pool));
9764
2107
 
9765
2108
  if (cb->old_value_p)
9766
2109
    {
9780
2123
    }
9781
2124
  svn_hash_sets(table, cb->name, cb->value);
9782
2125
 
9783
 
  return set_revision_proplist(cb->fs, cb->rev, table, pool);
 
2126
  return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool);
9784
2127
}
9785
2128
 
9786
2129
svn_error_t *
9804
2147
  return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
9805
2148
}
9806
2149
 
9807
 
 
9808
2150
 
9809
 
/*** Transactions ***/
9810
 
 
9811
2151
svn_error_t *
9812
 
svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
9813
 
                       const svn_fs_id_t **base_root_id_p,
 
2152
svn_fs_fs__info_format(int *fs_format,
 
2153
                       svn_version_t **supports_version,
9814
2154
                       svn_fs_t *fs,
9815
 
                       const char *txn_name,
9816
 
                       apr_pool_t *pool)
9817
 
{
9818
 
  transaction_t *txn;
9819
 
  SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
9820
 
  *root_id_p = txn->root_id;
9821
 
  *base_root_id_p = txn->base_id;
9822
 
  return SVN_NO_ERROR;
9823
 
}
9824
 
 
9825
 
 
9826
 
/* Generic transaction operations.  */
9827
 
 
9828
 
svn_error_t *
9829
 
svn_fs_fs__txn_prop(svn_string_t **value_p,
9830
 
                    svn_fs_txn_t *txn,
9831
 
                    const char *propname,
9832
 
                    apr_pool_t *pool)
9833
 
{
9834
 
  apr_hash_t *table;
9835
 
  svn_fs_t *fs = txn->fs;
9836
 
 
9837
 
  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9838
 
  SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
9839
 
 
9840
 
  *value_p = svn_hash_gets(table, propname);
9841
 
 
9842
 
  return SVN_NO_ERROR;
9843
 
}
9844
 
 
9845
 
svn_error_t *
9846
 
svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
9847
 
                     svn_fs_t *fs,
9848
 
                     svn_revnum_t rev,
9849
 
                     apr_uint32_t flags,
9850
 
                     apr_pool_t *pool)
9851
 
{
9852
 
  svn_string_t date;
9853
 
  svn_prop_t prop;
9854
 
  apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
9855
 
 
9856
 
  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9857
 
 
9858
 
  SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
9859
 
 
9860
 
  /* Put a datestamp on the newly created txn, so we always know
9861
 
     exactly how old it is.  (This will help sysadmins identify
9862
 
     long-abandoned txns that may need to be manually removed.)  When
9863
 
     a txn is promoted to a revision, this property will be
9864
 
     automatically overwritten with a revision datestamp. */
9865
 
  date.data = svn_time_to_cstring(apr_time_now(), pool);
9866
 
  date.len = strlen(date.data);
9867
 
 
9868
 
  prop.name = SVN_PROP_REVISION_DATE;
9869
 
  prop.value = &date;
9870
 
  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9871
 
 
9872
 
  /* Set temporary txn props that represent the requested 'flags'
9873
 
     behaviors. */
9874
 
  if (flags & SVN_FS_TXN_CHECK_OOD)
9875
 
    {
9876
 
      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
9877
 
      prop.value = svn_string_create("true", pool);
9878
 
      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9879
 
    }
9880
 
 
9881
 
  if (flags & SVN_FS_TXN_CHECK_LOCKS)
9882
 
    {
9883
 
      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
9884
 
      prop.value = svn_string_create("true", pool);
9885
 
      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9886
 
    }
9887
 
 
9888
 
  return svn_fs_fs__change_txn_props(*txn_p, props, pool);
9889
 
}
9890
 
 
9891
 
 
9892
 
/****** Packing FSFS shards *********/
9893
 
 
9894
 
/* Write a file FILENAME in directory FS_PATH, containing a single line
9895
 
 * with the number REVNUM in ASCII decimal.  Move the file into place
9896
 
 * atomically, overwriting any existing file.
9897
 
 *
9898
 
 * Similar to write_current(). */
9899
 
static svn_error_t *
9900
 
write_revnum_file(const char *fs_path,
9901
 
                  const char *filename,
9902
 
                  svn_revnum_t revnum,
9903
 
                  apr_pool_t *scratch_pool)
9904
 
{
9905
 
  const char *final_path, *tmp_path;
9906
 
  svn_stream_t *tmp_stream;
9907
 
 
9908
 
  final_path = svn_dirent_join(fs_path, filename, scratch_pool);
9909
 
  SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
9910
 
                                   svn_io_file_del_none,
9911
 
                                   scratch_pool, scratch_pool));
9912
 
  SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
9913
 
  SVN_ERR(svn_stream_close(tmp_stream));
9914
 
  SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
9915
 
  return SVN_NO_ERROR;
9916
 
}
9917
 
 
9918
 
/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
9919
 
 * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
9920
 
 * CANCEL_FUNC and CANCEL_BATON are what you think they are.
9921
 
 *
9922
 
 * If for some reason we detect a partial packing already performed, we
9923
 
 * remove the pack file and start again.
9924
 
 */
9925
 
static svn_error_t *
9926
 
pack_rev_shard(const char *pack_file_dir,
9927
 
               const char *shard_path,
9928
 
               apr_int64_t shard,
9929
 
               int max_files_per_dir,
9930
 
               svn_cancel_func_t cancel_func,
9931
 
               void *cancel_baton,
9932
 
               apr_pool_t *pool)
9933
 
{
9934
 
  const char *pack_file_path, *manifest_file_path;
9935
 
  svn_stream_t *pack_stream, *manifest_stream;
9936
 
  svn_revnum_t start_rev, end_rev, rev;
9937
 
  apr_off_t next_offset;
9938
 
  apr_pool_t *iterpool;
9939
 
 
9940
 
  /* Some useful paths. */
9941
 
  pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
9942
 
  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
9943
 
 
9944
 
  /* Remove any existing pack file for this shard, since it is incomplete. */
9945
 
  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
9946
 
                             pool));
9947
 
 
9948
 
  /* Create the new directory and pack and manifest files. */
9949
 
  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
9950
 
  SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
9951
 
                                    pool));
9952
 
  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
9953
 
                                   pool, pool));
9954
 
 
9955
 
  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
9956
 
  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
9957
 
  next_offset = 0;
9958
 
  iterpool = svn_pool_create(pool);
9959
 
 
9960
 
  /* Iterate over the revisions in this shard, squashing them together. */
9961
 
  for (rev = start_rev; rev <= end_rev; rev++)
9962
 
    {
9963
 
      svn_stream_t *rev_stream;
9964
 
      apr_finfo_t finfo;
9965
 
      const char *path;
9966
 
 
9967
 
      svn_pool_clear(iterpool);
9968
 
 
9969
 
      /* Get the size of the file. */
9970
 
      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
9971
 
                             iterpool);
9972
 
      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
9973
 
 
9974
 
      /* Update the manifest. */
9975
 
      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
9976
 
                                "\n", next_offset));
9977
 
      next_offset += finfo.size;
9978
 
 
9979
 
      /* Copy all the bits from the rev file to the end of the pack file. */
9980
 
      SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
9981
 
      SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
9982
 
                                                             iterpool),
9983
 
                          cancel_func, cancel_baton, iterpool));
9984
 
    }
9985
 
 
9986
 
  SVN_ERR(svn_stream_close(manifest_stream));
9987
 
  SVN_ERR(svn_stream_close(pack_stream));
9988
 
  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
9989
 
  SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
9990
 
  SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
9991
 
 
9992
 
  svn_pool_destroy(iterpool);
9993
 
 
9994
 
  return SVN_NO_ERROR;
9995
 
}
9996
 
 
9997
 
/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
9998
 
 * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
9999
 
 *
10000
 
 * The file sizes have already been determined and written to SIZES.
10001
 
 * Please note that this function will be executed while the filesystem
10002
 
 * has been locked and that revprops files will therefore not be modified
10003
 
 * while the pack is in progress.
10004
 
 *
10005
 
 * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10006
 
 * compressed or whether is shall be compressed at all.  TOTAL_SIZE is
10007
 
 * a hint on which initial buffer size we should use to hold the pack file
10008
 
 * content.
10009
 
 *
10010
 
 * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
10011
 
 * are done in SCRATCH_POOL.
10012
 
 */
10013
 
static svn_error_t *
10014
 
copy_revprops(const char *pack_file_dir,
10015
 
              const char *pack_filename,
10016
 
              const char *shard_path,
10017
 
              svn_revnum_t start_rev,
10018
 
              svn_revnum_t end_rev,
10019
 
              apr_array_header_t *sizes,
10020
 
              apr_size_t total_size,
10021
 
              int compression_level,
10022
 
              svn_cancel_func_t cancel_func,
10023
 
              void *cancel_baton,
10024
 
              apr_pool_t *scratch_pool)
10025
 
{
10026
 
  svn_stream_t *pack_stream;
10027
 
  apr_file_t *pack_file;
10028
 
  svn_revnum_t rev;
10029
 
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10030
 
  svn_stream_t *stream;
10031
 
 
10032
 
  /* create empty data buffer and a write stream on top of it */
10033
 
  svn_stringbuf_t *uncompressed
10034
 
    = svn_stringbuf_create_ensure(total_size, scratch_pool);
10035
 
  svn_stringbuf_t *compressed
10036
 
    = svn_stringbuf_create_empty(scratch_pool);
10037
 
  pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
10038
 
 
10039
 
  /* write the pack file header */
10040
 
  SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
10041
 
                                    sizes->nelts, iterpool));
10042
 
 
10043
 
  /* Some useful paths. */
10044
 
  SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
10045
 
                                                       pack_filename,
10046
 
                                                       scratch_pool),
10047
 
                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
10048
 
                           scratch_pool));
10049
 
 
10050
 
  /* Iterate over the revisions in this shard, squashing them together. */
10051
 
  for (rev = start_rev; rev <= end_rev; rev++)
10052
 
    {
10053
 
      const char *path;
10054
 
 
10055
 
      svn_pool_clear(iterpool);
10056
 
 
10057
 
      /* Construct the file name. */
10058
 
      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10059
 
                             iterpool);
10060
 
 
10061
 
      /* Copy all the bits from the non-packed revprop file to the end of
10062
 
       * the pack file. */
10063
 
      SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
10064
 
      SVN_ERR(svn_stream_copy3(stream, pack_stream,
10065
 
                               cancel_func, cancel_baton, iterpool));
10066
 
    }
10067
 
 
10068
 
  /* flush stream buffers to content buffer */
10069
 
  SVN_ERR(svn_stream_close(pack_stream));
10070
 
 
10071
 
  /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
10072
 
  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
10073
 
                        compressed, compression_level));
10074
 
 
10075
 
  /* write the pack file content to disk */
10076
 
  stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
10077
 
  SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
10078
 
  SVN_ERR(svn_stream_close(stream));
10079
 
 
10080
 
  svn_pool_destroy(iterpool);
10081
 
 
10082
 
  return SVN_NO_ERROR;
10083
 
}
10084
 
 
10085
 
/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
10086
 
 * revprop files in it, create a packed shared at PACK_FILE_DIR.
10087
 
 *
10088
 
 * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10089
 
 * compressed or whether is shall be compressed at all.  Individual pack
10090
 
 * file containing more than one revision will be limited to a size of
10091
 
 * MAX_PACK_SIZE bytes before compression.
10092
 
 *
10093
 
 * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10094
 
 * allocations are done in SCRATCH_POOL.
10095
 
 */
10096
 
static svn_error_t *
10097
 
pack_revprops_shard(const char *pack_file_dir,
10098
 
                    const char *shard_path,
10099
 
                    apr_int64_t shard,
10100
 
                    int max_files_per_dir,
10101
 
                    apr_off_t max_pack_size,
10102
 
                    int compression_level,
10103
 
                    svn_cancel_func_t cancel_func,
10104
 
                    void *cancel_baton,
10105
 
                    apr_pool_t *scratch_pool)
10106
 
{
10107
 
  const char *manifest_file_path, *pack_filename = NULL;
10108
 
  svn_stream_t *manifest_stream;
10109
 
  svn_revnum_t start_rev, end_rev, rev;
10110
 
  apr_off_t total_size;
10111
 
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10112
 
  apr_array_header_t *sizes;
10113
 
 
10114
 
  /* Some useful paths. */
10115
 
  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
10116
 
                                       scratch_pool);
10117
 
 
10118
 
  /* Remove any existing pack file for this shard, since it is incomplete. */
10119
 
  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
10120
 
                             scratch_pool));
10121
 
 
10122
 
  /* Create the new directory and manifest file stream. */
10123
 
  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
10124
 
  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
10125
 
                                   scratch_pool, scratch_pool));
10126
 
 
10127
 
  /* revisions to handle. Special case: revision 0 */
10128
 
  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
10129
 
  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
10130
 
  if (start_rev == 0)
10131
 
    ++start_rev;
10132
 
 
10133
 
  /* initialize the revprop size info */
10134
 
  sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
10135
 
  total_size = 2 * SVN_INT64_BUFFER_SIZE;
10136
 
 
10137
 
  /* Iterate over the revisions in this shard, determine their size and
10138
 
   * squashing them together into pack files. */
10139
 
  for (rev = start_rev; rev <= end_rev; rev++)
10140
 
    {
10141
 
      apr_finfo_t finfo;
10142
 
      const char *path;
10143
 
 
10144
 
      svn_pool_clear(iterpool);
10145
 
 
10146
 
      /* Get the size of the file. */
10147
 
      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10148
 
                             iterpool);
10149
 
      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
10150
 
 
10151
 
      /* if we already have started a pack file and this revprop cannot be
10152
 
       * appended to it, write the previous pack file. */
10153
 
      if (sizes->nelts != 0 &&
10154
 
          total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
10155
 
        {
10156
 
          SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10157
 
                                start_rev, rev-1, sizes, (apr_size_t)total_size,
10158
 
                                compression_level, cancel_func, cancel_baton,
10159
 
                                iterpool));
10160
 
 
10161
 
          /* next pack file starts empty again */
10162
 
          apr_array_clear(sizes);
10163
 
          total_size = 2 * SVN_INT64_BUFFER_SIZE;
10164
 
          start_rev = rev;
10165
 
        }
10166
 
 
10167
 
      /* Update the manifest. Allocate a file name for the current pack
10168
 
       * file if it is a new one */
10169
 
      if (sizes->nelts == 0)
10170
 
        pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
10171
 
 
10172
 
      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
10173
 
                                pack_filename));
10174
 
 
10175
 
      /* add to list of files to put into the current pack file */
10176
 
      APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
10177
 
      total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
10178
 
    }
10179
 
 
10180
 
  /* write the last pack file */
10181
 
  if (sizes->nelts != 0)
10182
 
    SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10183
 
                          start_rev, rev-1, sizes, (apr_size_t)total_size,
10184
 
                          compression_level, cancel_func, cancel_baton,
10185
 
                          iterpool));
10186
 
 
10187
 
  /* flush the manifest file and update permissions */
10188
 
  SVN_ERR(svn_stream_close(manifest_stream));
10189
 
  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10190
 
 
10191
 
  svn_pool_destroy(iterpool);
10192
 
 
10193
 
  return SVN_NO_ERROR;
10194
 
}
10195
 
 
10196
 
/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
10197
 
 * MAX_FILES_PER_DIR revprop files in it.  If this is shard 0, keep the
10198
 
 * revprop file for revision 0.
10199
 
 *
10200
 
 * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10201
 
 * allocations are done in SCRATCH_POOL.
10202
 
 */
10203
 
static svn_error_t *
10204
 
delete_revprops_shard(const char *shard_path,
10205
 
                      apr_int64_t shard,
10206
 
                      int max_files_per_dir,
10207
 
                      svn_cancel_func_t cancel_func,
10208
 
                      void *cancel_baton,
10209
 
                      apr_pool_t *scratch_pool)
10210
 
{
10211
 
  if (shard == 0)
10212
 
    {
10213
 
      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10214
 
      int i;
10215
 
 
10216
 
      /* delete all files except the one for revision 0 */
10217
 
      for (i = 1; i < max_files_per_dir; ++i)
10218
 
        {
10219
 
          const char *path = svn_dirent_join(shard_path,
10220
 
                                       apr_psprintf(iterpool, "%d", i),
10221
 
                                       iterpool);
10222
 
          if (cancel_func)
10223
 
            SVN_ERR((*cancel_func)(cancel_baton));
10224
 
 
10225
 
          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10226
 
          svn_pool_clear(iterpool);
10227
 
        }
10228
 
 
10229
 
      svn_pool_destroy(iterpool);
10230
 
    }
10231
 
  else
10232
 
    SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
10233
 
                               cancel_func, cancel_baton, scratch_pool));
10234
 
 
10235
 
  return SVN_NO_ERROR;
10236
 
}
10237
 
 
10238
 
/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
10239
 
 * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
10240
 
 * for allocations.  REVPROPS_DIR will be NULL if revprop packing is not
10241
 
 * supported.  COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
10242
 
 * case.
10243
 
 *
10244
 
 * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
10245
 
 * NOTIFY_FUNC and NOTIFY_BATON.
10246
 
 *
10247
 
 * If for some reason we detect a partial packing already performed, we
10248
 
 * remove the pack file and start again.
10249
 
 */
10250
 
static svn_error_t *
10251
 
pack_shard(const char *revs_dir,
10252
 
           const char *revsprops_dir,
10253
 
           const char *fs_path,
10254
 
           apr_int64_t shard,
10255
 
           int max_files_per_dir,
10256
 
           apr_off_t max_pack_size,
10257
 
           int compression_level,
10258
 
           svn_fs_pack_notify_t notify_func,
10259
 
           void *notify_baton,
10260
 
           svn_cancel_func_t cancel_func,
10261
 
           void *cancel_baton,
10262
 
           apr_pool_t *pool)
10263
 
{
10264
 
  const char *rev_shard_path, *rev_pack_file_dir;
10265
 
  const char *revprops_shard_path, *revprops_pack_file_dir;
10266
 
 
10267
 
  /* Notify caller we're starting to pack this shard. */
10268
 
  if (notify_func)
10269
 
    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
10270
 
                        pool));
10271
 
 
10272
 
  /* Some useful paths. */
10273
 
  rev_pack_file_dir = svn_dirent_join(revs_dir,
10274
 
                  apr_psprintf(pool,
10275
 
                               "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10276
 
                               shard),
10277
 
                  pool);
10278
 
  rev_shard_path = svn_dirent_join(revs_dir,
10279
 
                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10280
 
                           pool);
10281
 
 
10282
 
  /* pack the revision content */
10283
 
  SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
10284
 
                         shard, max_files_per_dir,
10285
 
                         cancel_func, cancel_baton, pool));
10286
 
 
10287
 
  /* if enabled, pack the revprops in an equivalent way */
10288
 
  if (revsprops_dir)
10289
 
    {
10290
 
      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
10291
 
                   apr_psprintf(pool,
10292
 
                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10293
 
                                shard),
10294
 
                   pool);
10295
 
      revprops_shard_path = svn_dirent_join(revsprops_dir,
10296
 
                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10297
 
                           pool);
10298
 
 
10299
 
      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
10300
 
                                  shard, max_files_per_dir,
10301
 
                                  (int)(0.9 * max_pack_size),
10302
 
                                  compression_level,
10303
 
                                  cancel_func, cancel_baton, pool));
10304
 
    }
10305
 
 
10306
 
  /* Update the min-unpacked-rev file to reflect our newly packed shard.
10307
 
   * (This doesn't update ffd->min_unpacked_rev.  That will be updated by
10308
 
   * update_min_unpacked_rev() when necessary.) */
10309
 
  SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
10310
 
                            (svn_revnum_t)((shard + 1) * max_files_per_dir),
10311
 
                            pool));
10312
 
 
10313
 
  /* Finally, remove the existing shard directories. */
10314
 
  SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
10315
 
                             cancel_func, cancel_baton, pool));
10316
 
  if (revsprops_dir)
10317
 
    SVN_ERR(delete_revprops_shard(revprops_shard_path,
10318
 
                                  shard, max_files_per_dir,
10319
 
                                  cancel_func, cancel_baton, pool));
10320
 
 
10321
 
  /* Notify caller we're starting to pack this shard. */
10322
 
  if (notify_func)
10323
 
    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
10324
 
                        pool));
10325
 
 
10326
 
  return SVN_NO_ERROR;
10327
 
}
10328
 
 
10329
 
struct pack_baton
10330
 
{
10331
 
  svn_fs_t *fs;
10332
 
  svn_fs_pack_notify_t notify_func;
10333
 
  void *notify_baton;
10334
 
  svn_cancel_func_t cancel_func;
10335
 
  void *cancel_baton;
10336
 
};
10337
 
 
10338
 
 
10339
 
/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
10340
 
   This implements the svn_fs_fs__with_write_lock() 'body' callback
10341
 
   type.  BATON is a 'struct pack_baton *'.
10342
 
 
10343
 
   WARNING: if you add a call to this function, please note:
10344
 
     The code currently assumes that any piece of code running with
10345
 
     the write-lock set can rely on the ffd->min_unpacked_rev and
10346
 
     ffd->min_unpacked_revprop caches to be up-to-date (and, by
10347
 
     extension, on not having to use a retry when calling
10348
 
     svn_fs_fs__path_rev_absolute() and friends).  If you add a call
10349
 
     to this function, consider whether you have to call
10350
 
     update_min_unpacked_rev().
10351
 
     See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
10352
 
 */
10353
 
static svn_error_t *
10354
 
pack_body(void *baton,
10355
 
          apr_pool_t *pool)
10356
 
{
10357
 
  struct pack_baton *pb = baton;
10358
 
  fs_fs_data_t ffd = {0};
10359
 
  apr_int64_t completed_shards;
10360
 
  apr_int64_t i;
10361
 
  svn_revnum_t youngest;
10362
 
  apr_pool_t *iterpool;
10363
 
  const char *rev_data_path;
10364
 
  const char *revprops_data_path = NULL;
10365
 
 
10366
 
  /* read repository settings */
10367
 
  SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
10368
 
                      path_format(pb->fs, pool), pool));
10369
 
  SVN_ERR(check_format(ffd.format));
10370
 
  SVN_ERR(read_config(&ffd, pb->fs->path, pool));
10371
 
 
10372
 
  /* If the repository isn't a new enough format, we don't support packing.
10373
 
     Return a friendly error to that effect. */
10374
 
  if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
10375
 
    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
10376
 
      _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
10377
 
      ffd.format);
10378
 
 
10379
 
  /* If we aren't using sharding, we can't do any packing, so quit. */
10380
 
  if (!ffd.max_files_per_dir)
10381
 
    return SVN_NO_ERROR;
10382
 
 
10383
 
  SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
10384
 
                                path_min_unpacked_rev(pb->fs, pool),
10385
 
                                pool));
10386
 
 
10387
 
  SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
10388
 
  completed_shards = (youngest + 1) / ffd.max_files_per_dir;
10389
 
 
10390
 
  /* See if we've already completed all possible shards thus far. */
10391
 
  if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
10392
 
    return SVN_NO_ERROR;
10393
 
 
10394
 
  rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
10395
 
  if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
10396
 
    revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
10397
 
                                         pool);
10398
 
 
10399
 
  iterpool = svn_pool_create(pool);
10400
 
  for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
10401
 
       i < completed_shards;
10402
 
       i++)
10403
 
    {
10404
 
      svn_pool_clear(iterpool);
10405
 
 
10406
 
      if (pb->cancel_func)
10407
 
        SVN_ERR(pb->cancel_func(pb->cancel_baton));
10408
 
 
10409
 
      SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
10410
 
                         pb->fs->path, i, ffd.max_files_per_dir,
10411
 
                         ffd.revprop_pack_size,
10412
 
                         ffd.compress_packed_revprops
10413
 
                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
10414
 
                           : SVN_DELTA_COMPRESSION_LEVEL_NONE,
10415
 
                         pb->notify_func, pb->notify_baton,
10416
 
                         pb->cancel_func, pb->cancel_baton, iterpool));
10417
 
    }
10418
 
 
10419
 
  svn_pool_destroy(iterpool);
10420
 
  return SVN_NO_ERROR;
10421
 
}
10422
 
 
10423
 
svn_error_t *
10424
 
svn_fs_fs__pack(svn_fs_t *fs,
10425
 
                svn_fs_pack_notify_t notify_func,
10426
 
                void *notify_baton,
10427
 
                svn_cancel_func_t cancel_func,
10428
 
                void *cancel_baton,
10429
 
                apr_pool_t *pool)
10430
 
{
10431
 
  struct pack_baton pb = { 0 };
10432
 
  pb.fs = fs;
10433
 
  pb.notify_func = notify_func;
10434
 
  pb.notify_baton = notify_baton;
10435
 
  pb.cancel_func = cancel_func;
10436
 
  pb.cancel_baton = cancel_baton;
10437
 
  return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
10438
 
}
10439
 
 
10440
 
 
10441
 
/** Verifying. **/
10442
 
 
10443
 
/* Baton type expected by verify_walker().  The purpose is to reuse open
10444
 
 * rev / pack file handles between calls.  Its contents need to be cleaned
10445
 
 * periodically to limit resource usage.
10446
 
 */
10447
 
typedef struct verify_walker_baton_t
10448
 
{
10449
 
  /* number of calls to verify_walker() since the last clean */
10450
 
  int iteration_count;
10451
 
 
10452
 
  /* number of files opened since the last clean */
10453
 
  int file_count;
10454
 
 
10455
 
  /* progress notification callback to invoke periodically (may be NULL) */
10456
 
  svn_fs_progress_notify_func_t notify_func;
10457
 
 
10458
 
  /* baton to use with NOTIFY_FUNC */
10459
 
  void *notify_baton;
10460
 
 
10461
 
  /* remember the last revision for which we called notify_func */
10462
 
  svn_revnum_t last_notified_revision;
10463
 
 
10464
 
  /* current file handle (or NULL) */
10465
 
  apr_file_t *file_hint;
10466
 
 
10467
 
  /* corresponding revision (or SVN_INVALID_REVNUM) */
10468
 
  svn_revnum_t rev_hint;
10469
 
 
10470
 
  /* pool to use for the file handles etc. */
10471
 
  apr_pool_t *pool;
10472
 
} verify_walker_baton_t;
10473
 
 
10474
 
/* Used by svn_fs_fs__verify().
10475
 
   Implements svn_fs_fs__walk_rep_reference().walker.  */
10476
 
static svn_error_t *
10477
 
verify_walker(representation_t *rep,
10478
 
              void *baton,
10479
 
              svn_fs_t *fs,
10480
 
              apr_pool_t *scratch_pool)
10481
 
{
10482
 
  struct rep_state *rs;
10483
 
  struct rep_args *rep_args;
10484
 
 
10485
 
  if (baton)
10486
 
    {
10487
 
      verify_walker_baton_t *walker_baton = baton;
10488
 
      apr_file_t * previous_file;
10489
 
 
10490
 
      /* notify and free resources periodically */
10491
 
      if (   walker_baton->iteration_count > 1000
10492
 
          || walker_baton->file_count > 16)
10493
 
        {
10494
 
          if (   walker_baton->notify_func
10495
 
              && rep->revision != walker_baton->last_notified_revision)
10496
 
            {
10497
 
              walker_baton->notify_func(rep->revision,
10498
 
                                        walker_baton->notify_baton,
10499
 
                                        scratch_pool);
10500
 
              walker_baton->last_notified_revision = rep->revision;
10501
 
            }
10502
 
 
10503
 
          svn_pool_clear(walker_baton->pool);
10504
 
 
10505
 
          walker_baton->iteration_count = 0;
10506
 
          walker_baton->file_count = 0;
10507
 
          walker_baton->file_hint = NULL;
10508
 
          walker_baton->rev_hint = SVN_INVALID_REVNUM;
10509
 
        }
10510
 
 
10511
 
      /* access the repo data */
10512
 
      previous_file = walker_baton->file_hint;
10513
 
      SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
10514
 
                               &walker_baton->rev_hint, rep, fs,
10515
 
                               walker_baton->pool));
10516
 
 
10517
 
      /* update resource usage counters */
10518
 
      walker_baton->iteration_count++;
10519
 
      if (previous_file != walker_baton->file_hint)
10520
 
        walker_baton->file_count++;
10521
 
    }
10522
 
  else
10523
 
    {
10524
 
      /* ### Should this be using read_rep_line() directly? */
10525
 
      SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
10526
 
                               scratch_pool));
10527
 
    }
10528
 
 
10529
 
  return SVN_NO_ERROR;
10530
 
}
10531
 
 
10532
 
svn_error_t *
10533
 
svn_fs_fs__verify(svn_fs_t *fs,
10534
 
                  svn_revnum_t start,
10535
 
                  svn_revnum_t end,
10536
 
                  svn_fs_progress_notify_func_t notify_func,
10537
 
                  void *notify_baton,
10538
 
                  svn_cancel_func_t cancel_func,
10539
 
                  void *cancel_baton,
10540
 
                  apr_pool_t *pool)
10541
 
{
10542
 
  fs_fs_data_t *ffd = fs->fsap_data;
10543
 
  svn_boolean_t exists;
10544
 
  svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
10545
 
 
10546
 
  if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
10547
 
    return SVN_NO_ERROR;
10548
 
 
10549
 
  /* Input validation. */
10550
 
  if (! SVN_IS_VALID_REVNUM(start))
10551
 
    start = 0;
10552
 
  if (! SVN_IS_VALID_REVNUM(end))
10553
 
    end = youngest;
10554
 
  SVN_ERR(ensure_revision_exists(fs, start, pool));
10555
 
  SVN_ERR(ensure_revision_exists(fs, end, pool));
10556
 
 
10557
 
  /* rep-cache verification. */
10558
 
  SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
10559
 
  if (exists)
10560
 
    {
10561
 
      /* provide a baton to allow the reuse of open file handles between
10562
 
         iterations (saves 2/3 of OS level file operations). */
10563
 
      verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
10564
 
      baton->rev_hint = SVN_INVALID_REVNUM;
10565
 
      baton->pool = svn_pool_create(pool);
10566
 
      baton->last_notified_revision = SVN_INVALID_REVNUM;
10567
 
      baton->notify_func = notify_func;
10568
 
      baton->notify_baton = notify_baton;
10569
 
 
10570
 
      /* tell the user that we are now ready to do *something* */
10571
 
      if (notify_func)
10572
 
        notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
10573
 
 
10574
 
      /* Do not attempt to walk the rep-cache database if its file does
10575
 
         not exist,  since doing so would create it --- which may confuse
10576
 
         the administrator.   Don't take any lock. */
10577
 
      SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
10578
 
                                            verify_walker, baton,
10579
 
                                            cancel_func, cancel_baton,
10580
 
                                            pool));
10581
 
 
10582
 
      /* walker resource cleanup */
10583
 
      svn_pool_destroy(baton->pool);
10584
 
    }
10585
 
 
10586
 
  return SVN_NO_ERROR;
10587
 
}
10588
 
 
10589
 
 
10590
 
/** Hotcopy. **/
10591
 
 
10592
 
/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
10593
 
 * the destination and do not differ in terms of kind, size, and mtime. */
10594
 
static svn_error_t *
10595
 
hotcopy_io_dir_file_copy(const char *src_path,
10596
 
                         const char *dst_path,
10597
 
                         const char *file,
10598
 
                         apr_pool_t *scratch_pool)
10599
 
{
10600
 
  const svn_io_dirent2_t *src_dirent;
10601
 
  const svn_io_dirent2_t *dst_dirent;
10602
 
  const char *src_target;
10603
 
  const char *dst_target;
10604
 
 
10605
 
  /* Does the destination already exist? If not, we must copy it. */
10606
 
  dst_target = svn_dirent_join(dst_path, file, scratch_pool);
10607
 
  SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
10608
 
                              scratch_pool, scratch_pool));
10609
 
  if (dst_dirent->kind != svn_node_none)
10610
 
    {
10611
 
      /* If the destination's stat information indicates that the file
10612
 
       * is equal to the source, don't bother copying the file again. */
10613
 
      src_target = svn_dirent_join(src_path, file, scratch_pool);
10614
 
      SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
10615
 
                                  scratch_pool, scratch_pool));
10616
 
      if (src_dirent->kind == dst_dirent->kind &&
10617
 
          src_dirent->special == dst_dirent->special &&
10618
 
          src_dirent->filesize == dst_dirent->filesize &&
10619
 
          src_dirent->mtime <= dst_dirent->mtime)
10620
 
        return SVN_NO_ERROR;
10621
 
    }
10622
 
 
10623
 
  return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
10624
 
                                              scratch_pool));
10625
 
}
10626
 
 
10627
 
/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
10628
 
 * NAME is in the internal encoding used by APR; PARENT is in
10629
 
 * UTF-8 and in internal (not local) style.
10630
 
 *
10631
 
 * Use PARENT only for generating an error string if the conversion
10632
 
 * fails because NAME could not be represented in UTF-8.  In that
10633
 
 * case, return a two-level error in which the outer error's message
10634
 
 * mentions PARENT, but the inner error's message does not mention
10635
 
 * NAME (except possibly in hex) since NAME may not be printable.
10636
 
 * Such a compound error at least allows the user to go looking in the
10637
 
 * right directory for the problem.
10638
 
 *
10639
 
 * If there is any other error, just return that error directly.
10640
 
 *
10641
 
 * If there is any error, the effect on *NAME_P is undefined.
10642
 
 *
10643
 
 * *NAME_P and NAME may refer to the same storage.
10644
 
 */
10645
 
static svn_error_t *
10646
 
entry_name_to_utf8(const char **name_p,
10647
 
                   const char *name,
10648
 
                   const char *parent,
10649
 
                   apr_pool_t *pool)
10650
 
{
10651
 
  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
10652
 
  if (err && err->apr_err == APR_EINVAL)
10653
 
    {
10654
 
      return svn_error_createf(err->apr_err, err,
10655
 
                               _("Error converting entry "
10656
 
                                 "in directory '%s' to UTF-8"),
10657
 
                               svn_dirent_local_style(parent, pool));
10658
 
    }
10659
 
  return err;
10660
 
}
10661
 
 
10662
 
/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
10663
 
 * exist in the destination and do not differ from the source in terms of
10664
 
 * kind, size, and mtime. */
10665
 
static svn_error_t *
10666
 
hotcopy_io_copy_dir_recursively(const char *src,
10667
 
                                const char *dst_parent,
10668
 
                                const char *dst_basename,
10669
 
                                svn_boolean_t copy_perms,
10670
 
                                svn_cancel_func_t cancel_func,
10671
 
                                void *cancel_baton,
10672
 
                                apr_pool_t *pool)
10673
 
{
10674
 
  svn_node_kind_t kind;
10675
 
  apr_status_t status;
10676
 
  const char *dst_path;
10677
 
  apr_dir_t *this_dir;
10678
 
  apr_finfo_t this_entry;
10679
 
  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
10680
 
 
10681
 
  /* Make a subpool for recursion */
10682
 
  apr_pool_t *subpool = svn_pool_create(pool);
10683
 
 
10684
 
  /* The 'dst_path' is simply dst_parent/dst_basename */
10685
 
  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
10686
 
 
10687
 
  /* Sanity checks:  SRC and DST_PARENT are directories, and
10688
 
     DST_BASENAME doesn't already exist in DST_PARENT. */
10689
 
  SVN_ERR(svn_io_check_path(src, &kind, subpool));
10690
 
  if (kind != svn_node_dir)
10691
 
    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10692
 
                             _("Source '%s' is not a directory"),
10693
 
                             svn_dirent_local_style(src, pool));
10694
 
 
10695
 
  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
10696
 
  if (kind != svn_node_dir)
10697
 
    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10698
 
                             _("Destination '%s' is not a directory"),
10699
 
                             svn_dirent_local_style(dst_parent, pool));
10700
 
 
10701
 
  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
10702
 
 
10703
 
  /* Create the new directory. */
10704
 
  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
10705
 
  SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
10706
 
 
10707
 
  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
10708
 
  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
10709
 
 
10710
 
  for (status = apr_dir_read(&this_entry, flags, this_dir);
10711
 
       status == APR_SUCCESS;
10712
 
       status = apr_dir_read(&this_entry, flags, this_dir))
10713
 
    {
10714
 
      if ((this_entry.name[0] == '.')
10715
 
          && ((this_entry.name[1] == '\0')
10716
 
              || ((this_entry.name[1] == '.')
10717
 
                  && (this_entry.name[2] == '\0'))))
10718
 
        {
10719
 
          continue;
10720
 
        }
10721
 
      else
10722
 
        {
10723
 
          const char *entryname_utf8;
10724
 
 
10725
 
          if (cancel_func)
10726
 
            SVN_ERR(cancel_func(cancel_baton));
10727
 
 
10728
 
          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
10729
 
                                     src, subpool));
10730
 
          if (this_entry.filetype == APR_REG) /* regular file */
10731
 
            {
10732
 
              SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
10733
 
                                               subpool));
10734
 
            }
10735
 
          else if (this_entry.filetype == APR_LNK) /* symlink */
10736
 
            {
10737
 
              const char *src_target = svn_dirent_join(src, entryname_utf8,
10738
 
                                                       subpool);
10739
 
              const char *dst_target = svn_dirent_join(dst_path,
10740
 
                                                       entryname_utf8,
10741
 
                                                       subpool);
10742
 
              SVN_ERR(svn_io_copy_link(src_target, dst_target,
10743
 
                                       subpool));
10744
 
            }
10745
 
          else if (this_entry.filetype == APR_DIR) /* recurse */
10746
 
            {
10747
 
              const char *src_target;
10748
 
 
10749
 
              /* Prevent infinite recursion by filtering off our
10750
 
                 newly created destination path. */
10751
 
              if (strcmp(src, dst_parent) == 0
10752
 
                  && strcmp(entryname_utf8, dst_basename) == 0)
10753
 
                continue;
10754
 
 
10755
 
              src_target = svn_dirent_join(src, entryname_utf8, subpool);
10756
 
              SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
10757
 
                                                      dst_path,
10758
 
                                                      entryname_utf8,
10759
 
                                                      copy_perms,
10760
 
                                                      cancel_func,
10761
 
                                                      cancel_baton,
10762
 
                                                      subpool));
10763
 
            }
10764
 
          /* ### support other APR node types someday?? */
10765
 
 
10766
 
        }
10767
 
    }
10768
 
 
10769
 
  if (! (APR_STATUS_IS_ENOENT(status)))
10770
 
    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
10771
 
                              svn_dirent_local_style(src, pool));
10772
 
 
10773
 
  status = apr_dir_close(this_dir);
10774
 
  if (status)
10775
 
    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
10776
 
                              svn_dirent_local_style(src, pool));
10777
 
 
10778
 
  /* Free any memory used by recursion */
10779
 
  svn_pool_destroy(subpool);
10780
 
 
10781
 
  return SVN_NO_ERROR;
10782
 
}
10783
 
 
10784
 
/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
10785
 
 * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
10786
 
 * Use SCRATCH_POOL for temporary allocations. */
10787
 
static svn_error_t *
10788
 
hotcopy_copy_shard_file(const char *src_subdir,
10789
 
                        const char *dst_subdir,
10790
 
                        svn_revnum_t rev,
10791
 
                        int max_files_per_dir,
10792
 
                        apr_pool_t *scratch_pool)
10793
 
{
10794
 
  const char *src_subdir_shard = src_subdir,
10795
 
             *dst_subdir_shard = dst_subdir;
10796
 
 
10797
 
  if (max_files_per_dir)
10798
 
    {
10799
 
      const char *shard = apr_psprintf(scratch_pool, "%ld",
10800
 
                                       rev / max_files_per_dir);
10801
 
      src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
10802
 
      dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10803
 
 
10804
 
      if (rev % max_files_per_dir == 0)
10805
 
        {
10806
 
          SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
10807
 
          SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
10808
 
                                    scratch_pool));
10809
 
        }
10810
 
    }
10811
 
 
10812
 
  SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
10813
 
                                   apr_psprintf(scratch_pool, "%ld", rev),
10814
 
                                   scratch_pool));
10815
 
  return SVN_NO_ERROR;
10816
 
}
10817
 
 
10818
 
 
10819
 
/* Copy a packed shard containing revision REV, and which contains
10820
 
 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
10821
 
 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
10822
 
 * Do not re-copy data which already exists in DST_FS.
10823
 
 * Use SCRATCH_POOL for temporary allocations. */
10824
 
static svn_error_t *
10825
 
hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
10826
 
                          svn_fs_t *src_fs,
10827
 
                          svn_fs_t *dst_fs,
10828
 
                          svn_revnum_t rev,
10829
 
                          int max_files_per_dir,
10830
 
                          apr_pool_t *scratch_pool)
10831
 
{
10832
 
  const char *src_subdir;
10833
 
  const char *dst_subdir;
10834
 
  const char *packed_shard;
10835
 
  const char *src_subdir_packed_shard;
10836
 
  svn_revnum_t revprop_rev;
10837
 
  apr_pool_t *iterpool;
10838
 
  fs_fs_data_t *src_ffd = src_fs->fsap_data;
10839
 
 
10840
 
  /* Copy the packed shard. */
10841
 
  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
10842
 
  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
10843
 
  packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10844
 
                              rev / max_files_per_dir);
10845
 
  src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10846
 
                                            scratch_pool);
10847
 
  SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10848
 
                                          dst_subdir, packed_shard,
10849
 
                                          TRUE /* copy_perms */,
10850
 
                                          NULL /* cancel_func */, NULL,
10851
 
                                          scratch_pool));
10852
 
 
10853
 
  /* Copy revprops belonging to revisions in this pack. */
10854
 
  src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10855
 
  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10856
 
 
10857
 
  if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
10858
 
      || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
10859
 
    {
10860
 
      /* copy unpacked revprops rev by rev */
10861
 
      iterpool = svn_pool_create(scratch_pool);
10862
 
      for (revprop_rev = rev;
10863
 
           revprop_rev < rev + max_files_per_dir;
10864
 
           revprop_rev++)
10865
 
        {
10866
 
          svn_pool_clear(iterpool);
10867
 
 
10868
 
          SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10869
 
                                          revprop_rev, max_files_per_dir,
10870
 
                                          iterpool));
10871
 
        }
10872
 
      svn_pool_destroy(iterpool);
10873
 
    }
10874
 
  else
10875
 
    {
10876
 
      /* revprop for revision 0 will never be packed */
10877
 
      if (rev == 0)
10878
 
        SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10879
 
                                        0, max_files_per_dir,
10880
 
                                        scratch_pool));
10881
 
 
10882
 
      /* packed revprops folder */
10883
 
      packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10884
 
                                  rev / max_files_per_dir);
10885
 
      src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10886
 
                                                scratch_pool);
10887
 
      SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10888
 
                                              dst_subdir, packed_shard,
10889
 
                                              TRUE /* copy_perms */,
10890
 
                                              NULL /* cancel_func */, NULL,
10891
 
                                              scratch_pool));
10892
 
    }
10893
 
 
10894
 
  /* If necessary, update the min-unpacked rev file in the hotcopy. */
10895
 
  if (*dst_min_unpacked_rev < rev + max_files_per_dir)
10896
 
    {
10897
 
      *dst_min_unpacked_rev = rev + max_files_per_dir;
10898
 
      SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
10899
 
                                *dst_min_unpacked_rev,
10900
 
                                scratch_pool));
10901
 
    }
10902
 
 
10903
 
  return SVN_NO_ERROR;
10904
 
}
10905
 
 
10906
 
/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
10907
 
 * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
10908
 
 * Use SCRATCH_POOL for temporary allocations. */
10909
 
static svn_error_t *
10910
 
hotcopy_update_current(svn_revnum_t *dst_youngest,
10911
 
                       svn_fs_t *dst_fs,
10912
 
                       svn_revnum_t new_youngest,
 
2155
                       apr_pool_t *result_pool,
10913
2156
                       apr_pool_t *scratch_pool)
10914
2157
{
10915
 
  char next_node_id[MAX_KEY_SIZE] = "0";
10916
 
  char next_copy_id[MAX_KEY_SIZE] = "0";
10917
 
  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
10918
 
 
10919
 
  if (*dst_youngest >= new_youngest)
10920
 
    return SVN_NO_ERROR;
10921
 
 
10922
 
  /* If necessary, get new current next_node and next_copy IDs. */
10923
 
  if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
10924
 
    {
10925
 
      apr_off_t root_offset;
10926
 
      apr_file_t *rev_file;
10927
 
      char max_node_id[MAX_KEY_SIZE] = "0";
10928
 
      char max_copy_id[MAX_KEY_SIZE] = "0";
10929
 
      apr_size_t len;
10930
 
 
10931
 
      if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
10932
 
        SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
10933
 
 
10934
 
      SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
10935
 
                                    scratch_pool));
10936
 
      SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
10937
 
                                      dst_fs, new_youngest, scratch_pool));
10938
 
      SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
10939
 
                                   root_offset, max_node_id, max_copy_id,
10940
 
                                   scratch_pool));
10941
 
      SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
10942
 
 
10943
 
      /* We store the _next_ ids. */
10944
 
      len = strlen(max_node_id);
10945
 
      svn_fs_fs__next_key(max_node_id, &len, next_node_id);
10946
 
      len = strlen(max_copy_id);
10947
 
      svn_fs_fs__next_key(max_copy_id, &len, next_copy_id);
10948
 
    }
10949
 
 
10950
 
  /* Update 'current'. */
10951
 
  SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
10952
 
                        scratch_pool));
10953
 
 
10954
 
  *dst_youngest = new_youngest;
10955
 
 
10956
 
  return SVN_NO_ERROR;
10957
 
}
10958
 
 
10959
 
 
10960
 
/* Remove revision or revprop files between START_REV (inclusive) and
10961
 
 * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
10962
 
 * sharding as per MAX_FILES_PER_DIR.
10963
 
 * Use SCRATCH_POOL for temporary allocations. */
10964
 
static svn_error_t *
10965
 
hotcopy_remove_files(svn_fs_t *dst_fs,
10966
 
                     const char *dst_subdir,
10967
 
                     svn_revnum_t start_rev,
10968
 
                     svn_revnum_t end_rev,
10969
 
                     int max_files_per_dir,
10970
 
                     apr_pool_t *scratch_pool)
10971
 
{
10972
 
  const char *shard;
10973
 
  const char *dst_subdir_shard;
10974
 
  svn_revnum_t rev;
10975
 
  apr_pool_t *iterpool;
10976
 
 
10977
 
  /* Pre-compute paths for initial shard. */
10978
 
  shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
10979
 
  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10980
 
 
10981
 
  iterpool = svn_pool_create(scratch_pool);
10982
 
  for (rev = start_rev; rev < end_rev; rev++)
10983
 
    {
10984
 
      const char *path;
10985
 
      svn_pool_clear(iterpool);
10986
 
 
10987
 
      /* If necessary, update paths for shard. */
10988
 
      if (rev != start_rev && rev % max_files_per_dir == 0)
10989
 
        {
10990
 
          shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
10991
 
          dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10992
 
        }
10993
 
 
10994
 
      /* remove files for REV */
10995
 
      path = svn_dirent_join(dst_subdir_shard,
10996
 
                             apr_psprintf(iterpool, "%ld", rev),
10997
 
                             iterpool);
10998
 
 
10999
 
      /* Make the rev file writable and remove it. */
11000
 
      SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
11001
 
      SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
11002
 
    }
11003
 
 
11004
 
  svn_pool_destroy(iterpool);
11005
 
 
11006
 
  return SVN_NO_ERROR;
11007
 
}
11008
 
 
11009
 
/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
11010
 
 * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11011
 
 * Use SCRATCH_POOL for temporary allocations. */
11012
 
static svn_error_t *
11013
 
hotcopy_remove_rev_files(svn_fs_t *dst_fs,
11014
 
                         svn_revnum_t start_rev,
11015
 
                         svn_revnum_t end_rev,
11016
 
                         int max_files_per_dir,
11017
 
                         apr_pool_t *scratch_pool)
11018
 
{
11019
 
  SVN_ERR_ASSERT(start_rev <= end_rev);
11020
 
  SVN_ERR(hotcopy_remove_files(dst_fs,
11021
 
                               svn_dirent_join(dst_fs->path,
11022
 
                                               PATH_REVS_DIR,
11023
 
                                               scratch_pool),
11024
 
                               start_rev, end_rev,
11025
 
                               max_files_per_dir, scratch_pool));
11026
 
 
11027
 
  return SVN_NO_ERROR;
11028
 
}
11029
 
 
11030
 
/* Remove revision properties between START_REV (inclusive) and END_REV
11031
 
 * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11032
 
 * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
11033
 
 * not be deleted. */
11034
 
static svn_error_t *
11035
 
hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
11036
 
                             svn_revnum_t start_rev,
11037
 
                             svn_revnum_t end_rev,
11038
 
                             int max_files_per_dir,
 
2158
  fs_fs_data_t *ffd = fs->fsap_data;
 
2159
  *fs_format = ffd->format;
 
2160
  *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
 
2161
 
 
2162
  (*supports_version)->major = SVN_VER_MAJOR;
 
2163
  (*supports_version)->minor = 1;
 
2164
  (*supports_version)->patch = 0;
 
2165
  (*supports_version)->tag = "";
 
2166
 
 
2167
  switch (ffd->format)
 
2168
    {
 
2169
    case 1:
 
2170
      break;
 
2171
    case 2:
 
2172
      (*supports_version)->minor = 4;
 
2173
      break;
 
2174
    case 3:
 
2175
      (*supports_version)->minor = 5;
 
2176
      break;
 
2177
    case 4:
 
2178
      (*supports_version)->minor = 6;
 
2179
      break;
 
2180
    case 6:
 
2181
      (*supports_version)->minor = 8;
 
2182
      break;
 
2183
    case 7:
 
2184
      (*supports_version)->minor = 9;
 
2185
      break;
 
2186
#ifdef SVN_DEBUG
 
2187
# if SVN_FS_FS__FORMAT_NUMBER != 7
 
2188
#  error "Need to add a 'case' statement here"
 
2189
# endif
 
2190
#endif
 
2191
    }
 
2192
 
 
2193
  return SVN_NO_ERROR;
 
2194
}
 
2195
 
 
2196
svn_error_t *
 
2197
svn_fs_fs__info_config_files(apr_array_header_t **files,
 
2198
                             svn_fs_t *fs,
 
2199
                             apr_pool_t *result_pool,
11039
2200
                             apr_pool_t *scratch_pool)
11040
2201
{
11041
 
  SVN_ERR_ASSERT(start_rev <= end_rev);
11042
 
 
11043
 
  /* don't delete rev 0 props */
11044
 
  SVN_ERR(hotcopy_remove_files(dst_fs,
11045
 
                               svn_dirent_join(dst_fs->path,
11046
 
                                               PATH_REVPROPS_DIR,
11047
 
                                               scratch_pool),
11048
 
                               start_rev ? start_rev : 1, end_rev,
11049
 
                               max_files_per_dir, scratch_pool));
11050
 
 
11051
 
  return SVN_NO_ERROR;
11052
 
}
11053
 
 
11054
 
/* Verify that DST_FS is a suitable destination for an incremental
11055
 
 * hotcopy from SRC_FS. */
11056
 
static svn_error_t *
11057
 
hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
11058
 
                                        svn_fs_t *dst_fs,
11059
 
                                        apr_pool_t *pool)
11060
 
{
11061
 
  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11062
 
  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11063
 
 
11064
 
  /* We only support incremental hotcopy between the same format. */
11065
 
  if (src_ffd->format != dst_ffd->format)
11066
 
    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11067
 
      _("The FSFS format (%d) of the hotcopy source does not match the "
11068
 
        "FSFS format (%d) of the hotcopy destination; please upgrade "
11069
 
        "both repositories to the same format"),
11070
 
      src_ffd->format, dst_ffd->format);
11071
 
 
11072
 
  /* Make sure the UUID of source and destination match up.
11073
 
   * We don't want to copy over a different repository. */
11074
 
  if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
11075
 
    return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
11076
 
                            _("The UUID of the hotcopy source does "
11077
 
                              "not match the UUID of the hotcopy "
11078
 
                              "destination"));
11079
 
 
11080
 
  /* Also require same shard size. */
11081
 
  if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
11082
 
    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11083
 
                            _("The sharding layout configuration "
11084
 
                              "of the hotcopy source does not match "
11085
 
                              "the sharding layout configuration of "
11086
 
                              "the hotcopy destination"));
11087
 
  return SVN_NO_ERROR;
11088
 
}
11089
 
 
11090
 
/* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
11091
 
 * CANCEL_FUNC and CANCEL_BATON do the usual thing.
11092
 
 * Use POOL for temporary allocations.
11093
 
 */
11094
 
static svn_error_t *
11095
 
remove_folder(const char *path,
11096
 
              svn_cancel_func_t cancel_func,
11097
 
              void *cancel_baton,
11098
 
              apr_pool_t *pool)
11099
 
{
11100
 
  svn_error_t *err = svn_io_remove_dir2(path, TRUE,
11101
 
                                        cancel_func, cancel_baton, pool);
11102
 
 
11103
 
  if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
11104
 
    {
11105
 
      svn_error_clear(err);
11106
 
      err = SVN_NO_ERROR;
11107
 
    }
11108
 
 
11109
 
  return svn_error_trace(err);
11110
 
}
11111
 
 
11112
 
/* Baton for hotcopy_body(). */
11113
 
struct hotcopy_body_baton {
11114
 
  svn_fs_t *src_fs;
11115
 
  svn_fs_t *dst_fs;
11116
 
  svn_boolean_t incremental;
11117
 
  svn_cancel_func_t cancel_func;
11118
 
  void *cancel_baton;
11119
 
} hotcopy_body_baton;
11120
 
 
11121
 
/* Perform a hotcopy, either normal or incremental.
11122
 
 *
11123
 
 * Normal hotcopy assumes that the destination exists as an empty
11124
 
 * directory. It behaves like an incremental hotcopy except that
11125
 
 * none of the copied files already exist in the destination.
11126
 
 *
11127
 
 * An incremental hotcopy copies only changed or new files to the destination,
11128
 
 * and removes files from the destination no longer present in the source.
11129
 
 * While the incremental hotcopy is running, readers should still be able
11130
 
 * to access the destintation repository without error and should not see
11131
 
 * revisions currently in progress of being copied. Readers are able to see
11132
 
 * new fully copied revisions even if the entire incremental hotcopy procedure
11133
 
 * has not yet completed.
11134
 
 *
11135
 
 * Writers are blocked out completely during the entire incremental hotcopy
11136
 
 * process to ensure consistency. This function assumes that the repository
11137
 
 * write-lock is held.
11138
 
 */
11139
 
static svn_error_t *
11140
 
hotcopy_body(void *baton, apr_pool_t *pool)
11141
 
{
11142
 
  struct hotcopy_body_baton *hbb = baton;
11143
 
  svn_fs_t *src_fs = hbb->src_fs;
11144
 
  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11145
 
  svn_fs_t *dst_fs = hbb->dst_fs;
11146
 
  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11147
 
  int max_files_per_dir = src_ffd->max_files_per_dir;
11148
 
  svn_boolean_t incremental = hbb->incremental;
11149
 
  svn_cancel_func_t cancel_func = hbb->cancel_func;
11150
 
  void* cancel_baton = hbb->cancel_baton;
11151
 
  svn_revnum_t src_youngest;
11152
 
  svn_revnum_t dst_youngest;
11153
 
  svn_revnum_t rev;
11154
 
  svn_revnum_t src_min_unpacked_rev;
11155
 
  svn_revnum_t dst_min_unpacked_rev;
11156
 
  const char *src_subdir;
11157
 
  const char *dst_subdir;
11158
 
  const char *revprop_src_subdir;
11159
 
  const char *revprop_dst_subdir;
11160
 
  apr_pool_t *iterpool;
11161
 
  svn_node_kind_t kind;
11162
 
 
11163
 
  /* Try to copy the config.
11164
 
   *
11165
 
   * ### We try copying the config file before doing anything else,
11166
 
   * ### because higher layers will abort the hotcopy if we throw
11167
 
   * ### an error from this function, and that renders the hotcopy
11168
 
   * ### unusable anyway. */
11169
 
  if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
11170
 
    {
11171
 
      svn_error_t *err;
11172
 
 
11173
 
      err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
11174
 
                                 pool);
11175
 
      if (err)
11176
 
        {
11177
 
          if (APR_STATUS_IS_ENOENT(err->apr_err))
11178
 
            {
11179
 
              /* 1.6.0 to 1.6.11 did not copy the configuration file during
11180
 
               * hotcopy. So if we're hotcopying a repository which has been
11181
 
               * created as a hotcopy itself, it's possible that fsfs.conf
11182
 
               * does not exist. Ask the user to re-create it.
11183
 
               *
11184
 
               * ### It would be nice to make this a non-fatal error,
11185
 
               * ### but this function does not get an svn_fs_t object
11186
 
               * ### so we have no way of just printing a warning via
11187
 
               * ### the fs->warning() callback. */
11188
 
 
11189
 
              const char *msg;
11190
 
              const char *src_abspath;
11191
 
              const char *dst_abspath;
11192
 
              const char *config_relpath;
11193
 
              svn_error_t *err2;
11194
 
 
11195
 
              config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
11196
 
              err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
11197
 
              if (err2)
11198
 
                return svn_error_trace(svn_error_compose_create(err, err2));
11199
 
              err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
11200
 
              if (err2)
11201
 
                return svn_error_trace(svn_error_compose_create(err, err2));
11202
 
 
11203
 
              /* ### hack: strip off the 'db/' directory from paths so
11204
 
               * ### they make sense to the user */
11205
 
              src_abspath = svn_dirent_dirname(src_abspath, pool);
11206
 
              dst_abspath = svn_dirent_dirname(dst_abspath, pool);
11207
 
 
11208
 
              msg = apr_psprintf(pool,
11209
 
                                 _("Failed to create hotcopy at '%s'. "
11210
 
                                   "The file '%s' is missing from the source "
11211
 
                                   "repository. Please create this file, for "
11212
 
                                   "instance by running 'svnadmin upgrade %s'"),
11213
 
                                 dst_abspath, config_relpath, src_abspath);
11214
 
              return svn_error_quick_wrap(err, msg);
11215
 
            }
11216
 
          else
11217
 
            return svn_error_trace(err);
11218
 
        }
11219
 
    }
11220
 
 
11221
 
  if (cancel_func)
11222
 
    SVN_ERR(cancel_func(cancel_baton));
11223
 
 
11224
 
  /* Find the youngest revision in the source and destination.
11225
 
   * We only support hotcopies from sources with an equal or greater amount
11226
 
   * of revisions than the destination.
11227
 
   * This also catches the case where users accidentally swap the
11228
 
   * source and destination arguments. */
11229
 
  SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
11230
 
  if (incremental)
11231
 
    {
11232
 
      SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
11233
 
      if (src_youngest < dst_youngest)
11234
 
        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11235
 
                 _("The hotcopy destination already contains more revisions "
11236
 
                   "(%lu) than the hotcopy source contains (%lu); are source "
11237
 
                   "and destination swapped?"),
11238
 
                  dst_youngest, src_youngest);
11239
 
    }
11240
 
  else
11241
 
    dst_youngest = 0;
11242
 
 
11243
 
  if (cancel_func)
11244
 
    SVN_ERR(cancel_func(cancel_baton));
11245
 
 
11246
 
  /* Copy the min unpacked rev, and read its value. */
11247
 
  if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11248
 
    {
11249
 
      const char *min_unpacked_rev_path;
11250
 
 
11251
 
      min_unpacked_rev_path = svn_dirent_join(src_fs->path,
11252
 
                                              PATH_MIN_UNPACKED_REV,
11253
 
                                              pool);
11254
 
      SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
11255
 
                                    min_unpacked_rev_path,
11256
 
                                    pool));
11257
 
 
11258
 
      min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
11259
 
                                              PATH_MIN_UNPACKED_REV,
11260
 
                                              pool);
11261
 
      SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
11262
 
                                    min_unpacked_rev_path,
11263
 
                                    pool));
11264
 
 
11265
 
      /* We only support packs coming from the hotcopy source.
11266
 
       * The destination should not be packed independently from
11267
 
       * the source. This also catches the case where users accidentally
11268
 
       * swap the source and destination arguments. */
11269
 
      if (src_min_unpacked_rev < dst_min_unpacked_rev)
11270
 
        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11271
 
                                 _("The hotcopy destination already contains "
11272
 
                                   "more packed revisions (%lu) than the "
11273
 
                                   "hotcopy source contains (%lu)"),
11274
 
                                   dst_min_unpacked_rev - 1,
11275
 
                                   src_min_unpacked_rev - 1);
11276
 
 
11277
 
      SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11278
 
                                   PATH_MIN_UNPACKED_REV, pool));
11279
 
    }
11280
 
  else
11281
 
    {
11282
 
      src_min_unpacked_rev = 0;
11283
 
      dst_min_unpacked_rev = 0;
11284
 
    }
11285
 
 
11286
 
  if (cancel_func)
11287
 
    SVN_ERR(cancel_func(cancel_baton));
11288
 
 
11289
 
  /*
11290
 
   * Copy the necessary rev files.
11291
 
   */
11292
 
 
11293
 
  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
11294
 
  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
11295
 
  SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
11296
 
 
11297
 
  iterpool = svn_pool_create(pool);
11298
 
  /* First, copy packed shards. */
11299
 
  for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
11300
 
    {
11301
 
      svn_pool_clear(iterpool);
11302
 
 
11303
 
      if (cancel_func)
11304
 
        SVN_ERR(cancel_func(cancel_baton));
11305
 
 
11306
 
      /* Copy the packed shard. */
11307
 
      SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11308
 
                                        src_fs, dst_fs,
11309
 
                                        rev, max_files_per_dir,
11310
 
                                        iterpool));
11311
 
 
11312
 
      /* If necessary, update 'current' to the most recent packed rev,
11313
 
       * so readers can see new revisions which arrived in this pack. */
11314
 
      SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
11315
 
                                     rev + max_files_per_dir - 1,
11316
 
                                     iterpool));
11317
 
 
11318
 
      /* Remove revision files which are now packed. */
11319
 
      if (incremental)
11320
 
        {
11321
 
          SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
11322
 
                                           rev + max_files_per_dir,
11323
 
                                           max_files_per_dir, iterpool));
11324
 
          if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11325
 
            SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
11326
 
                                                 rev + max_files_per_dir,
11327
 
                                                 max_files_per_dir,
11328
 
                                                 iterpool));
11329
 
        }
11330
 
 
11331
 
      /* Now that all revisions have moved into the pack, the original
11332
 
       * rev dir can be removed. */
11333
 
      SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
11334
 
                            cancel_func, cancel_baton, iterpool));
11335
 
      if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11336
 
        SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
11337
 
                              cancel_func, cancel_baton, iterpool));
11338
 
    }
11339
 
 
11340
 
  if (cancel_func)
11341
 
    SVN_ERR(cancel_func(cancel_baton));
11342
 
 
11343
 
  /* Now, copy pairs of non-packed revisions and revprop files.
11344
 
   * If necessary, update 'current' after copying all files from a shard. */
11345
 
  SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
11346
 
  SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
11347
 
  revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
11348
 
  revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
11349
 
  SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
11350
 
  for (; rev <= src_youngest; rev++)
11351
 
    {
11352
 
      svn_error_t *err;
11353
 
 
11354
 
      svn_pool_clear(iterpool);
11355
 
 
11356
 
      if (cancel_func)
11357
 
        SVN_ERR(cancel_func(cancel_baton));
11358
 
 
11359
 
      /* Copy the rev file. */
11360
 
      err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
11361
 
                                    rev, max_files_per_dir,
11362
 
                                    iterpool);
11363
 
      if (err)
11364
 
        {
11365
 
          if (APR_STATUS_IS_ENOENT(err->apr_err) &&
11366
 
              src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11367
 
            {
11368
 
              svn_error_clear(err);
11369
 
 
11370
 
              /* The source rev file does not exist. This can happen if the
11371
 
               * source repository is being packed concurrently with this
11372
 
               * hotcopy operation.
11373
 
               *
11374
 
               * If the new revision is now packed, and the youngest revision
11375
 
               * we're interested in is not inside this pack, try to copy the
11376
 
               * pack instead.
11377
 
               *
11378
 
               * If the youngest revision ended up being packed, don't try
11379
 
               * to be smart and work around this. Just abort the hotcopy. */
11380
 
              SVN_ERR(update_min_unpacked_rev(src_fs, pool));
11381
 
              if (is_packed_rev(src_fs, rev))
11382
 
                {
11383
 
                  if (is_packed_rev(src_fs, src_youngest))
11384
 
                    return svn_error_createf(
11385
 
                             SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11386
 
                             _("The assumed HEAD revision (%lu) of the "
11387
 
                               "hotcopy source has been packed while the "
11388
 
                               "hotcopy was in progress; please restart "
11389
 
                               "the hotcopy operation"),
11390
 
                             src_youngest);
11391
 
 
11392
 
                  SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11393
 
                                                    src_fs, dst_fs,
11394
 
                                                    rev, max_files_per_dir,
11395
 
                                                    iterpool));
11396
 
                  rev = dst_min_unpacked_rev;
11397
 
                  continue;
11398
 
                }
11399
 
              else
11400
 
                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11401
 
                                         _("Revision %lu disappeared from the "
11402
 
                                           "hotcopy source while hotcopy was "
11403
 
                                           "in progress"), rev);
11404
 
            }
11405
 
          else
11406
 
            return svn_error_trace(err);
11407
 
        }
11408
 
 
11409
 
      /* Copy the revprop file. */
11410
 
      SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
11411
 
                                      revprop_dst_subdir,
11412
 
                                      rev, max_files_per_dir,
11413
 
                                      iterpool));
11414
 
 
11415
 
      /* After completing a full shard, update 'current'. */
11416
 
      if (max_files_per_dir && rev % max_files_per_dir == 0)
11417
 
        SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
11418
 
    }
11419
 
  svn_pool_destroy(iterpool);
11420
 
 
11421
 
  if (cancel_func)
11422
 
    SVN_ERR(cancel_func(cancel_baton));
11423
 
 
11424
 
  /* We assume that all revisions were copied now, i.e. we didn't exit the
11425
 
   * above loop early. 'rev' was last incremented during exit of the loop. */
11426
 
  SVN_ERR_ASSERT(rev == src_youngest + 1);
11427
 
 
11428
 
  /* All revisions were copied. Update 'current'. */
11429
 
  SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
11430
 
 
11431
 
  /* Replace the locks tree.
11432
 
   * This is racy in case readers are currently trying to list locks in
11433
 
   * the destination. However, we need to get rid of stale locks.
11434
 
   * This is the simplest way of doing this, so we accept this small race. */
11435
 
  dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
11436
 
  SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
11437
 
                             pool));
11438
 
  src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
11439
 
  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11440
 
  if (kind == svn_node_dir)
11441
 
    SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
11442
 
                                        PATH_LOCKS_DIR, TRUE,
11443
 
                                        cancel_func, cancel_baton, pool));
11444
 
 
11445
 
  /* Now copy the node-origins cache tree. */
11446
 
  src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
11447
 
  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11448
 
  if (kind == svn_node_dir)
11449
 
    SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
11450
 
                                            PATH_NODE_ORIGINS_DIR, TRUE,
11451
 
                                            cancel_func, cancel_baton, pool));
11452
 
 
11453
 
  /*
11454
 
   * NB: Data copied below is only read by writers, not readers.
11455
 
   *     Writers are still locked out at this point.
11456
 
   */
11457
 
 
11458
 
  if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
11459
 
    {
11460
 
      /* Copy the rep cache and then remove entries for revisions
11461
 
       * younger than the destination's youngest revision. */
11462
 
      src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
11463
 
      dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
11464
 
      SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11465
 
      if (kind == svn_node_file)
11466
 
        {
11467
 
          SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
11468
 
          SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
11469
 
        }
11470
 
    }
11471
 
 
11472
 
  /* Copy the txn-current file. */
11473
 
  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11474
 
    SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11475
 
                                 PATH_TXN_CURRENT, pool));
11476
 
 
11477
 
  /* If a revprop generation file exists in the source filesystem,
11478
 
   * reset it to zero (since this is on a different path, it will not
11479
 
   * overlap with data already in cache).  Also, clean up stale files
11480
 
   * used for the named atomics implementation. */
11481
 
  SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
11482
 
                            &kind, pool));
11483
 
  if (kind == svn_node_file)
11484
 
    SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
11485
 
 
11486
 
  SVN_ERR(cleanup_revprop_namespace(dst_fs));
11487
 
 
11488
 
  /* Hotcopied FS is complete. Stamp it with a format file. */
11489
 
  SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
11490
 
                       dst_ffd->format, max_files_per_dir, TRUE, pool));
11491
 
 
11492
 
  return SVN_NO_ERROR;
11493
 
}
11494
 
 
11495
 
 
11496
 
/* Set up shared data between SRC_FS and DST_FS. */
11497
 
static void
11498
 
hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
11499
 
{
11500
 
  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11501
 
  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11502
 
 
11503
 
  /* The common pool and mutexes are shared between src and dst filesystems.
11504
 
   * During hotcopy we only grab the mutexes for the destination, so there
11505
 
   * is no risk of dead-lock. We don't write to the src filesystem. Shared
11506
 
   * data for the src_fs has already been initialised in fs_hotcopy(). */
11507
 
  dst_ffd->shared = src_ffd->shared;
11508
 
}
11509
 
 
11510
 
/* Create an empty filesystem at DST_FS at DST_PATH with the same
11511
 
 * configuration as SRC_FS (uuid, format, and other parameters).
11512
 
 * After creation DST_FS has no revisions, not even revision zero. */
11513
 
static svn_error_t *
11514
 
hotcopy_create_empty_dest(svn_fs_t *src_fs,
11515
 
                          svn_fs_t *dst_fs,
11516
 
                          const char *dst_path,
11517
 
                          apr_pool_t *pool)
11518
 
{
11519
 
  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11520
 
  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11521
 
 
11522
 
  dst_fs->path = apr_pstrdup(pool, dst_path);
11523
 
 
11524
 
  dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
11525
 
  dst_ffd->config = src_ffd->config;
11526
 
  dst_ffd->format = src_ffd->format;
11527
 
 
11528
 
  /* Create the revision data directories. */
11529
 
  if (dst_ffd->max_files_per_dir)
11530
 
    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
11531
 
                                        pool));
11532
 
  else
11533
 
    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11534
 
                                                        PATH_REVS_DIR, pool),
11535
 
                                        pool));
11536
 
 
11537
 
  /* Create the revprops directory. */
11538
 
  if (src_ffd->max_files_per_dir)
11539
 
    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
11540
 
                                        pool));
11541
 
  else
11542
 
    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11543
 
                                                        PATH_REVPROPS_DIR,
11544
 
                                                        pool),
11545
 
                                        pool));
11546
 
 
11547
 
  /* Create the transaction directory. */
11548
 
  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
11549
 
                                                      pool),
11550
 
                                      pool));
11551
 
 
11552
 
  /* Create the protorevs directory. */
11553
 
  if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
11554
 
    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11555
 
                                                        PATH_TXN_PROTOS_DIR,
11556
 
                                                        pool),
11557
 
                                        pool));
11558
 
 
11559
 
  /* Create the 'current' file. */
11560
 
  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
11561
 
                             (dst_ffd->format >=
11562
 
                                SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
11563
 
                                ? "0\n" : "0 1 1\n"),
11564
 
                             pool));
11565
 
 
11566
 
  /* Create lock file and UUID. */
11567
 
  SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
11568
 
  SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
11569
 
 
11570
 
  /* Create the min unpacked rev file. */
11571
 
  if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11572
 
    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
11573
 
                                                     "0\n", pool));
11574
 
  /* Create the txn-current file if the repository supports
11575
 
     the transaction sequence file. */
11576
 
  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11577
 
    {
11578
 
      SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
11579
 
                                 "0\n", pool));
11580
 
      SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
11581
 
                                 "", pool));
11582
 
    }
11583
 
 
11584
 
  dst_ffd->youngest_rev_cache = 0;
11585
 
 
11586
 
  hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11587
 
  SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11588
 
 
11589
 
  return SVN_NO_ERROR;
11590
 
}
11591
 
 
11592
 
svn_error_t *
11593
 
svn_fs_fs__hotcopy(svn_fs_t *src_fs,
11594
 
                   svn_fs_t *dst_fs,
11595
 
                   const char *src_path,
11596
 
                   const char *dst_path,
11597
 
                   svn_boolean_t incremental,
11598
 
                   svn_cancel_func_t cancel_func,
11599
 
                   void *cancel_baton,
11600
 
                   apr_pool_t *pool)
11601
 
{
11602
 
  struct hotcopy_body_baton hbb;
11603
 
 
11604
 
  if (cancel_func)
11605
 
    SVN_ERR(cancel_func(cancel_baton));
11606
 
 
11607
 
  SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
11608
 
 
11609
 
  if (incremental)
11610
 
    {
11611
 
      const char *dst_format_abspath;
11612
 
      svn_node_kind_t dst_format_kind;
11613
 
 
11614
 
      /* Check destination format to be sure we know how to incrementally
11615
 
       * hotcopy to the destination FS. */
11616
 
      dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
11617
 
      SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
11618
 
      if (dst_format_kind == svn_node_none)
11619
 
        {
11620
 
          /* Destination doesn't exist yet. Perform a normal hotcopy to a
11621
 
           * empty destination using the same configuration as the source. */
11622
 
          SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11623
 
        }
11624
 
      else
11625
 
        {
11626
 
          /* Check the existing repository. */
11627
 
          SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
11628
 
          SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
11629
 
                                                          pool));
11630
 
          hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11631
 
          SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11632
 
        }
11633
 
    }
11634
 
  else
11635
 
    {
11636
 
      /* Start out with an empty destination using the same configuration
11637
 
       * as the source. */
11638
 
      SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11639
 
    }
11640
 
 
11641
 
  if (cancel_func)
11642
 
    SVN_ERR(cancel_func(cancel_baton));
11643
 
 
11644
 
  hbb.src_fs = src_fs;
11645
 
  hbb.dst_fs = dst_fs;
11646
 
  hbb.incremental = incremental;
11647
 
  hbb.cancel_func = cancel_func;
11648
 
  hbb.cancel_baton = cancel_baton;
11649
 
  SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
11650
 
 
 
2202
  *files = apr_array_make(result_pool, 1, sizeof(const char *));
 
2203
  APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
 
2204
                                                         result_pool);
11651
2205
  return SVN_NO_ERROR;
11652
2206
}