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

« back to all changes in this revision

Viewing changes to tools/dev/fsfs-reorg.c

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* fsfs-reorg.c -- prototypic tool to reorganize packed FSFS repositories
2
 
 *                 to reduce seeks
3
 
 *
4
 
 * ====================================================================
5
 
 *    Licensed to the Apache Software Foundation (ASF) under one
6
 
 *    or more contributor license agreements.  See the NOTICE file
7
 
 *    distributed with this work for additional information
8
 
 *    regarding copyright ownership.  The ASF licenses this file
9
 
 *    to you under the Apache License, Version 2.0 (the
10
 
 *    "License"); you may not use this file except in compliance
11
 
 *    with the License.  You may obtain a copy of the License at
12
 
 *
13
 
 *      http://www.apache.org/licenses/LICENSE-2.0
14
 
 *
15
 
 *    Unless required by applicable law or agreed to in writing,
16
 
 *    software distributed under the License is distributed on an
17
 
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
 
 *    KIND, either express or implied.  See the License for the
19
 
 *    specific language governing permissions and limitations
20
 
 *    under the License.
21
 
 * ====================================================================
22
 
 */
23
 
 
24
 
 
25
 
#include <assert.h>
26
 
 
27
 
#include <apr.h>
28
 
#include <apr_general.h>
29
 
#include <apr_file_io.h>
30
 
#include <apr_poll.h>
31
 
 
32
 
#include "svn_pools.h"
33
 
#include "svn_diff.h"
34
 
#include "svn_io.h"
35
 
#include "svn_utf.h"
36
 
#include "svn_dirent_uri.h"
37
 
#include "svn_sorts.h"
38
 
#include "svn_delta.h"
39
 
#include "svn_hash.h"
40
 
 
41
 
#include "private/svn_string_private.h"
42
 
#include "private/svn_subr_private.h"
43
 
#include "private/svn_dep_compat.h"
44
 
 
45
 
#ifndef _
46
 
#define _(x) x
47
 
#endif
48
 
 
49
 
#define ERROR_TAG "fsfs-reporg: "
50
 
 
51
 
/* forward declarations */
52
 
typedef struct noderev_t noderev_t;
53
 
typedef struct revision_info_t revision_info_t;
54
 
 
55
 
/* A FSFS rev file is sequence of fragments and unused space (the latter
56
 
 * only being inserted by this tool and not during ordinary SVN operation).
57
 
 *
58
 
 * This type defines the type of any fragment.
59
 
 *
60
 
 * Please note that the classification as "property", "dir" or "file"
61
 
 * fragments is only to be used while determining the future placement
62
 
 * of a representation.  If the rep is shared, the same rep may be used
63
 
 * as *any* of the 3 kinds.
64
 
 */
65
 
enum fragment_kind_t
66
 
{
67
 
  /* the 2 number line containing changes and root node offsets */
68
 
  header_fragment,
69
 
 
70
 
  /* list of all changes in a revision */
71
 
  changes_fragment,
72
 
 
73
 
  /* (the textual representation of) a noderev */
74
 
  noderev_fragment,
75
 
 
76
 
  /* a property rep (including PLAIN / DELTA header) */
77
 
  property_fragment,
78
 
 
79
 
  /* a directory rep (including PLAIN / DELTA header) */
80
 
  dir_fragment,
81
 
 
82
 
  /* a file rep (including PLAIN / DELTA header) */
83
 
  file_fragment
84
 
};
85
 
 
86
 
/* A fragment.  This is used to represent the final ordering, i.e. there
87
 
 * will be an array containing elements of this type that basically put
88
 
 * a fragment at some location in the target file.
89
 
 */
90
 
typedef struct fragment_t
91
 
{
92
 
  /* position in the target file */
93
 
  apr_size_t position;
94
 
 
95
 
  /* kind of fragment */
96
 
  enum fragment_kind_t kind;
97
 
 
98
 
  /* pointer to the  fragment struct; type depends on KIND */
99
 
  void *data;
100
 
} fragment_t;
101
 
 
102
 
/* Location info for a single revision.
103
 
 */
104
 
typedef struct revision_location_t
105
 
{
106
 
  /* pack file offset (manifest value), 0 for non-packed files */
107
 
  apr_size_t offset;
108
 
 
109
 
  /* offset of the changes list relative to OFFSET */
110
 
  apr_size_t changes;
111
 
 
112
 
  /* length of the changes list on bytes */
113
 
  apr_size_t changes_len;
114
 
 
115
 
  /* first offset behind the revision data in the pack file (file length
116
 
   * for non-packed revs) */
117
 
  apr_size_t end;
118
 
} revision_location_t;
119
 
 
120
 
/* Absolute position and size of some item.
121
 
 */
122
 
typedef struct location_t
123
 
{
124
 
  /* absolute offset in the file */
125
 
  apr_size_t offset;
126
 
 
127
 
  /* item length in bytes */
128
 
  apr_size_t size;
129
 
} location_t;
130
 
 
131
 
/* A parsed directory entry. Note that instances of this struct may be
132
 
 * shared between different DIRECTORY_T containers.
133
 
 */
134
 
typedef struct direntry_t
135
 
{
136
 
  /* (local) entry / path name */
137
 
  const char *name;
138
 
 
139
 
  /* strlen (name) */
140
 
  apr_size_t name_len;
141
 
 
142
 
  /* node rev providing ID and representation(s) */
143
 
  noderev_t *node;
144
 
} direntry_t;
145
 
 
146
 
/* Representation of a parsed directory content.
147
 
 */
148
 
typedef struct directory_t
149
 
{
150
 
  /* array of pointers to DIRENTRY_T */
151
 
  apr_array_header_t *entries;
152
 
 
153
 
  /* MD5 of the textual representation. Will be set lazily as a side-effect
154
 
   * of determining the length of this dir's textual representation. */
155
 
  unsigned char target_md5[16];
156
 
 
157
 
  /* (expanded) length of the textual representation.
158
 
   * Determined lazily during the write process. */
159
 
  apr_size_t size;
160
 
} directory_t;
161
 
 
162
 
/* A representation fragment.
163
 
 */
164
 
typedef struct representation_t
165
 
{
166
 
  /* location in the source file */
167
 
  location_t original;
168
 
 
169
 
  /* location in the reordered target file */
170
 
  location_t target;
171
 
 
172
 
  /* length of the PLAIN / DELTA line in the source file in bytes */
173
 
  apr_size_t header_size;
174
 
 
175
 
  /* deltification base, or NULL if there is none */
176
 
  struct representation_t *delta_base;
177
 
 
178
 
  /* revision that contains this representation
179
 
   * (may be referenced by other revisions, though) */
180
 
  revision_info_t *revision;
181
 
 
182
 
  /* representation content parsed as a directory. This will be NULL, if
183
 
   * *no* directory noderev uses this representation. */
184
 
  directory_t *dir;
185
 
 
186
 
  /* the source content has a PLAIN header, so we may simply copy the
187
 
   * source content into the target */
188
 
  svn_boolean_t is_plain;
189
 
 
190
 
  /* coloring flag used in the reordering algorithm to keep track of
191
 
   * representations that still need to be placed. */
192
 
  svn_boolean_t covered;
193
 
} representation_t;
194
 
 
195
 
/* A node rev.
196
 
 */
197
 
struct noderev_t
198
 
{
199
 
  /* location within the source file */
200
 
  location_t original;
201
 
 
202
 
  /* location within the reorganized target file. */
203
 
  location_t target;
204
 
 
205
 
  /* predecessor node, or NULL if there is none */
206
 
  noderev_t *predecessor;
207
 
 
208
 
  /* content representation; may be NULL if there is none */
209
 
  representation_t *text;
210
 
 
211
 
  /* properties representation; may be NULL if there is none */
212
 
  representation_t *props;
213
 
 
214
 
  /* revision that this noderev belongs to */
215
 
  revision_info_t *revision;
216
 
 
217
 
  /* coloring flag used in the reordering algorithm to keep track of
218
 
   * representations that still need to be placed. */
219
 
  svn_boolean_t covered;
220
 
};
221
 
 
222
 
/* Represents a single revision.
223
 
 * There will be only one instance per revision. */
224
 
struct revision_info_t
225
 
{
226
 
  /* number of this revision */
227
 
  svn_revnum_t revision;
228
 
 
229
 
  /* position in the source file */
230
 
  revision_location_t original;
231
 
 
232
 
  /* position in the reorganized target file */
233
 
  revision_location_t target;
234
 
 
235
 
  /* noderev of the root directory */
236
 
  noderev_t *root_noderev;
237
 
 
238
 
  /* all noderevs_t of this revision (ordered by source file offset),
239
 
   * i.e. those that point back to this struct */
240
 
  apr_array_header_t *node_revs;
241
 
 
242
 
  /* all representation_t of this revision (ordered by source file offset),
243
 
   * i.e. those that point back to this struct */
244
 
  apr_array_header_t *representations;
245
 
};
246
 
 
247
 
/* Represents a packed revision file.
248
 
 */
249
 
typedef struct revision_pack_t
250
 
{
251
 
  /* first revision in the pack file */
252
 
  svn_revnum_t base;
253
 
 
254
 
  /* revision_info_t* of all revisions in the pack file; in revision order. */
255
 
  apr_array_header_t *info;
256
 
 
257
 
  /* list of fragments to place in the target pack file; in target order. */
258
 
  apr_array_header_t *fragments;
259
 
 
260
 
  /* source pack file length */
261
 
  apr_size_t filesize;
262
 
 
263
 
  /* temporary value. Equal to the number of bytes in the target pack file
264
 
   * already allocated to fragments. */
265
 
  apr_size_t target_offset;
266
 
} revision_pack_t;
267
 
 
268
 
/* Cache for revision source content.  All content is stored in DATA and
269
 
 * the HASH maps revision number to an svn_string_t instance whose data
270
 
 * member points into DATA.
271
 
 *
272
 
 * Once TOTAL_SIZE exceeds LIMIT, all content will be discarded.  Similarly,
273
 
 * the hash gets cleared every 10000 insertions to keep the HASH_POOL
274
 
 * memory usage in check.
275
 
 */
276
 
typedef struct content_cache_t
277
 
{
278
 
  /* pool used for HASH */
279
 
  apr_pool_t *hash_pool;
280
 
 
281
 
  /* svn_revnum_t -> svn_string_t.
282
 
   * The strings become (potentially) invalid when adding new cache entries. */
283
 
  apr_hash_t *hash;
284
 
 
285
 
  /* data buffer. the first TOTAL_SIZE bytes are actually being used. */
286
 
  char *data;
287
 
 
288
 
  /* DATA capacity */
289
 
  apr_size_t limit;
290
 
 
291
 
  /* number of bytes used in DATA */
292
 
  apr_size_t total_size;
293
 
 
294
 
  /* number of insertions since the last hash cleanup */
295
 
  apr_size_t insert_count;
296
 
} content_cache_t;
297
 
 
298
 
/* A cached directory. In contrast to directory_t, this stored the data as
299
 
 * the plain hash that the normal FSFS will use to serialize & diff dirs.
300
 
 */
301
 
typedef struct dir_cache_entry_t
302
 
{
303
 
  /* revision containing the representation */
304
 
  svn_revnum_t revision;
305
 
 
306
 
  /* offset of the representation within that revision */
307
 
  apr_size_t offset;
308
 
 
309
 
  /* key-value representation of the directory entries */
310
 
  apr_hash_t *hash;
311
 
} dir_cache_entry_t;
312
 
 
313
 
/* Directory cache. (revision, offset) will be mapped directly into the
314
 
 * ENTRIES array of ENTRY_COUNT buckets (many entries will be NULL).
315
 
 * Two alternating pools will be used to allocate dir content.
316
 
 *
317
 
 * If the INSERT_COUNT exceeds a given limit, the pools get exchanged and
318
 
 * the older of the two will be cleared. This is to keep dir objects valid
319
 
 * for at least one insertion.
320
 
 */
321
 
typedef struct dir_cache_t
322
 
{
323
 
  /* fixed-size array of ENTRY_COUNT elements */
324
 
  dir_cache_entry_t *entries;
325
 
 
326
 
  /* currently used for entry allocations */
327
 
  apr_pool_t *pool1;
328
 
 
329
 
  /* previously used for entry allocations */
330
 
  apr_pool_t *pool2;
331
 
 
332
 
  /* size of ENTRIES in elements */
333
 
  apr_size_t entry_count;
334
 
 
335
 
  /* number of directory elements added. I.e. usually >> #cached dirs */
336
 
  apr_size_t insert_count;
337
 
} dir_cache_t;
338
 
 
339
 
/* A cached, undeltified txdelta window.
340
 
 */
341
 
typedef struct window_cache_entry_t
342
 
{
343
 
  /* revision containing the window */
344
 
  svn_revnum_t revision;
345
 
 
346
 
  /* offset of the deltified window within that revision */
347
 
  apr_size_t offset;
348
 
 
349
 
  /* window content */
350
 
  svn_stringbuf_t *window;
351
 
} window_cache_entry_t;
352
 
 
353
 
/* Cache for undeltified txdelta windows. (revision, offset) will be mapped
354
 
 * directly into the ENTRIES array of INSERT_COUNT buckets (most entries
355
 
 * will be NULL).
356
 
 *
357
 
 * The cache will be cleared when USED exceeds CAPACITY.
358
 
 */
359
 
typedef struct window_cache_t
360
 
{
361
 
  /* fixed-size array of ENTRY_COUNT elements */
362
 
  window_cache_entry_t *entries;
363
 
 
364
 
  /* used to allocate windows */
365
 
  apr_pool_t *pool;
366
 
 
367
 
  /* size of ENTRIES in elements */
368
 
  apr_size_t entry_count;
369
 
 
370
 
  /* maximum combined size of all cached windows */
371
 
  apr_size_t capacity;
372
 
 
373
 
  /* current combined size of all cached windows */
374
 
  apr_size_t used;
375
 
} window_cache_t;
376
 
 
377
 
/* Root data structure containing all information about a given repository.
378
 
 */
379
 
typedef struct fs_fs_t
380
 
{
381
 
  /* repository to reorg */
382
 
  const char *path;
383
 
 
384
 
  /* revision to start at (must be 0, ATM) */
385
 
  svn_revnum_t start_revision;
386
 
 
387
 
  /* FSFS format number */
388
 
  int format;
389
 
 
390
 
  /* highest revision number in the repo */
391
 
  svn_revnum_t max_revision;
392
 
 
393
 
  /* first non-packed revision */
394
 
  svn_revnum_t min_unpacked_rev;
395
 
 
396
 
  /* sharing size*/
397
 
  int max_files_per_dir;
398
 
 
399
 
  /* all revisions */
400
 
  apr_array_header_t *revisions;
401
 
 
402
 
  /* all packed files */
403
 
  apr_array_header_t *packs;
404
 
 
405
 
  /* empty representation.
406
 
   * Used as a dummy base for DELTA reps without base. */
407
 
  representation_t *null_base;
408
 
 
409
 
  /* revision content cache */
410
 
  content_cache_t *cache;
411
 
 
412
 
  /* directory hash cache */
413
 
  dir_cache_t *dir_cache;
414
 
 
415
 
  /* undeltified txdelta window cache */
416
 
  window_cache_t *window_cache;
417
 
} fs_fs_t;
418
 
 
419
 
/* Return the rev pack folder for revision REV in FS.
420
 
 */
421
 
static const char *
422
 
get_pack_folder(fs_fs_t *fs,
423
 
                svn_revnum_t rev,
424
 
                apr_pool_t *pool)
425
 
{
426
 
  return apr_psprintf(pool, "%s/db/revs/%ld.pack",
427
 
                      fs->path, rev / fs->max_files_per_dir);
428
 
}
429
 
 
430
 
/* Return the path of the file containing revision REV in FS.
431
 
 */
432
 
static const char *
433
 
rev_or_pack_file_name(fs_fs_t *fs,
434
 
                      svn_revnum_t rev,
435
 
                      apr_pool_t *pool)
436
 
{
437
 
  return fs->min_unpacked_rev > rev
438
 
     ? svn_dirent_join(get_pack_folder(fs, rev, pool), "pack", pool)
439
 
     : apr_psprintf(pool, "%s/db/revs/%ld/%ld", fs->path,
440
 
                          rev / fs->max_files_per_dir, rev);
441
 
}
442
 
 
443
 
/* Open the file containing revision REV in FS and return it in *FILE.
444
 
 */
445
 
static svn_error_t *
446
 
open_rev_or_pack_file(apr_file_t **file,
447
 
                      fs_fs_t *fs,
448
 
                      svn_revnum_t rev,
449
 
                      apr_pool_t *pool)
450
 
{
451
 
  return svn_io_file_open(file,
452
 
                          rev_or_pack_file_name(fs, rev, pool),
453
 
                          APR_READ | APR_BUFFERED,
454
 
                          APR_OS_DEFAULT,
455
 
                          pool);
456
 
}
457
 
 
458
 
/* Read the whole content of the file containing REV in FS and return that
459
 
 * in *CONTENT.
460
 
 */
461
 
static svn_error_t *
462
 
read_rev_or_pack_file(svn_stringbuf_t **content,
463
 
                      fs_fs_t *fs,
464
 
                      svn_revnum_t rev,
465
 
                      apr_pool_t *pool)
466
 
{
467
 
  return svn_stringbuf_from_file2(content,
468
 
                                  rev_or_pack_file_name(fs, rev, pool),
469
 
                                  pool);
470
 
}
471
 
 
472
 
/* Return a new content cache with the given size LIMIT.  Use POOL for
473
 
 * all cache-related allocations.
474
 
 */
475
 
static content_cache_t *
476
 
create_content_cache(apr_pool_t *pool,
477
 
                     apr_size_t limit)
478
 
{
479
 
  content_cache_t *result = apr_pcalloc(pool, sizeof(*result));
480
 
 
481
 
  result->hash_pool = svn_pool_create(pool);
482
 
  result->hash = svn_hash__make(result->hash_pool);
483
 
  result->limit = limit;
484
 
  result->total_size = 0;
485
 
  result->insert_count = 0;
486
 
  result->data = apr_palloc(pool, limit);
487
 
 
488
 
  return result;
489
 
}
490
 
 
491
 
/* Return the content of revision REVISION from CACHE. Return NULL upon a
492
 
 * cache miss. This is a cache-internal function.
493
 
 */
494
 
static svn_string_t *
495
 
get_cached_content(content_cache_t *cache,
496
 
                   svn_revnum_t revision)
497
 
{
498
 
  return apr_hash_get(cache->hash, &revision, sizeof(revision));
499
 
}
500
 
 
501
 
/* Take the content in DATA and store it under REVISION in CACHE.
502
 
 * This is a cache-internal function.
503
 
 */
504
 
static void
505
 
set_cached_content(content_cache_t *cache,
506
 
                   svn_revnum_t revision,
507
 
                   svn_string_t *data)
508
 
{
509
 
  svn_string_t *content;
510
 
  svn_revnum_t *key;
511
 
 
512
 
  /* double insertion? -> broken cache logic */
513
 
  assert(get_cached_content(cache, revision) == NULL);
514
 
 
515
 
  /* purge the cache upon overflow */
516
 
  if (cache->total_size + data->len > cache->limit)
517
 
    {
518
 
      /* the hash pool grows slowly over time; clear it once in a while */
519
 
      if (cache->insert_count > 10000)
520
 
        {
521
 
          svn_pool_clear(cache->hash_pool);
522
 
          cache->hash = svn_hash__make(cache->hash_pool);
523
 
          cache->insert_count = 0;
524
 
        }
525
 
      else
526
 
        cache->hash = svn_hash__make(cache->hash_pool);
527
 
 
528
 
      cache->total_size = 0;
529
 
 
530
 
      /* buffer overflow / revision too large */
531
 
      if (data->len > cache->limit)
532
 
        SVN_ERR_MALFUNCTION_NO_RETURN();
533
 
    }
534
 
 
535
 
  /* copy data to cache and update he index (hash) */
536
 
  content = apr_palloc(cache->hash_pool, sizeof(*content));
537
 
  content->data = cache->data + cache->total_size;
538
 
  content->len = data->len;
539
 
 
540
 
  memcpy(cache->data + cache->total_size, data->data, data->len);
541
 
  cache->total_size += data->len;
542
 
 
543
 
  key = apr_palloc(cache->hash_pool, sizeof(*key));
544
 
  *key = revision;
545
 
 
546
 
  apr_hash_set(cache->hash, key, sizeof(*key), content);
547
 
  ++cache->insert_count;
548
 
}
549
 
 
550
 
/* Get the file content of revision REVISION in FS and return it in *DATA.
551
 
 * Use SCRATCH_POOL for temporary allocations.
552
 
 */
553
 
static svn_error_t *
554
 
get_content(svn_string_t **data,
555
 
            fs_fs_t *fs,
556
 
            svn_revnum_t revision,
557
 
            apr_pool_t *scratch_pool)
558
 
{
559
 
  apr_file_t *file;
560
 
  revision_info_t *revision_info;
561
 
  svn_stringbuf_t *temp;
562
 
  apr_off_t temp_offset;
563
 
 
564
 
  /* try getting the data from our cache */
565
 
  svn_string_t *result = get_cached_content(fs->cache, revision);
566
 
  if (result)
567
 
    {
568
 
      *data = result;
569
 
      return SVN_NO_ERROR;
570
 
    }
571
 
 
572
 
  /* not in cache. Is the revision valid at all? */
573
 
  if (revision - fs->start_revision > fs->revisions->nelts)
574
 
    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
575
 
                             _("Unknown revision %ld"), revision);
576
 
  revision_info = APR_ARRAY_IDX(fs->revisions,
577
 
                                revision - fs->start_revision,
578
 
                                revision_info_t*);
579
 
 
580
 
  /* read the revision content. Assume that the file has *not* been
581
 
   * reorg'ed, yet, i.e. all data is in one place. */
582
 
  temp = svn_stringbuf_create_ensure(  revision_info->original.end
583
 
                                     - revision_info->original.offset,
584
 
                                     scratch_pool);
585
 
  temp->len = revision_info->original.end - revision_info->original.offset;
586
 
  SVN_ERR(open_rev_or_pack_file(&file, fs, revision, scratch_pool));
587
 
 
588
 
  temp_offset = revision_info->original.offset;
589
 
  SVN_ERR(svn_io_file_seek(file, APR_SET, &temp_offset,
590
 
                           scratch_pool));
591
 
  SVN_ERR_ASSERT(temp_offset < APR_SIZE_MAX);
592
 
  revision_info->original.offset = (apr_size_t)temp_offset;
593
 
  SVN_ERR(svn_io_file_read(file, temp->data, &temp->len, scratch_pool));
594
 
 
595
 
  /* cache the result and return it */
596
 
  set_cached_content(fs->cache, revision,
597
 
                     svn_stringbuf__morph_into_string(temp));
598
 
  *data = get_cached_content(fs->cache, revision);
599
 
 
600
 
  return SVN_NO_ERROR;
601
 
}
602
 
 
603
 
/* Return a new directory cache with ENTRY_COUNT buckets in its index.
604
 
 * Use POOL for all cache-related allocations.
605
 
 */
606
 
static dir_cache_t *
607
 
create_dir_cache(apr_pool_t *pool,
608
 
                 apr_size_t entry_count)
609
 
{
610
 
  dir_cache_t *result = apr_pcalloc(pool, sizeof(*result));
611
 
 
612
 
  result->pool1 = svn_pool_create(pool);
613
 
  result->pool2 = svn_pool_create(pool);
614
 
  result->entry_count = entry_count;
615
 
  result->insert_count = 0;
616
 
  result->entries = apr_pcalloc(pool, sizeof(*result->entries) * entry_count);
617
 
 
618
 
  return result;
619
 
}
620
 
 
621
 
/* Return the position within FS' dir cache ENTRIES index for the given
622
 
 * (REVISION, OFFSET) pair. This is a cache-internal function.
623
 
 */
624
 
static apr_size_t
625
 
get_dir_cache_index(fs_fs_t *fs,
626
 
                    svn_revnum_t revision,
627
 
                    apr_size_t offset)
628
 
{
629
 
  return (revision + offset * 0xd1f3da69) % fs->dir_cache->entry_count;
630
 
}
631
 
 
632
 
/* Return the currently active pool of FS' dir cache. Note that it may be
633
 
 * cleared after *2* insertions.
634
 
 */
635
 
static apr_pool_t *
636
 
get_cached_dir_pool(fs_fs_t *fs)
637
 
{
638
 
  return fs->dir_cache->pool1;
639
 
}
640
 
 
641
 
/* Return the cached directory content stored in REPRESENTATION within FS.
642
 
 * If that has not been found in cache, return NULL.
643
 
 */
644
 
static apr_hash_t *
645
 
get_cached_dir(fs_fs_t *fs,
646
 
               representation_t *representation)
647
 
{
648
 
  svn_revnum_t revision = representation->revision->revision;
649
 
  apr_size_t offset = representation->original.offset;
650
 
 
651
 
  apr_size_t i = get_dir_cache_index(fs, revision, offset);
652
 
  dir_cache_entry_t *entry = &fs->dir_cache->entries[i];
653
 
 
654
 
  return entry->offset == offset && entry->revision == revision
655
 
    ? entry->hash
656
 
    : NULL;
657
 
}
658
 
 
659
 
/* Cache the directory HASH for  REPRESENTATION within FS.
660
 
 */
661
 
static void
662
 
set_cached_dir(fs_fs_t *fs,
663
 
               representation_t *representation,
664
 
               apr_hash_t *hash)
665
 
{
666
 
  /* select the entry to use */
667
 
  svn_revnum_t revision = representation->revision->revision;
668
 
  apr_size_t offset = representation->original.offset;
669
 
 
670
 
  apr_size_t i = get_dir_cache_index(fs, revision, offset);
671
 
  dir_cache_entry_t *entry = &fs->dir_cache->entries[i];
672
 
 
673
 
  /* clean the cache and rotate pools at regular intervals */
674
 
  fs->dir_cache->insert_count += apr_hash_count(hash);
675
 
  if (fs->dir_cache->insert_count >= fs->dir_cache->entry_count * 100)
676
 
    {
677
 
      apr_pool_t *pool;
678
 
 
679
 
      svn_pool_clear(fs->dir_cache->pool2);
680
 
      memset(fs->dir_cache->entries,
681
 
             0,
682
 
             sizeof(*fs->dir_cache->entries) * fs->dir_cache->entry_count);
683
 
      fs->dir_cache->insert_count = 0;
684
 
 
685
 
      pool = fs->dir_cache->pool2;
686
 
      fs->dir_cache->pool2 = fs->dir_cache->pool1;
687
 
      fs->dir_cache->pool1 = pool;
688
 
    }
689
 
 
690
 
  /* write data to cache */
691
 
  entry->hash = hash;
692
 
  entry->offset = offset;
693
 
  entry->revision = revision;
694
 
}
695
 
 
696
 
/* Return a new txdelta window cache with ENTRY_COUNT buckets in its index
697
 
 * and a the total CAPACITY given in bytes.
698
 
 * Use POOL for all cache-related allocations.
699
 
 */
700
 
static window_cache_t *
701
 
create_window_cache(apr_pool_t *pool,
702
 
                    apr_size_t entry_count,
703
 
                    apr_size_t capacity)
704
 
{
705
 
  window_cache_t *result = apr_pcalloc(pool, sizeof(*result));
706
 
 
707
 
  result->pool = svn_pool_create(pool);
708
 
  result->entry_count = entry_count;
709
 
  result->capacity = capacity;
710
 
  result->used = 0;
711
 
  result->entries = apr_pcalloc(pool, sizeof(*result->entries) * entry_count);
712
 
 
713
 
  return result;
714
 
}
715
 
 
716
 
/* Return the position within FS' window cache ENTRIES index for the given
717
 
 * (REVISION, OFFSET) pair. This is a cache-internal function.
718
 
 */
719
 
static apr_size_t
720
 
get_window_cache_index(fs_fs_t *fs,
721
 
                       svn_revnum_t revision,
722
 
                       apr_size_t offset)
723
 
{
724
 
  return (revision + offset * 0xd1f3da69) % fs->window_cache->entry_count;
725
 
}
726
 
 
727
 
/* Return the cached txdelta window stored in REPRESENTATION within FS.
728
 
 * If that has not been found in cache, return NULL.
729
 
 */
730
 
static svn_stringbuf_t *
731
 
get_cached_window(fs_fs_t *fs,
732
 
                  representation_t *representation,
733
 
                  apr_pool_t *pool)
734
 
{
735
 
  svn_revnum_t revision = representation->revision->revision;
736
 
  apr_size_t offset = representation->original.offset;
737
 
 
738
 
  apr_size_t i = get_window_cache_index(fs, revision, offset);
739
 
  window_cache_entry_t *entry = &fs->window_cache->entries[i];
740
 
 
741
 
  return entry->offset == offset && entry->revision == revision
742
 
    ? svn_stringbuf_dup(entry->window, pool)
743
 
    : NULL;
744
 
}
745
 
 
746
 
/* Cache the undeltified txdelta WINDOW for REPRESENTATION within FS.
747
 
 */
748
 
static void
749
 
set_cached_window(fs_fs_t *fs,
750
 
                  representation_t *representation,
751
 
                  svn_stringbuf_t *window)
752
 
{
753
 
  /* select entry */
754
 
  svn_revnum_t revision = representation->revision->revision;
755
 
  apr_size_t offset = representation->original.offset;
756
 
 
757
 
  apr_size_t i = get_window_cache_index(fs, revision, offset);
758
 
  window_cache_entry_t *entry = &fs->window_cache->entries[i];
759
 
 
760
 
  /* if the capacity is exceeded, clear the cache */
761
 
  fs->window_cache->used += window->len;
762
 
  if (fs->window_cache->used >= fs->window_cache->capacity)
763
 
    {
764
 
      svn_pool_clear(fs->window_cache->pool);
765
 
      memset(fs->window_cache->entries,
766
 
             0,
767
 
             sizeof(*fs->window_cache->entries) * fs->window_cache->entry_count);
768
 
      fs->window_cache->used = window->len;
769
 
    }
770
 
 
771
 
  /* set the entry to a copy of the window data */
772
 
  entry->window = svn_stringbuf_dup(window, fs->window_cache->pool);
773
 
  entry->offset = offset;
774
 
  entry->revision = revision;
775
 
}
776
 
 
777
 
/* Given rev pack PATH in FS, read the manifest file and return the offsets
778
 
 * in *MANIFEST. Use POOL for allocations.
779
 
 */
780
 
static svn_error_t *
781
 
read_manifest(apr_array_header_t **manifest,
782
 
              fs_fs_t *fs,
783
 
              const char *path,
784
 
              apr_pool_t *pool)
785
 
{
786
 
  svn_stream_t *manifest_stream;
787
 
  apr_pool_t *iterpool;
788
 
 
789
 
  /* Open the manifest file. */
790
 
  SVN_ERR(svn_stream_open_readonly(&manifest_stream,
791
 
                                   svn_dirent_join(path, "manifest", pool),
792
 
                                   pool, pool));
793
 
 
794
 
  /* While we're here, let's just read the entire manifest file into an array,
795
 
     so we can cache the entire thing. */
796
 
  iterpool = svn_pool_create(pool);
797
 
  *manifest = apr_array_make(pool, fs->max_files_per_dir, sizeof(apr_size_t));
798
 
  while (1)
799
 
    {
800
 
      svn_stringbuf_t *sb;
801
 
      svn_boolean_t eof;
802
 
      apr_uint64_t val;
803
 
      svn_error_t *err;
804
 
 
805
 
      svn_pool_clear(iterpool);
806
 
      SVN_ERR(svn_stream_readline(manifest_stream, &sb, "\n", &eof, iterpool));
807
 
      if (eof)
808
 
        break;
809
 
 
810
 
      err = svn_cstring_strtoui64(&val, sb->data, 0, APR_SIZE_MAX, 10);
811
 
      if (err)
812
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
813
 
                                 _("Manifest offset '%s' too large"),
814
 
                                 sb->data);
815
 
      APR_ARRAY_PUSH(*manifest, apr_size_t) = (apr_size_t)val;
816
 
    }
817
 
  svn_pool_destroy(iterpool);
818
 
 
819
 
  return svn_stream_close(manifest_stream);
820
 
}
821
 
 
822
 
/* Read header information for the revision stored in FILE_CONTENT at
823
 
 * offsets START or END.  Return the offsets within FILE_CONTENT for the
824
 
 * *ROOT_NODEREV, the list of *CHANGES and its len in *CHANGES_LEN.
825
 
 * Use POOL for temporary allocations. */
826
 
static svn_error_t *
827
 
read_revision_header(apr_size_t *changes,
828
 
                     apr_size_t *changes_len,
829
 
                     apr_size_t *root_noderev,
830
 
                     svn_stringbuf_t *file_content,
831
 
                     apr_size_t start,
832
 
                     apr_size_t end,
833
 
                     apr_pool_t *pool)
834
 
{
835
 
  char buf[64];
836
 
  const char *line;
837
 
  char *space;
838
 
  apr_uint64_t val;
839
 
  apr_size_t len;
840
 
 
841
 
  /* Read in this last block, from which we will identify the last line. */
842
 
  len = sizeof(buf);
843
 
  if (start + len > end)
844
 
    len = end - start;
845
 
 
846
 
  memcpy(buf, file_content->data + end - len, len);
847
 
 
848
 
  /* The last byte should be a newline. */
849
 
  if (buf[(apr_ssize_t)len - 1] != '\n')
850
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
851
 
                            _("Revision lacks trailing newline"));
852
 
 
853
 
  /* Look for the next previous newline. */
854
 
  buf[len - 1] = 0;
855
 
  line = strrchr(buf, '\n');
856
 
  if (line == NULL)
857
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
858
 
                            _("Final line in revision file longer "
859
 
                              "than 64 characters"));
860
 
 
861
 
  space = strchr(line, ' ');
862
 
  if (space == NULL)
863
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
864
 
                            _("Final line in revision file missing space"));
865
 
 
866
 
  /* terminate the header line */
867
 
  *space = 0;
868
 
 
869
 
  /* extract information */
870
 
  SVN_ERR(svn_cstring_strtoui64(&val, line+1, 0, APR_SIZE_MAX, 10));
871
 
  *root_noderev = (apr_size_t)val;
872
 
  SVN_ERR(svn_cstring_strtoui64(&val, space+1, 0, APR_SIZE_MAX, 10));
873
 
  *changes = (apr_size_t)val;
874
 
  *changes_len = end - *changes - start - (buf + len - line) + 1;
875
 
 
876
 
  return SVN_NO_ERROR;
877
 
}
878
 
 
879
 
/* Read the FSFS format number and sharding size from the format file at
880
 
 * PATH and return it in *PFORMAT and *MAX_FILES_PER_DIR respectively.
881
 
 * Use POOL for temporary allocations.
882
 
 */
883
 
static svn_error_t *
884
 
read_format(int *pformat, int *max_files_per_dir,
885
 
            const char *path, apr_pool_t *pool)
886
 
{
887
 
  svn_error_t *err;
888
 
  apr_file_t *file;
889
 
  char buf[80];
890
 
  apr_size_t len;
891
 
 
892
 
  /* open format file and read the first line */
893
 
  err = svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
894
 
                         APR_OS_DEFAULT, pool);
895
 
  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
896
 
    {
897
 
      /* Treat an absent format file as format 1.  Do not try to
898
 
         create the format file on the fly, because the repository
899
 
         might be read-only for us, or this might be a read-only
900
 
         operation, and the spirit of FSFS is to make no changes
901
 
         whatseover in read-only operations.  See thread starting at
902
 
         http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
903
 
         for more. */
904
 
      svn_error_clear(err);
905
 
      *pformat = 1;
906
 
      *max_files_per_dir = 0;
907
 
 
908
 
      return SVN_NO_ERROR;
909
 
    }
910
 
  SVN_ERR(err);
911
 
 
912
 
  len = sizeof(buf);
913
 
  err = svn_io_read_length_line(file, buf, &len, pool);
914
 
  if (err && APR_STATUS_IS_EOF(err->apr_err))
915
 
    {
916
 
      /* Return a more useful error message. */
917
 
      svn_error_clear(err);
918
 
      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
919
 
                               _("Can't read first line of format file '%s'"),
920
 
                               svn_dirent_local_style(path, pool));
921
 
    }
922
 
  SVN_ERR(err);
923
 
 
924
 
  /* Check that the first line contains only digits. */
925
 
  SVN_ERR(svn_cstring_atoi(pformat, buf));
926
 
 
927
 
  /* Set the default values for anything that can be set via an option. */
928
 
  *max_files_per_dir = 0;
929
 
 
930
 
  /* Read any options. */
931
 
  while (1)
932
 
    {
933
 
      len = sizeof(buf);
934
 
      err = svn_io_read_length_line(file, buf, &len, pool);
935
 
      if (err && APR_STATUS_IS_EOF(err->apr_err))
936
 
        {
937
 
          /* No more options; that's okay. */
938
 
          svn_error_clear(err);
939
 
          break;
940
 
        }
941
 
      SVN_ERR(err);
942
 
 
943
 
      if (strncmp(buf, "layout ", 7) == 0)
944
 
        {
945
 
          if (strcmp(buf+7, "linear") == 0)
946
 
            {
947
 
              *max_files_per_dir = 0;
948
 
              continue;
949
 
            }
950
 
 
951
 
          if (strncmp(buf+7, "sharded ", 8) == 0)
952
 
            {
953
 
              /* Check that the argument is numeric. */
954
 
              SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf + 15));
955
 
              continue;
956
 
            }
957
 
        }
958
 
 
959
 
      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
960
 
         _("'%s' contains invalid filesystem format option '%s'"),
961
 
         svn_dirent_local_style(path, pool), buf);
962
 
    }
963
 
 
964
 
  return svn_io_file_close(file, pool);
965
 
}
966
 
 
967
 
/* Read the content of the file at PATH and return it in *RESULT.
968
 
 * Use POOL for temporary allocations.
969
 
 */
970
 
static svn_error_t *
971
 
read_number(svn_revnum_t *result, const char *path, apr_pool_t *pool)
972
 
{
973
 
  svn_stringbuf_t *content;
974
 
  apr_uint64_t number;
975
 
 
976
 
  SVN_ERR(svn_stringbuf_from_file2(&content, path, pool));
977
 
 
978
 
  content->data[content->len-1] = 0;
979
 
  SVN_ERR(svn_cstring_strtoui64(&number, content->data, 0, LONG_MAX, 10));
980
 
  *result = (svn_revnum_t)number;
981
 
 
982
 
  return SVN_NO_ERROR;
983
 
}
984
 
 
985
 
/* Create *FS for the repository at PATH and read the format and size info.
986
 
 * Use POOL for temporary allocations.
987
 
 */
988
 
static svn_error_t *
989
 
fs_open(fs_fs_t **fs, const char *path, apr_pool_t *pool)
990
 
{
991
 
  *fs = apr_pcalloc(pool, sizeof(**fs));
992
 
  (*fs)->path = apr_pstrdup(pool, path);
993
 
  (*fs)->max_files_per_dir = 1000;
994
 
 
995
 
  /* Read the FS format number. */
996
 
  SVN_ERR(read_format(&(*fs)->format,
997
 
                      &(*fs)->max_files_per_dir,
998
 
                      svn_dirent_join(path, "db/format", pool),
999
 
                      pool));
1000
 
  if (((*fs)->format != 4) && ((*fs)->format != 6))
1001
 
    return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, NULL);
1002
 
 
1003
 
  /* read size (HEAD) info */
1004
 
  SVN_ERR(read_number(&(*fs)->min_unpacked_rev,
1005
 
                      svn_dirent_join(path, "db/min-unpacked-rev", pool),
1006
 
                      pool));
1007
 
  return read_number(&(*fs)->max_revision,
1008
 
                     svn_dirent_join(path, "db/current", pool),
1009
 
                     pool);
1010
 
}
1011
 
 
1012
 
/* Utility function that returns true if STRING->DATA matches KEY.
1013
 
 */
1014
 
static svn_boolean_t
1015
 
key_matches(svn_string_t *string, const char *key)
1016
 
{
1017
 
  return strcmp(string->data, key) == 0;
1018
 
}
1019
 
 
1020
 
/* Comparator used for binary search comparing the absolute file offset
1021
 
 * of a noderev to some other offset. DATA is a *noderev_t, KEY is pointer
1022
 
 * to an apr_size_t.
1023
 
 */
1024
 
static int
1025
 
compare_noderev_offsets(const void *data, const void *key)
1026
 
{
1027
 
  apr_ssize_t diff = (*(const noderev_t *const *)data)->original.offset
1028
 
                     - *(const apr_size_t *)key;
1029
 
 
1030
 
  /* sizeof(int) may be < sizeof(ssize_t) */
1031
 
  if (diff < 0)
1032
 
    return -1;
1033
 
  return diff > 0 ? 1 : 0;
1034
 
}
1035
 
 
1036
 
/* Get the revision and offset info from the node ID with FS. Return the
1037
 
 * data as *REVISION_INFO and *OFFSET, respectively.
1038
 
 *
1039
 
 * Note that we assume that the revision_info_t object ID's revision has
1040
 
 * already been created. That can be guaranteed for standard FSFS pack
1041
 
 * files as IDs never point to future revisions.
1042
 
 */
1043
 
static svn_error_t *
1044
 
parse_revnode_pos(revision_info_t **revision_info,
1045
 
                  apr_size_t *offset,
1046
 
                  fs_fs_t *fs,
1047
 
                  svn_string_t *id)
1048
 
{
1049
 
  int revision;
1050
 
  apr_uint64_t temp;
1051
 
 
1052
 
  /* split the ID and verify the format */
1053
 
  const char *revision_pos = strrchr(id->data, 'r');
1054
 
  char *offset_pos = (char *)strchr(id->data, '/');
1055
 
 
1056
 
  if (revision_pos == NULL || offset_pos == NULL)
1057
 
    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1058
 
                             _("Invalid node id '%s'"), id->data);
1059
 
 
1060
 
  /* extract the numbers (temp. modifying the ID)*/
1061
 
  *offset_pos = 0;
1062
 
  SVN_ERR(svn_cstring_atoi(&revision, revision_pos + 1));
1063
 
  SVN_ERR(svn_cstring_strtoui64(&temp, offset_pos + 1, 0, APR_SIZE_MAX, 10));
1064
 
  *offset = (apr_size_t)temp;
1065
 
  *offset_pos = '/';
1066
 
 
1067
 
  /* validate the revision number and return the revision info */
1068
 
  if (revision - fs->start_revision > fs->revisions->nelts)
1069
 
    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1070
 
                             _("Unknown revision %d"), revision);
1071
 
 
1072
 
  *revision_info = APR_ARRAY_IDX(fs->revisions,
1073
 
                                 revision - fs->start_revision,
1074
 
                                 revision_info_t*);
1075
 
 
1076
 
  return SVN_NO_ERROR;
1077
 
}
1078
 
 
1079
 
/* Returns in *RESULT the noderev at OFFSET relative the revision given in
1080
 
 * REVISION_INFO.  If no such noderev has been parsed, yet, error out.
1081
 
 *
1082
 
 * Since we require the noderev to already have been parsed, we can use
1083
 
 * this functions only to access "older", i.e. predecessor noderevs.
1084
 
 */
1085
 
static svn_error_t *
1086
 
find_noderev(noderev_t **result,
1087
 
            revision_info_t *revision_info,
1088
 
            apr_size_t offset)
1089
 
{
1090
 
  int idx = svn_sort__bsearch_lower_bound(&offset,
1091
 
                                          revision_info->node_revs,
1092
 
                                          compare_noderev_offsets);
1093
 
  if ((idx < 0) || (idx >= revision_info->node_revs->nelts))
1094
 
    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1095
 
                             _("No noderev found at offset %ld"),
1096
 
                             (long)offset);
1097
 
 
1098
 
  *result = APR_ARRAY_IDX(revision_info->node_revs, idx, noderev_t *);
1099
 
  if ((*result)->original.offset != offset)
1100
 
    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1101
 
                             _("No noderev found at offset %ld"),
1102
 
                             (long)offset);
1103
 
 
1104
 
  return SVN_NO_ERROR;
1105
 
}
1106
 
 
1107
 
/* In *RESULT, return the noderev given by ID in FS.  The noderev must
1108
 
 * already have been parsed and put into the FS data structures.
1109
 
 */
1110
 
static svn_error_t *
1111
 
parse_pred(noderev_t **result,
1112
 
           fs_fs_t *fs,
1113
 
           svn_string_t *id)
1114
 
{
1115
 
  apr_size_t offset;
1116
 
  revision_info_t *revision_info;
1117
 
 
1118
 
  SVN_ERR(parse_revnode_pos(&revision_info, &offset, fs, id));
1119
 
  SVN_ERR(find_noderev(result, revision_info, offset));
1120
 
 
1121
 
  return SVN_NO_ERROR;
1122
 
}
1123
 
 
1124
 
/* Comparator used for binary search comparing the absolute file offset
1125
 
 * of a representation to some other offset. DATA is a *representation_t,
1126
 
 * KEY is a pointer to an apr_size_t.
1127
 
 */
1128
 
static int
1129
 
compare_representation_offsets(const void *data, const void *key)
1130
 
{
1131
 
  apr_ssize_t diff = (*(const representation_t *const *)data)->original.offset
1132
 
                     - *(const apr_size_t *)key;
1133
 
 
1134
 
  /* sizeof(int) may be < sizeof(ssize_t) */
1135
 
  if (diff < 0)
1136
 
    return -1;
1137
 
  return diff > 0 ? 1 : 0;
1138
 
}
1139
 
 
1140
 
/* Find the revision_info_t object to the given REVISION in FS and return
1141
 
 * it in *REVISION_INFO. For performance reasons, we skip the lookup if
1142
 
 * the info is already provided.
1143
 
 *
1144
 
 * In that revision, look for the representation_t object for offset OFFSET.
1145
 
 * If it already exists, set *idx to its index in *REVISION_INFO's
1146
 
 * representations list and return the representation object. Otherwise,
1147
 
 * set the index to where it must be inserted and return NULL.
1148
 
 */
1149
 
static representation_t *
1150
 
find_representation(int *idx,
1151
 
                    fs_fs_t *fs,
1152
 
                    revision_info_t **revision_info,
1153
 
                    int revision,
1154
 
                    apr_size_t offset)
1155
 
{
1156
 
  revision_info_t *info;
1157
 
  *idx = -1;
1158
 
 
1159
 
  /* first let's find the revision '*/
1160
 
  info = revision_info ? *revision_info : NULL;
1161
 
  if (info == NULL || info->revision != revision)
1162
 
    {
1163
 
      info = APR_ARRAY_IDX(fs->revisions,
1164
 
                           revision - fs->start_revision,
1165
 
                           revision_info_t*);
1166
 
      if (revision_info)
1167
 
        *revision_info = info;
1168
 
    }
1169
 
 
1170
 
  /* not found -> no result */
1171
 
  if (info == NULL)
1172
 
    return NULL;
1173
 
 
1174
 
  assert(revision == info->revision);
1175
 
 
1176
 
  /* look for the representation */
1177
 
  *idx = svn_sort__bsearch_lower_bound(&offset,
1178
 
                                       info->representations,
1179
 
                                       compare_representation_offsets);
1180
 
  if (*idx < info->representations->nelts)
1181
 
    {
1182
 
      /* return the representation, if this is the one we were looking for */
1183
 
      representation_t *result
1184
 
        = APR_ARRAY_IDX(info->representations, *idx, representation_t *);
1185
 
      if (result->original.offset == offset)
1186
 
        return result;
1187
 
    }
1188
 
 
1189
 
  /* not parsed, yet */
1190
 
  return NULL;
1191
 
}
1192
 
 
1193
 
/* Read the representation header in FILE_CONTENT at OFFSET.  Return its
1194
 
 * size in *HEADER_SIZE, set *IS_PLAIN if no deltification was used and
1195
 
 * return the deltification base representation in *REPRESENTATION.  If
1196
 
 * there is none, set it to NULL.  Use FS to it look up.
1197
 
 *
1198
 
 * Use SCRATCH_POOL for temporary allocations.
1199
 
 */
1200
 
static svn_error_t *
1201
 
read_rep_base(representation_t **representation,
1202
 
              apr_size_t *header_size,
1203
 
              svn_boolean_t *is_plain,
1204
 
              fs_fs_t *fs,
1205
 
              svn_stringbuf_t *file_content,
1206
 
              apr_size_t offset,
1207
 
              apr_pool_t *scratch_pool)
1208
 
{
1209
 
  char *str, *last_str;
1210
 
  int idx, revision;
1211
 
  apr_uint64_t temp;
1212
 
 
1213
 
  /* identify representation header (1 line) */
1214
 
  const char *buffer = file_content->data + offset;
1215
 
  const char *line_end = strchr(buffer, '\n');
1216
 
  *header_size = line_end - buffer + 1;
1217
 
 
1218
 
  /* check for PLAIN rep */
1219
 
  if (strncmp(buffer, "PLAIN\n", *header_size) == 0)
1220
 
    {
1221
 
      *is_plain = TRUE;
1222
 
      *representation = NULL;
1223
 
      return SVN_NO_ERROR;
1224
 
    }
1225
 
 
1226
 
  /* check for DELTA against empty rep */
1227
 
  *is_plain = FALSE;
1228
 
  if (strncmp(buffer, "DELTA\n", *header_size) == 0)
1229
 
    {
1230
 
      /* This is a delta against the empty stream. */
1231
 
      *representation = fs->null_base;
1232
 
      return SVN_NO_ERROR;
1233
 
    }
1234
 
 
1235
 
  /* it's delta against some other rep. Duplicate the header info such
1236
 
   * that we may modify it during parsing. */
1237
 
  str = apr_pstrndup(scratch_pool, buffer, line_end - buffer);
1238
 
  last_str = str;
1239
 
 
1240
 
  /* parse it. */
1241
 
  str = svn_cstring_tokenize(" ", &last_str);
1242
 
  str = svn_cstring_tokenize(" ", &last_str);
1243
 
  SVN_ERR(svn_cstring_atoi(&revision, str));
1244
 
 
1245
 
  str = svn_cstring_tokenize(" ", &last_str);
1246
 
  SVN_ERR(svn_cstring_strtoui64(&temp, str, 0, APR_SIZE_MAX, 10));
1247
 
 
1248
 
  /* it should refer to a rep in an earlier revision.  Look it up */
1249
 
  *representation = find_representation(&idx, fs, NULL, revision, (apr_size_t)temp);
1250
 
  return SVN_NO_ERROR;
1251
 
}
1252
 
 
1253
 
/* Parse the representation reference (text: or props:) in VALUE, look
1254
 
 * it up in FS and return it in *REPRESENTATION.  To be able to parse the
1255
 
 * base rep, we pass the FILE_CONTENT as well.
1256
 
 *
1257
 
 * If necessary, allocate the result in POOL; use SCRATCH_POOL for temp.
1258
 
 * allocations.
1259
 
 */
1260
 
static svn_error_t *
1261
 
parse_representation(representation_t **representation,
1262
 
                     fs_fs_t *fs,
1263
 
                     svn_stringbuf_t *file_content,
1264
 
                     svn_string_t *value,
1265
 
                     revision_info_t *revision_info,
1266
 
                     apr_pool_t *pool,
1267
 
                     apr_pool_t *scratch_pool)
1268
 
{
1269
 
  representation_t *result;
1270
 
  int revision;
1271
 
 
1272
 
  apr_uint64_t offset;
1273
 
  apr_uint64_t size;
1274
 
  int idx;
1275
 
 
1276
 
  /* read location (revision, offset) and size */
1277
 
  char *c = (char *)value->data;
1278
 
  SVN_ERR(svn_cstring_atoi(&revision, svn_cstring_tokenize(" ", &c)));
1279
 
  SVN_ERR(svn_cstring_strtoui64(&offset, svn_cstring_tokenize(" ", &c), 0, APR_SIZE_MAX, 10));
1280
 
  SVN_ERR(svn_cstring_strtoui64(&size, svn_cstring_tokenize(" ", &c), 0, APR_SIZE_MAX, 10));
1281
 
 
1282
 
  /* look it up */
1283
 
  result = find_representation(&idx, fs, &revision_info, revision, (apr_size_t)offset);
1284
 
  if (!result)
1285
 
    {
1286
 
      /* not parsed, yet (probably a rep in the same revision).
1287
 
       * Create a new rep object and determine its base rep as well.
1288
 
       */
1289
 
      result = apr_pcalloc(pool, sizeof(*result));
1290
 
      result->revision = revision_info;
1291
 
      result->original.offset = (apr_size_t)offset;
1292
 
      result->original.size = (apr_size_t)size;
1293
 
      SVN_ERR(read_rep_base(&result->delta_base, &result->header_size,
1294
 
                            &result->is_plain, fs, file_content,
1295
 
                            (apr_size_t)offset + revision_info->original.offset,
1296
 
                            scratch_pool));
1297
 
 
1298
 
      svn_sort__array_insert(&result, revision_info->representations, idx);
1299
 
    }
1300
 
 
1301
 
  *representation = result;
1302
 
 
1303
 
  return SVN_NO_ERROR;
1304
 
}
1305
 
 
1306
 
/* Read the delta window contents of all windows in REPRESENTATION in FS.
1307
 
 * Return the data as svn_txdelta_window_t* instances in *WINDOWS.
1308
 
 * Use POOL for allocations.
1309
 
 */
1310
 
static svn_error_t *
1311
 
read_windows(apr_array_header_t **windows,
1312
 
             fs_fs_t *fs,
1313
 
             representation_t *representation,
1314
 
             apr_pool_t *pool)
1315
 
{
1316
 
  svn_string_t *content;
1317
 
  svn_string_t data;
1318
 
  svn_stream_t *stream;
1319
 
  apr_size_t offset = representation->original.offset
1320
 
                    + representation->header_size;
1321
 
  char version;
1322
 
  apr_size_t len = sizeof(version);
1323
 
 
1324
 
  *windows = apr_array_make(pool, 0, sizeof(svn_txdelta_window_t *));
1325
 
 
1326
 
  /* get the whole revision content */
1327
 
  SVN_ERR(get_content(&content, fs, representation->revision->revision, pool));
1328
 
 
1329
 
  /* create a read stream and position it directly after the rep header */
1330
 
  data.data = content->data + offset + 3;
1331
 
  data.len = representation->original.size - 3;
1332
 
  stream = svn_stream_from_string(&data, pool);
1333
 
  SVN_ERR(svn_stream_read(stream, &version, &len));
1334
 
 
1335
 
  /* read the windows from that stream */
1336
 
  while (TRUE)
1337
 
    {
1338
 
      svn_txdelta_window_t *window;
1339
 
      svn_stream_mark_t *mark;
1340
 
      char dummy;
1341
 
 
1342
 
      len = sizeof(dummy);
1343
 
      SVN_ERR(svn_stream_mark(stream, &mark, pool));
1344
 
      SVN_ERR(svn_stream_read(stream, &dummy, &len));
1345
 
      if (len == 0)
1346
 
        break;
1347
 
 
1348
 
      SVN_ERR(svn_stream_seek(stream, mark));
1349
 
      SVN_ERR(svn_txdelta_read_svndiff_window(&window, stream, version, pool));
1350
 
      APR_ARRAY_PUSH(*windows, svn_txdelta_window_t *) = window;
1351
 
    }
1352
 
 
1353
 
  return SVN_NO_ERROR;
1354
 
}
1355
 
 
1356
 
/* Read the content of the PLAIN REPRESENTATION in FS and return it in
1357
 
 * *CONTENT.  Use POOL for allocations.
1358
 
 */
1359
 
static svn_error_t *
1360
 
read_plain(svn_stringbuf_t **content,
1361
 
           fs_fs_t *fs,
1362
 
           representation_t *representation,
1363
 
           apr_pool_t *pool)
1364
 
{
1365
 
  svn_string_t *data;
1366
 
  apr_size_t offset = representation->original.offset
1367
 
                    + representation->header_size;
1368
 
 
1369
 
  SVN_ERR(get_content(&data, fs, representation->revision->revision, pool));
1370
 
 
1371
 
  /* content is stored as fulltext already */
1372
 
  *content = svn_stringbuf_ncreate(data->data + offset,
1373
 
                                   representation->original.size,
1374
 
                                   pool);
1375
 
 
1376
 
  return SVN_NO_ERROR;
1377
 
}
1378
 
 
1379
 
/* Get the undeltified representation that is a result of combining all
1380
 
 * deltas from the current desired REPRESENTATION in FS with its base
1381
 
 * representation.  Store the result in *CONTENT.
1382
 
 * Use POOL for allocations. */
1383
 
static svn_error_t *
1384
 
get_combined_window(svn_stringbuf_t **content,
1385
 
                    fs_fs_t *fs,
1386
 
                    representation_t *representation,
1387
 
                    apr_pool_t *pool)
1388
 
{
1389
 
  int i;
1390
 
  apr_array_header_t *windows;
1391
 
  svn_stringbuf_t *base_content, *result;
1392
 
  const char *source;
1393
 
  apr_pool_t *sub_pool;
1394
 
  apr_pool_t *iter_pool;
1395
 
 
1396
 
  /* special case: no un-deltification necessary */
1397
 
  if (representation->is_plain)
1398
 
    return read_plain(content, fs, representation, pool);
1399
 
 
1400
 
  /* special case: data already in cache */
1401
 
  *content = get_cached_window(fs, representation, pool);
1402
 
  if (*content)
1403
 
    return SVN_NO_ERROR;
1404
 
 
1405
 
  /* read the delta windows for this representation */
1406
 
  sub_pool = svn_pool_create(pool);
1407
 
  iter_pool = svn_pool_create(pool);
1408
 
  SVN_ERR(read_windows(&windows, fs, representation, sub_pool));
1409
 
 
1410
 
  /* fetch the / create a base content */
1411
 
  if (representation->delta_base && representation->delta_base->revision)
1412
 
    SVN_ERR(get_combined_window(&base_content, fs,
1413
 
                                representation->delta_base, sub_pool));
1414
 
  else
1415
 
    base_content = svn_stringbuf_create_empty(sub_pool);
1416
 
 
1417
 
  /* apply deltas */
1418
 
  result = svn_stringbuf_create_empty(pool);
1419
 
  source = base_content->data;
1420
 
 
1421
 
  for (i = 0; i < windows->nelts; ++i)
1422
 
    {
1423
 
      svn_txdelta_window_t *window
1424
 
        = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
1425
 
      svn_stringbuf_t *buf
1426
 
        = svn_stringbuf_create_ensure(window->tview_len, iter_pool);
1427
 
 
1428
 
      buf->len = window->tview_len;
1429
 
      svn_txdelta_apply_instructions(window, window->src_ops ? source : NULL,
1430
 
                                     buf->data, &buf->len);
1431
 
 
1432
 
      svn_stringbuf_appendbytes(result, buf->data, buf->len);
1433
 
      source += window->sview_len;
1434
 
 
1435
 
      svn_pool_clear(iter_pool);
1436
 
    }
1437
 
 
1438
 
  svn_pool_destroy(iter_pool);
1439
 
  svn_pool_destroy(sub_pool);
1440
 
 
1441
 
  /* cache result and return it */
1442
 
  set_cached_window(fs, representation, result);
1443
 
  *content = result;
1444
 
 
1445
 
  return SVN_NO_ERROR;
1446
 
}
1447
 
 
1448
 
/* forward declaration */
1449
 
static svn_error_t *
1450
 
read_noderev(noderev_t **noderev,
1451
 
             fs_fs_t *fs,
1452
 
             svn_stringbuf_t *file_content,
1453
 
             apr_size_t offset,
1454
 
             revision_info_t *revision_info,
1455
 
             apr_pool_t *pool,
1456
 
             apr_pool_t *scratch_pool);
1457
 
 
1458
 
/* Get the noderev at OFFSET in FILE_CONTENT in FS.  The file content must
1459
 
 * pertain to the revision given in REVISION_INFO.  If the data has not
1460
 
 * been read yet, parse it and store it in REVISION_INFO.  Return the result
1461
 
 * in *NODEREV.
1462
 
 *
1463
 
 * Use POOL for allocations and SCRATCH_POOL for temporaries.
1464
 
 */
1465
 
static svn_error_t *
1466
 
get_noderev(noderev_t **noderev,
1467
 
            fs_fs_t *fs,
1468
 
            svn_stringbuf_t *file_content,
1469
 
            apr_size_t offset,
1470
 
            revision_info_t *revision_info,
1471
 
            apr_pool_t *pool,
1472
 
            apr_pool_t *scratch_pool)
1473
 
{
1474
 
  int idx = svn_sort__bsearch_lower_bound(&offset,
1475
 
                                          revision_info->node_revs,
1476
 
                                          compare_noderev_offsets);
1477
 
  if ((idx < 0) || (idx >= revision_info->node_revs->nelts))
1478
 
    SVN_ERR(read_noderev(noderev, fs, file_content, offset, revision_info,
1479
 
                         pool, scratch_pool));
1480
 
  else
1481
 
    {
1482
 
      *noderev = APR_ARRAY_IDX(revision_info->node_revs, idx, noderev_t *);
1483
 
      if ((*noderev)->original.offset != offset)
1484
 
        SVN_ERR(read_noderev(noderev, fs, file_content, offset, revision_info,
1485
 
                             pool, scratch_pool));
1486
 
    }
1487
 
 
1488
 
  return SVN_NO_ERROR;
1489
 
}
1490
 
 
1491
 
/* Read the directory stored in REPRESENTATION in FS into *HASH.  The result
1492
 
 * will be allocated in FS' directory cache and it will be plain key-value
1493
 
 * hash.  Use SCRATCH_POOL for temporary allocations.
1494
 
 */
1495
 
static svn_error_t *
1496
 
read_dir(apr_hash_t **hash,
1497
 
         fs_fs_t *fs,
1498
 
         representation_t *representation,
1499
 
         apr_pool_t *scratch_pool)
1500
 
{
1501
 
  svn_stringbuf_t *text;
1502
 
  apr_pool_t *text_pool;
1503
 
  svn_stream_t *stream;
1504
 
  apr_pool_t *pool;
1505
 
 
1506
 
  /* chances are, we find the info in cache already */
1507
 
  *hash = get_cached_dir(fs, representation);
1508
 
  if (*hash)
1509
 
    return SVN_NO_ERROR;
1510
 
 
1511
 
  /* create the result container */
1512
 
  pool = get_cached_dir_pool(fs);
1513
 
  *hash = svn_hash__make(pool);
1514
 
 
1515
 
  /* if this is a non-empty rep, read it and de-serialize the hash */
1516
 
  if (representation != NULL)
1517
 
    {
1518
 
      text_pool = svn_pool_create(scratch_pool);
1519
 
      SVN_ERR(get_combined_window(&text, fs, representation, text_pool));
1520
 
      stream = svn_stream_from_stringbuf(text, text_pool);
1521
 
      SVN_ERR(svn_hash_read2(*hash, stream, SVN_HASH_TERMINATOR, pool));
1522
 
      svn_pool_destroy(text_pool);
1523
 
    }
1524
 
 
1525
 
  /* cache the result */
1526
 
  set_cached_dir(fs, representation, *hash);
1527
 
 
1528
 
  return SVN_NO_ERROR;
1529
 
}
1530
 
 
1531
 
/* Starting at the directory in REPRESENTATION in FILE_CONTENT, read all
1532
 
 * DAG nodes, directories and representations linked in that tree structure.
1533
 
 * Store them in FS and read them only once.
1534
 
 *
1535
 
 * Use POOL for persistent allocations and SCRATCH_POOL for temporaries.
1536
 
 */
1537
 
static svn_error_t *
1538
 
parse_dir(fs_fs_t *fs,
1539
 
          svn_stringbuf_t *file_content,
1540
 
          representation_t *representation,
1541
 
          apr_pool_t *pool,
1542
 
          apr_pool_t *scratch_pool)
1543
 
{
1544
 
  apr_hash_t *hash;
1545
 
  apr_hash_index_t *hi;
1546
 
  apr_pool_t *iter_pool = svn_pool_create(scratch_pool);
1547
 
  apr_hash_t *base_dir = svn_hash__make(scratch_pool);
1548
 
 
1549
 
  /* special case: empty dir rep */
1550
 
  if (representation == NULL)
1551
 
    return SVN_NO_ERROR;
1552
 
 
1553
 
  /* if we have a previous representation of that dir, hash it by name */
1554
 
  if (representation->delta_base && representation->delta_base->dir)
1555
 
    {
1556
 
      apr_array_header_t *dir = representation->delta_base->dir->entries;
1557
 
      int i;
1558
 
 
1559
 
      for (i = 0; i < dir->nelts; ++i)
1560
 
        {
1561
 
          direntry_t *entry = APR_ARRAY_IDX(dir, i, direntry_t *);
1562
 
          apr_hash_set(base_dir, entry->name, entry->name_len, entry);
1563
 
        }
1564
 
    }
1565
 
 
1566
 
  /* read this directory */
1567
 
  SVN_ERR(read_dir(&hash, fs, representation, scratch_pool));
1568
 
 
1569
 
  /* add it as an array to the representation (entries yet to be filled) */
1570
 
  representation->dir = apr_pcalloc(pool, sizeof(*representation->dir));
1571
 
  representation->dir->entries
1572
 
    = apr_array_make(pool, apr_hash_count(hash), sizeof(direntry_t *));
1573
 
 
1574
 
  /* Translate the string dir entries into real entries.  Reuse existing
1575
 
   * objects as much as possible to keep memory consumption low.
1576
 
   */
1577
 
  for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi))
1578
 
    {
1579
 
      const char *name = svn__apr_hash_index_key(hi);
1580
 
      svn_string_t *str_val = svn__apr_hash_index_val(hi);
1581
 
      apr_size_t offset;
1582
 
      revision_info_t *revision_info;
1583
 
 
1584
 
      /* look for corresponding entry in previous version */
1585
 
      apr_size_t name_len = strlen(name);
1586
 
      direntry_t *entry = base_dir
1587
 
                        ? apr_hash_get(base_dir, name, name_len)
1588
 
                        : NULL;
1589
 
 
1590
 
      /* parse the new target revnode ID (revision, offset) */
1591
 
      SVN_ERR(parse_revnode_pos(&revision_info, &offset, fs, str_val));
1592
 
 
1593
 
      /* if this is a new entry or if the content changed, create a new
1594
 
       * instance for it. */
1595
 
      if (   !entry
1596
 
          || !entry->node->text
1597
 
          || entry->node->text->revision != revision_info
1598
 
          || entry->node->original.offset != offset)
1599
 
        {
1600
 
          /* create & init the new entry. Reuse the name string if possible */
1601
 
          direntry_t *new_entry = apr_pcalloc(pool, sizeof(*entry));
1602
 
          new_entry->name_len = name_len;
1603
 
          if (entry)
1604
 
            new_entry->name = entry->name;
1605
 
          else
1606
 
            new_entry->name = apr_pstrdup(pool, name);
1607
 
 
1608
 
          /* Link it to the content noderev. Recurse. */
1609
 
          entry = new_entry;
1610
 
          SVN_ERR(get_noderev(&entry->node, fs, file_content, offset,
1611
 
                              revision_info, pool, iter_pool));
1612
 
        }
1613
 
 
1614
 
      /* set the directory entry */
1615
 
      APR_ARRAY_PUSH(representation->dir->entries, direntry_t *) = entry;
1616
 
      svn_pool_clear(iter_pool);
1617
 
    }
1618
 
 
1619
 
  svn_pool_destroy(iter_pool);
1620
 
  return SVN_NO_ERROR;
1621
 
}
1622
 
 
1623
 
/* Starting at the noderev at OFFSET in FILE_CONTENT, read all DAG nodes,
1624
 
 * directories and representations linked in that tree structure.  Store
1625
 
 * them in FS and read them only once.  Return the result in *NODEREV.
1626
 
 *
1627
 
 * Use POOL for persistent allocations and SCRATCH_POOL for temporaries.
1628
 
 */
1629
 
static svn_error_t *
1630
 
read_noderev(noderev_t **noderev,
1631
 
             fs_fs_t *fs,
1632
 
             svn_stringbuf_t *file_content,
1633
 
             apr_size_t offset,
1634
 
             revision_info_t *revision_info,
1635
 
             apr_pool_t *pool,
1636
 
             apr_pool_t *scratch_pool)
1637
 
{
1638
 
  noderev_t *result = apr_pcalloc(pool, sizeof(*result));
1639
 
  svn_string_t *line;
1640
 
  svn_boolean_t is_dir = FALSE;
1641
 
 
1642
 
  scratch_pool = svn_pool_create(scratch_pool);
1643
 
 
1644
 
  /* parse the noderev line-by-line until we find an empty line */
1645
 
  result->original.offset = offset;
1646
 
  while (1)
1647
 
    {
1648
 
      /* for this line, extract key and value. Ignore invalid values */
1649
 
      svn_string_t key;
1650
 
      svn_string_t value;
1651
 
      char *sep;
1652
 
      const char *start = file_content->data + offset
1653
 
                        + revision_info->original.offset;
1654
 
      const char *end = strchr(start, '\n');
1655
 
 
1656
 
      line = svn_string_ncreate(start, end - start, scratch_pool);
1657
 
      offset += end - start + 1;
1658
 
 
1659
 
      /* empty line -> end of noderev data */
1660
 
      if (line->len == 0)
1661
 
        break;
1662
 
 
1663
 
      sep = strchr(line->data, ':');
1664
 
      if (sep == NULL)
1665
 
        continue;
1666
 
 
1667
 
      key.data = line->data;
1668
 
      key.len = sep - key.data;
1669
 
      *sep = 0;
1670
 
 
1671
 
      if (key.len + 2 > line->len)
1672
 
        continue;
1673
 
 
1674
 
      value.data = sep + 2;
1675
 
      value.len = line->len - (key.len + 2);
1676
 
 
1677
 
      /* translate (key, value) into noderev elements */
1678
 
      if (key_matches(&key, "type"))
1679
 
        is_dir = strcmp(value.data, "dir") == 0;
1680
 
      else if (key_matches(&key, "pred"))
1681
 
        SVN_ERR(parse_pred(&result->predecessor, fs, &value));
1682
 
      else if (key_matches(&key, "text"))
1683
 
        SVN_ERR(parse_representation(&result->text, fs, file_content,
1684
 
                                     &value, revision_info,
1685
 
                                     pool, scratch_pool));
1686
 
      else if (key_matches(&key, "props"))
1687
 
        SVN_ERR(parse_representation(&result->props, fs, file_content,
1688
 
                                     &value, revision_info,
1689
 
                                     pool, scratch_pool));
1690
 
    }
1691
 
 
1692
 
  /* link noderev to revision info */
1693
 
  result->revision = revision_info;
1694
 
  result->original.size = offset - result->original.offset;
1695
 
 
1696
 
  svn_sort__array_insert(&result,
1697
 
                         revision_info->node_revs,
1698
 
                         svn_sort__bsearch_lower_bound(&offset,
1699
 
                                                       revision_info->node_revs,
1700
 
                                                       compare_noderev_offsets));
1701
 
 
1702
 
  /* if this is a directory, read and process that recursively */
1703
 
  if (is_dir)
1704
 
    SVN_ERR(parse_dir(fs, file_content, result->text,
1705
 
                      pool, scratch_pool));
1706
 
 
1707
 
  /* done */
1708
 
  svn_pool_destroy(scratch_pool);
1709
 
  *noderev = result;
1710
 
 
1711
 
  return SVN_NO_ERROR;
1712
 
}
1713
 
 
1714
 
/* Simple utility to print a REVISION number and make it appear immediately.
1715
 
 */
1716
 
static void
1717
 
print_progress(svn_revnum_t revision)
1718
 
{
1719
 
  printf("%8ld", revision);
1720
 
  fflush(stdout);
1721
 
}
1722
 
 
1723
 
/* Read the content of the pack file staring at revision BASE and store it
1724
 
 * in FS.  Use POOL for allocations.
1725
 
 */
1726
 
static svn_error_t *
1727
 
read_pack_file(fs_fs_t *fs,
1728
 
               svn_revnum_t base,
1729
 
               apr_pool_t *pool)
1730
 
{
1731
 
  apr_array_header_t *manifest = NULL;
1732
 
  apr_pool_t *local_pool = svn_pool_create(pool);
1733
 
  apr_pool_t *iter_pool = svn_pool_create(local_pool);
1734
 
  int i;
1735
 
  svn_stringbuf_t *file_content;
1736
 
  revision_pack_t *revisions;
1737
 
  const char *pack_folder = get_pack_folder(fs, base, local_pool);
1738
 
 
1739
 
  /* read the whole pack file into memory */
1740
 
  SVN_ERR(read_rev_or_pack_file(&file_content, fs, base, local_pool));
1741
 
 
1742
 
  /* create the revision container */
1743
 
  revisions = apr_pcalloc(pool, sizeof(*revisions));
1744
 
  revisions->base = base;
1745
 
  revisions->fragments = NULL;
1746
 
  revisions->info = apr_array_make(pool,
1747
 
                                   fs->max_files_per_dir,
1748
 
                                   sizeof(revision_info_t*));
1749
 
  revisions->filesize = file_content->len;
1750
 
  APR_ARRAY_PUSH(fs->packs, revision_pack_t*) = revisions;
1751
 
 
1752
 
  /* parse the manifest file */
1753
 
  SVN_ERR(read_manifest(&manifest, fs, pack_folder, local_pool));
1754
 
  if (manifest->nelts != fs->max_files_per_dir)
1755
 
    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, NULL);
1756
 
 
1757
 
  /* process each revision in the pack file */
1758
 
  for (i = 0; i < manifest->nelts; ++i)
1759
 
    {
1760
 
      apr_size_t root_node_offset;
1761
 
      svn_string_t rev_content;
1762
 
 
1763
 
      /* create the revision info for the current rev */
1764
 
      revision_info_t *info = apr_pcalloc(pool, sizeof(*info));
1765
 
      info->node_revs = apr_array_make(iter_pool, 4, sizeof(noderev_t*));
1766
 
      info->representations = apr_array_make(iter_pool, 4, sizeof(representation_t*));
1767
 
 
1768
 
      info->revision = base + i;
1769
 
      info->original.offset = APR_ARRAY_IDX(manifest, i, apr_size_t);
1770
 
      info->original.end = i+1 < manifest->nelts
1771
 
                         ? APR_ARRAY_IDX(manifest, i+1 , apr_size_t)
1772
 
                         : file_content->len;
1773
 
      SVN_ERR(read_revision_header(&info->original.changes,
1774
 
                                   &info->original.changes_len,
1775
 
                                   &root_node_offset,
1776
 
                                   file_content,
1777
 
                                   APR_ARRAY_IDX(manifest, i , apr_size_t),
1778
 
                                   info->original.end,
1779
 
                                   iter_pool));
1780
 
 
1781
 
      /* put it into our containers */
1782
 
      APR_ARRAY_PUSH(revisions->info, revision_info_t*) = info;
1783
 
      APR_ARRAY_PUSH(fs->revisions, revision_info_t*) = info;
1784
 
 
1785
 
      /* cache the revision content */
1786
 
      rev_content.data = file_content->data + info->original.offset;
1787
 
      rev_content.len = info->original.end - info->original.offset;
1788
 
      set_cached_content(fs->cache, info->revision, &rev_content);
1789
 
 
1790
 
      /* parse the revision content recursively. */
1791
 
      SVN_ERR(read_noderev(&info->root_noderev, fs, file_content,
1792
 
                           root_node_offset, info, pool, iter_pool));
1793
 
 
1794
 
      /* copy dynamically grown containers from temp into result pool */
1795
 
      info->node_revs = apr_array_copy(pool, info->node_revs);
1796
 
      info->representations = apr_array_copy(pool, info->representations);
1797
 
 
1798
 
      /* destroy temps */
1799
 
      svn_pool_clear(iter_pool);
1800
 
    }
1801
 
 
1802
 
  /* one more pack file processed */
1803
 
  print_progress(base);
1804
 
  svn_pool_destroy(local_pool);
1805
 
 
1806
 
  return SVN_NO_ERROR;
1807
 
}
1808
 
 
1809
 
/* Read the content of REVSION file and store it in FS.
1810
 
 * Use POOL for allocations.
1811
 
 */
1812
 
static svn_error_t *
1813
 
read_revision_file(fs_fs_t *fs,
1814
 
                   svn_revnum_t revision,
1815
 
                   apr_pool_t *pool)
1816
 
{
1817
 
  apr_size_t root_node_offset;
1818
 
  apr_pool_t *local_pool = svn_pool_create(pool);
1819
 
  svn_stringbuf_t *file_content;
1820
 
  svn_string_t rev_content;
1821
 
  revision_pack_t *revisions = apr_pcalloc(pool, sizeof(*revisions));
1822
 
  revision_info_t *info = apr_pcalloc(pool, sizeof(*info));
1823
 
 
1824
 
  /* read the whole pack file into memory */
1825
 
  SVN_ERR(read_rev_or_pack_file(&file_content, fs, revision, local_pool));
1826
 
 
1827
 
  /* create the revision info for the current rev */
1828
 
  info->node_revs = apr_array_make(pool, 4, sizeof(noderev_t*));
1829
 
  info->representations = apr_array_make(pool, 4, sizeof(representation_t*));
1830
 
 
1831
 
  info->revision = revision;
1832
 
  info->original.offset = 0;
1833
 
  info->original.end = file_content->len;
1834
 
  SVN_ERR(read_revision_header(&info->original.changes,
1835
 
                               &info->original.changes_len,
1836
 
                               &root_node_offset,
1837
 
                               file_content,
1838
 
                               0,
1839
 
                               info->original.end,
1840
 
                               local_pool));
1841
 
 
1842
 
  /* put it into our containers */
1843
 
  APR_ARRAY_PUSH(fs->revisions, revision_info_t*) = info;
1844
 
 
1845
 
  /* create a pseudo-pack file container for just this rev to keep our
1846
 
   * data structures as uniform as possible.
1847
 
   */
1848
 
  revisions->base = revision;
1849
 
  revisions->fragments = NULL;
1850
 
  revisions->info = apr_array_make(pool, 1, sizeof(revision_info_t*));
1851
 
  revisions->filesize = file_content->len;
1852
 
  APR_ARRAY_PUSH(revisions->info, revision_info_t*) = info;
1853
 
  APR_ARRAY_PUSH(fs->packs, revision_pack_t*) = revisions;
1854
 
 
1855
 
  /* cache the revision content */
1856
 
  rev_content.data = file_content->data + info->original.offset;
1857
 
  rev_content.len = info->original.end - info->original.offset;
1858
 
  set_cached_content(fs->cache, info->revision, &rev_content);
1859
 
 
1860
 
  /* parse the revision content recursively. */
1861
 
  SVN_ERR(read_noderev(&info->root_noderev, fs, file_content,
1862
 
                       root_node_offset, info,
1863
 
                       pool, local_pool));
1864
 
  APR_ARRAY_PUSH(info->node_revs, noderev_t*) = info->root_noderev;
1865
 
 
1866
 
  /* show progress every 1000 revs or so */
1867
 
  if (revision % fs->max_files_per_dir == 0)
1868
 
    print_progress(revision);
1869
 
 
1870
 
  svn_pool_destroy(local_pool);
1871
 
 
1872
 
  return SVN_NO_ERROR;
1873
 
}
1874
 
 
1875
 
/* Read the repository at PATH beginning with revision START_REVISION and
1876
 
 * return the result in *FS.  Allocate caches with MEMSIZE bytes total
1877
 
 * capacity.  Use POOL for non-cache allocations.
1878
 
 */
1879
 
static svn_error_t *
1880
 
read_revisions(fs_fs_t **fs,
1881
 
               const char *path,
1882
 
               svn_revnum_t start_revision,
1883
 
               apr_size_t memsize,
1884
 
               apr_pool_t *pool)
1885
 
{
1886
 
  svn_revnum_t revision;
1887
 
  apr_size_t content_cache_size;
1888
 
  apr_size_t window_cache_size;
1889
 
  apr_size_t dir_cache_size;
1890
 
 
1891
 
  /* determine cache sizes */
1892
 
  if (memsize < 100)
1893
 
    memsize = 100;
1894
 
 
1895
 
  content_cache_size = memsize * 7 / 10 > 4000 ? 4000 : memsize * 7 / 10;
1896
 
  window_cache_size = memsize * 2 / 10 * 1024 * 1024;
1897
 
  dir_cache_size = (memsize / 10) * 16000;
1898
 
 
1899
 
  /* read repo format and such */
1900
 
  SVN_ERR(fs_open(fs, path, pool));
1901
 
 
1902
 
  /* create data containers and caches */
1903
 
  (*fs)->start_revision = start_revision
1904
 
                        - (start_revision % (*fs)->max_files_per_dir);
1905
 
  (*fs)->revisions = apr_array_make(pool,
1906
 
                                    (*fs)->max_revision + 1 - (*fs)->start_revision,
1907
 
                                    sizeof(revision_info_t *));
1908
 
  (*fs)->packs = apr_array_make(pool,
1909
 
                                ((*fs)->min_unpacked_rev - (*fs)->start_revision)
1910
 
                                   / (*fs)->max_files_per_dir,
1911
 
                                sizeof(revision_pack_t *));
1912
 
  (*fs)->null_base = apr_pcalloc(pool, sizeof(*(*fs)->null_base));
1913
 
  (*fs)->cache = create_content_cache
1914
 
                    (apr_allocator_owner_get
1915
 
                         (svn_pool_create_allocator(FALSE)),
1916
 
                          content_cache_size * 1024 * 1024);
1917
 
  (*fs)->dir_cache = create_dir_cache
1918
 
                    (apr_allocator_owner_get
1919
 
                         (svn_pool_create_allocator(FALSE)),
1920
 
                          dir_cache_size);
1921
 
  (*fs)->window_cache = create_window_cache
1922
 
                    (apr_allocator_owner_get
1923
 
                         (svn_pool_create_allocator(FALSE)),
1924
 
                          10000, window_cache_size);
1925
 
 
1926
 
  /* read all packed revs */
1927
 
  for ( revision = start_revision
1928
 
      ; revision < (*fs)->min_unpacked_rev
1929
 
      ; revision += (*fs)->max_files_per_dir)
1930
 
    SVN_ERR(read_pack_file(*fs, revision, pool));
1931
 
 
1932
 
  /* read non-packed revs */
1933
 
  for ( ; revision <= (*fs)->max_revision; ++revision)
1934
 
    SVN_ERR(read_revision_file(*fs, revision, pool));
1935
 
 
1936
 
  return SVN_NO_ERROR;
1937
 
}
1938
 
 
1939
 
/* Return the maximum number of decimal digits required to represent offsets
1940
 
 * in the given PACK file.
1941
 
 */
1942
 
static apr_size_t
1943
 
get_max_offset_len(const revision_pack_t *pack)
1944
 
{
1945
 
  /* the pack files may grow a few percent.
1946
 
   * Fudge it up to be on safe side.
1947
 
   */
1948
 
  apr_size_t max_future_size = pack->filesize * 2 + 10000;
1949
 
  apr_size_t result = 0;
1950
 
 
1951
 
  while (max_future_size > 0)
1952
 
    {
1953
 
      ++result;
1954
 
      max_future_size /= 10;
1955
 
    }
1956
 
 
1957
 
  return result;
1958
 
}
1959
 
 
1960
 
/* Create the fragments container in PACK and add revision header fragments
1961
 
 * to it.  Use POOL for allocations.
1962
 
 */
1963
 
static svn_error_t *
1964
 
add_revisions_pack_heads(revision_pack_t *pack,
1965
 
                         apr_pool_t *pool)
1966
 
{
1967
 
  int i;
1968
 
  revision_info_t *info;
1969
 
  apr_size_t offset_len = get_max_offset_len(pack);
1970
 
  fragment_t fragment;
1971
 
 
1972
 
  /* allocate fragment arrays */
1973
 
 
1974
 
  int fragment_count = 1;
1975
 
  for (i = 0; i < pack->info->nelts; ++i)
1976
 
    {
1977
 
      info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
1978
 
      fragment_count += info->node_revs->nelts
1979
 
                      + info->representations->nelts
1980
 
                      + 2;
1981
 
    }
1982
 
 
1983
 
  pack->target_offset = pack->info->nelts > 1 ? 64 : 0;
1984
 
  pack->fragments = apr_array_make(pool,
1985
 
                                   fragment_count,
1986
 
                                   sizeof(fragment_t));
1987
 
 
1988
 
  /* put revision headers first */
1989
 
 
1990
 
  for (i = 0; i < pack->info->nelts - 1; ++i)
1991
 
    {
1992
 
      info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
1993
 
      info->target.offset = pack->target_offset;
1994
 
 
1995
 
      fragment.data = info;
1996
 
      fragment.kind = header_fragment;
1997
 
      fragment.position = pack->target_offset;
1998
 
      APR_ARRAY_PUSH(pack->fragments, fragment_t) = fragment;
1999
 
 
2000
 
      pack->target_offset += 2 * offset_len + 3;
2001
 
    }
2002
 
 
2003
 
  info = APR_ARRAY_IDX(pack->info, pack->info->nelts - 1, revision_info_t*);
2004
 
  info->target.offset = pack->target_offset;
2005
 
 
2006
 
  /* followed by the changes list */
2007
 
 
2008
 
  for (i = 0; i < pack->info->nelts; ++i)
2009
 
    {
2010
 
      info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
2011
 
 
2012
 
      info->target.changes = pack->target_offset - info->target.offset;
2013
 
      info->target.changes_len = info->original.changes_len;
2014
 
 
2015
 
      fragment.data = info;
2016
 
      fragment.kind = changes_fragment;
2017
 
      fragment.position = pack->target_offset;
2018
 
      APR_ARRAY_PUSH(pack->fragments, fragment_t) = fragment;
2019
 
 
2020
 
      pack->target_offset += info->original.changes_len;
2021
 
    }
2022
 
 
2023
 
  return SVN_NO_ERROR;
2024
 
}
2025
 
 
2026
 
/* For the revision given by INFO in FS, return the fragment container in
2027
 
 * *FRAGMENTS and the current placement offset in *CURRENT_POS.
2028
 
 */
2029
 
static svn_error_t *
2030
 
get_target_offset(apr_size_t **current_pos,
2031
 
                  apr_array_header_t **fragments,
2032
 
                  fs_fs_t *fs,
2033
 
                  revision_info_t *info)
2034
 
{
2035
 
  int i;
2036
 
  revision_pack_t *pack;
2037
 
  svn_revnum_t revision = info->revision;
2038
 
 
2039
 
  /* identify the pack object */
2040
 
  if (fs->min_unpacked_rev > revision)
2041
 
    {
2042
 
      i = (revision - fs->start_revision) / fs->max_files_per_dir;
2043
 
    }
2044
 
  else
2045
 
    {
2046
 
      i = (fs->min_unpacked_rev - fs->start_revision) / fs->max_files_per_dir;
2047
 
      i += revision - fs->min_unpacked_rev;
2048
 
    }
2049
 
 
2050
 
  /* extract the desired info from it */
2051
 
  pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
2052
 
  *current_pos = &pack->target_offset;
2053
 
  *fragments = pack->fragments;
2054
 
 
2055
 
  return SVN_NO_ERROR;
2056
 
}
2057
 
 
2058
 
/* forward declaration */
2059
 
static svn_error_t *
2060
 
add_noderev_recursively(fs_fs_t *fs,
2061
 
                        noderev_t *node,
2062
 
                        apr_pool_t *pool);
2063
 
 
2064
 
/* Place fragments for the given REPRESENTATION of the given KIND, iff it
2065
 
 * has not been covered, yet.  Place the base reps along the deltification
2066
 
 * chain as far as those reps have not been covered, yet.  If REPRESENTATION
2067
 
 * is a directory, recursively place its elements.
2068
 
 *
2069
 
 * Use POOL for allocations.
2070
 
 */
2071
 
static svn_error_t *
2072
 
add_representation_recursively(fs_fs_t *fs,
2073
 
                               representation_t *representation,
2074
 
                               enum fragment_kind_t kind,
2075
 
                               apr_pool_t *pool)
2076
 
{
2077
 
  apr_size_t *current_pos;
2078
 
  apr_array_header_t *fragments;
2079
 
  fragment_t fragment;
2080
 
 
2081
 
  /* place REPRESENTATION only once and only if it exists and will not
2082
 
   * be covered later as a directory. */
2083
 
  if (   representation == NULL
2084
 
      || representation->covered
2085
 
      || (representation->dir && kind != dir_fragment)
2086
 
      || representation == fs->null_base)
2087
 
    return SVN_NO_ERROR;
2088
 
 
2089
 
  /* add and place a fragment for REPRESENTATION */
2090
 
  SVN_ERR(get_target_offset(&current_pos, &fragments,
2091
 
                            fs, representation->revision));
2092
 
  representation->target.offset = *current_pos;
2093
 
  representation->covered = TRUE;
2094
 
 
2095
 
  fragment.data = representation;
2096
 
  fragment.kind = kind;
2097
 
  fragment.position = *current_pos;
2098
 
  APR_ARRAY_PUSH(fragments, fragment_t) = fragment;
2099
 
 
2100
 
  /* determine the size of data to be added to the target file */
2101
 
  if (   kind != dir_fragment
2102
 
      && representation->delta_base && representation->delta_base->dir)
2103
 
    {
2104
 
      /* base rep is a dir -> would change -> need to store it as fulltext
2105
 
       * in our target file */
2106
 
      apr_pool_t *text_pool = svn_pool_create(pool);
2107
 
      svn_stringbuf_t *content;
2108
 
 
2109
 
      SVN_ERR(get_combined_window(&content, fs, representation, text_pool));
2110
 
      representation->target.size = content->len;
2111
 
      *current_pos += representation->target.size + 13;
2112
 
 
2113
 
      svn_pool_destroy(text_pool);
2114
 
    }
2115
 
  else
2116
 
    if (   kind == dir_fragment
2117
 
        || (representation->delta_base && representation->delta_base->dir))
2118
 
      {
2119
 
        /* deltified directories may grow considerably */
2120
 
        if (representation->original.size < 50)
2121
 
          *current_pos += 300;
2122
 
        else
2123
 
          *current_pos += representation->original.size * 3 + 150;
2124
 
      }
2125
 
    else
2126
 
      {
2127
 
        /* plain / deltified content will not change but the header may
2128
 
         * grow slightly due to larger offsets. */
2129
 
        representation->target.size = representation->original.size;
2130
 
 
2131
 
        if (representation->delta_base &&
2132
 
            (representation->delta_base != fs->null_base))
2133
 
          *current_pos += representation->original.size + 50;
2134
 
        else
2135
 
          *current_pos += representation->original.size + 13;
2136
 
      }
2137
 
 
2138
 
  /* follow the delta chain and place base revs immediately after this */
2139
 
  if (representation->delta_base)
2140
 
    SVN_ERR(add_representation_recursively(fs,
2141
 
                                           representation->delta_base,
2142
 
                                           kind,
2143
 
                                           pool));
2144
 
 
2145
 
  /* finally, recurse into directories */
2146
 
  if (representation->dir)
2147
 
    {
2148
 
      int i;
2149
 
      apr_array_header_t *entries = representation->dir->entries;
2150
 
 
2151
 
      for (i = 0; i < entries->nelts; ++i)
2152
 
        {
2153
 
          direntry_t *entry = APR_ARRAY_IDX(entries, i, direntry_t *);
2154
 
          if (entry->node)
2155
 
            SVN_ERR(add_noderev_recursively(fs, entry->node, pool));
2156
 
        }
2157
 
    }
2158
 
 
2159
 
  return SVN_NO_ERROR;
2160
 
}
2161
 
 
2162
 
/* Place fragments for the given NODE in FS, iff it has not been covered,
2163
 
 * yet.  Place the reps (text, props) immediately after the node.
2164
 
 *
2165
 
 * Use POOL for allocations.
2166
 
 */
2167
 
static svn_error_t *
2168
 
add_noderev_recursively(fs_fs_t *fs,
2169
 
                        noderev_t *node,
2170
 
                        apr_pool_t *pool)
2171
 
{
2172
 
  apr_size_t *current_pos;
2173
 
  apr_array_header_t *fragments;
2174
 
  fragment_t fragment;
2175
 
 
2176
 
  /* don't add it twice */
2177
 
  if (node->covered)
2178
 
    return SVN_NO_ERROR;
2179
 
 
2180
 
  /* add and place a fragment for NODE */
2181
 
  SVN_ERR(get_target_offset(&current_pos, &fragments, fs, node->revision));
2182
 
  node->covered = TRUE;
2183
 
  node->target.offset = *current_pos;
2184
 
 
2185
 
  fragment.data = node;
2186
 
  fragment.kind = noderev_fragment;
2187
 
  fragment.position = *current_pos;
2188
 
  APR_ARRAY_PUSH(fragments, fragment_t) = fragment;
2189
 
 
2190
 
  /* size may slightly increase */
2191
 
  *current_pos += node->original.size + 40;
2192
 
 
2193
 
  /* recurse into representations */
2194
 
  if (node->text && node->text->dir)
2195
 
    SVN_ERR(add_representation_recursively(fs, node->text, dir_fragment, pool));
2196
 
  else
2197
 
    SVN_ERR(add_representation_recursively(fs, node->text, file_fragment, pool));
2198
 
 
2199
 
  SVN_ERR(add_representation_recursively(fs, node->props, property_fragment, pool));
2200
 
 
2201
 
  return SVN_NO_ERROR;
2202
 
}
2203
 
 
2204
 
/* Place a fragment for the last revision in PACK. Use POOL for allocations.
2205
 
 */
2206
 
static svn_error_t *
2207
 
add_revisions_pack_tail(revision_pack_t *pack,
2208
 
                        apr_pool_t *pool)
2209
 
{
2210
 
  int i;
2211
 
  revision_info_t *info;
2212
 
  apr_size_t offset_len = get_max_offset_len(pack);
2213
 
  fragment_t fragment;
2214
 
 
2215
 
  /* put final revision header last and fix up revision lengths */
2216
 
 
2217
 
  info = APR_ARRAY_IDX(pack->info, pack->info->nelts-1, revision_info_t*);
2218
 
 
2219
 
  fragment.data = info;
2220
 
  fragment.kind = header_fragment;
2221
 
  fragment.position = pack->target_offset;
2222
 
  APR_ARRAY_PUSH(pack->fragments, fragment_t) = fragment;
2223
 
 
2224
 
  pack->target_offset += 2 * offset_len + 3;
2225
 
 
2226
 
  /* end of target file reached.  Store that info in all revs. */
2227
 
  for (i = 0; i < pack->info->nelts; ++i)
2228
 
    {
2229
 
      info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
2230
 
      info->target.end = pack->target_offset;
2231
 
    }
2232
 
 
2233
 
  return SVN_NO_ERROR;
2234
 
}
2235
 
 
2236
 
/* Place all fragments for all revisions / packs in FS.
2237
 
 * Use POOL for allocations.
2238
 
 */
2239
 
static svn_error_t *
2240
 
reorder_revisions(fs_fs_t *fs,
2241
 
                  apr_pool_t *pool)
2242
 
{
2243
 
  int i, k;
2244
 
 
2245
 
  /* headers and changes */
2246
 
 
2247
 
  for (i = 0; i < fs->packs->nelts; ++i)
2248
 
    {
2249
 
      revision_pack_t *pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
2250
 
      SVN_ERR(add_revisions_pack_heads(pack, pool));
2251
 
    }
2252
 
 
2253
 
  /* representations & nodes */
2254
 
 
2255
 
  for (i = fs->revisions->nelts-1; i >= 0; --i)
2256
 
    {
2257
 
      revision_info_t *info = APR_ARRAY_IDX(fs->revisions, i, revision_info_t*);
2258
 
      for (k = info->node_revs->nelts - 1; k >= 0; --k)
2259
 
        {
2260
 
          noderev_t *node = APR_ARRAY_IDX(info->node_revs, k, noderev_t*);
2261
 
          SVN_ERR(add_noderev_recursively(fs, node, pool));
2262
 
        }
2263
 
 
2264
 
      if (info->revision % fs->max_files_per_dir == 0)
2265
 
        print_progress(info->revision);
2266
 
    }
2267
 
 
2268
 
  /* pack file tails */
2269
 
 
2270
 
  for (i = 0; i < fs->packs->nelts; ++i)
2271
 
    {
2272
 
      revision_pack_t *pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
2273
 
      SVN_ERR(add_revisions_pack_tail(pack, pool));
2274
 
    }
2275
 
 
2276
 
  return SVN_NO_ERROR;
2277
 
}
2278
 
 
2279
 
/* forward declaration */
2280
 
static svn_error_t *
2281
 
get_fragment_content(svn_string_t **content,
2282
 
                     fs_fs_t *fs,
2283
 
                     fragment_t *fragment,
2284
 
                     apr_pool_t *pool);
2285
 
 
2286
 
/* Directory content may change and with it, the deltified representations
2287
 
 * may significantly.  This function causes all directory target reps in
2288
 
 * PACK of FS to be built and their new MD5 as well as rep sizes be updated.
2289
 
 * We must do that before attempting to write noderevs.
2290
 
 *
2291
 
 * Use POOL for allocations.
2292
 
 */
2293
 
static svn_error_t *
2294
 
update_noderevs(fs_fs_t *fs,
2295
 
                revision_pack_t *pack,
2296
 
                apr_pool_t *pool)
2297
 
{
2298
 
  int i;
2299
 
  apr_pool_t *itempool = svn_pool_create(pool);
2300
 
 
2301
 
  for (i = 0; i < pack->fragments->nelts; ++i)
2302
 
    {
2303
 
      fragment_t *fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
2304
 
      if (fragment->kind == dir_fragment)
2305
 
        {
2306
 
          svn_string_t *content;
2307
 
 
2308
 
          /* request updated rep content but ignore the result.
2309
 
           * We are only interested in the MD5, content and rep size updates. */
2310
 
          SVN_ERR(get_fragment_content(&content, fs, fragment, itempool));
2311
 
          svn_pool_clear(itempool);
2312
 
        }
2313
 
    }
2314
 
 
2315
 
  svn_pool_destroy(itempool);
2316
 
 
2317
 
  return SVN_NO_ERROR;
2318
 
}
2319
 
 
2320
 
/* Determine the target size of the FRAGMENT in FS and return the value
2321
 
 * in *LENGTH.  If ADD_PADDING has been set, slightly fudge the numbers
2322
 
 * to account for changes in offset lengths etc.  Use POOL for temporary
2323
 
 * allocations.
2324
 
 */
2325
 
static svn_error_t *
2326
 
get_content_length(apr_size_t *length,
2327
 
                   fs_fs_t *fs,
2328
 
                   fragment_t *fragment,
2329
 
                   svn_boolean_t add_padding,
2330
 
                   apr_pool_t *pool)
2331
 
{
2332
 
  svn_string_t *content;
2333
 
 
2334
 
  SVN_ERR(get_fragment_content(&content, fs, fragment, pool));
2335
 
  if (add_padding)
2336
 
    switch (fragment->kind)
2337
 
      {
2338
 
        case dir_fragment:
2339
 
          *length = content->len + 16;
2340
 
          break;
2341
 
        case noderev_fragment:
2342
 
          *length = content->len + 3;
2343
 
          break;
2344
 
        default:
2345
 
          *length = content->len;
2346
 
          break;
2347
 
      }
2348
 
  else
2349
 
    *length = content->len;
2350
 
 
2351
 
  return SVN_NO_ERROR;
2352
 
}
2353
 
 
2354
 
/* Move the FRAGMENT to global file offset NEW_POSITION.  Update the target
2355
 
 * location info of the underlying object as well.
2356
 
 */
2357
 
static void
2358
 
move_fragment(fragment_t *fragment,
2359
 
              apr_size_t new_position)
2360
 
{
2361
 
  revision_info_t *info;
2362
 
  representation_t *representation;
2363
 
  noderev_t *node;
2364
 
 
2365
 
  /* move the fragment */
2366
 
  fragment->position = new_position;
2367
 
 
2368
 
  /* move the underlying object */
2369
 
  switch (fragment->kind)
2370
 
    {
2371
 
      case header_fragment:
2372
 
        info = fragment->data;
2373
 
        info->target.offset = new_position;
2374
 
        break;
2375
 
 
2376
 
      case changes_fragment:
2377
 
        info = fragment->data;
2378
 
        info->target.changes = new_position - info->target.offset;
2379
 
        break;
2380
 
 
2381
 
      case property_fragment:
2382
 
      case file_fragment:
2383
 
      case dir_fragment:
2384
 
        representation = fragment->data;
2385
 
        representation->target.offset = new_position;
2386
 
        break;
2387
 
 
2388
 
      case noderev_fragment:
2389
 
        node = fragment->data;
2390
 
        node->target.offset = new_position;
2391
 
        break;
2392
 
    }
2393
 
}
2394
 
 
2395
 
/* Move the fragments in PACK's target fragment list to their final offsets.
2396
 
 * This may require several iterations if the fudge factors turned out to
2397
 
 * be insufficient.  Use POOL for allocations.
2398
 
 */
2399
 
static svn_error_t *
2400
 
pack_revisions(fs_fs_t *fs,
2401
 
               revision_pack_t *pack,
2402
 
               apr_pool_t *pool)
2403
 
{
2404
 
  int i;
2405
 
  fragment_t *fragment, *next;
2406
 
  svn_boolean_t needed_to_expand;
2407
 
  revision_info_t *info;
2408
 
  apr_size_t current_pos, len, old_len;
2409
 
 
2410
 
  apr_pool_t *itempool = svn_pool_create(pool);
2411
 
 
2412
 
  /* update all directory reps. Chances are that most of the target rep
2413
 
   * sizes are now close to accurate. */
2414
 
  SVN_ERR(update_noderevs(fs, pack, pool));
2415
 
 
2416
 
  /* compression phase: pack all fragments tightly with only a very small
2417
 
   * fudge factor.  This should cause offsets to shrink, thus all the
2418
 
   * actual fragment rate should tend to be even smaller afterwards. */
2419
 
  current_pos = pack->info->nelts > 1 ? 64 : 0;
2420
 
  for (i = 0; i + 1 < pack->fragments->nelts; ++i)
2421
 
    {
2422
 
      fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
2423
 
      SVN_ERR(get_content_length(&len, fs, fragment, TRUE, itempool));
2424
 
      move_fragment(fragment, current_pos);
2425
 
      current_pos += len;
2426
 
 
2427
 
      svn_pool_clear(itempool);
2428
 
    }
2429
 
 
2430
 
  /* don't forget the final fragment (last revision's revision header) */
2431
 
  fragment = &APR_ARRAY_IDX(pack->fragments, pack->fragments->nelts-1, fragment_t);
2432
 
  fragment->position = current_pos;
2433
 
 
2434
 
  /* expansion phase: check whether all fragments fit into their allotted
2435
 
   * slots.  Grow them geometrically if they don't fit.  Retry until they
2436
 
   * all do fit.
2437
 
   * Note: there is an upper limit to which fragments can grow.  So, this
2438
 
   * loop will terminate.  Often, no expansion will be necessary at all. */
2439
 
  do
2440
 
    {
2441
 
      needed_to_expand = FALSE;
2442
 
      current_pos = pack->info->nelts > 1 ? 64 : 0;
2443
 
 
2444
 
      for (i = 0; i + 1 < pack->fragments->nelts; ++i)
2445
 
        {
2446
 
          fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
2447
 
          next = &APR_ARRAY_IDX(pack->fragments, i + 1, fragment_t);
2448
 
          old_len = next->position - fragment->position;
2449
 
 
2450
 
          SVN_ERR(get_content_length(&len, fs, fragment, FALSE, itempool));
2451
 
 
2452
 
          if (len > old_len)
2453
 
            {
2454
 
              len = (apr_size_t)(len * 1.1) + 10;
2455
 
              needed_to_expand = TRUE;
2456
 
            }
2457
 
          else
2458
 
            len = old_len;
2459
 
 
2460
 
          if (i == pack->info->nelts - 1)
2461
 
            {
2462
 
              info = APR_ARRAY_IDX(pack->info, pack->info->nelts - 1, revision_info_t*);
2463
 
              info->target.offset = current_pos;
2464
 
            }
2465
 
 
2466
 
          move_fragment(fragment, current_pos);
2467
 
          current_pos += len;
2468
 
 
2469
 
          svn_pool_clear(itempool);
2470
 
        }
2471
 
 
2472
 
      fragment = &APR_ARRAY_IDX(pack->fragments, pack->fragments->nelts-1, fragment_t);
2473
 
      fragment->position = current_pos;
2474
 
 
2475
 
      /* update the revision
2476
 
       * sizes (they all end at the end of the pack file now) */
2477
 
      SVN_ERR(get_content_length(&len, fs, fragment, FALSE, itempool));
2478
 
      current_pos += len;
2479
 
 
2480
 
      for (i = 0; i < pack->info->nelts; ++i)
2481
 
        {
2482
 
          info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
2483
 
          info->target.end = current_pos;
2484
 
        }
2485
 
    }
2486
 
  while (needed_to_expand);
2487
 
 
2488
 
  svn_pool_destroy(itempool);
2489
 
 
2490
 
  return SVN_NO_ERROR;
2491
 
}
2492
 
 
2493
 
/* Write reorg'ed target content for PACK in FS.  Use POOL for allocations.
2494
 
 */
2495
 
static svn_error_t *
2496
 
write_revisions(fs_fs_t *fs,
2497
 
                revision_pack_t *pack,
2498
 
                apr_pool_t *pool)
2499
 
{
2500
 
  int i;
2501
 
  fragment_t *fragment = NULL;
2502
 
  svn_string_t *content;
2503
 
 
2504
 
  apr_pool_t *itempool = svn_pool_create(pool);
2505
 
  apr_pool_t *iterpool = svn_pool_create(pool);
2506
 
 
2507
 
  apr_file_t *file;
2508
 
  apr_size_t current_pos = 0;
2509
 
  svn_stringbuf_t *null_buffer = svn_stringbuf_create_empty(iterpool);
2510
 
 
2511
 
  /* create the target file */
2512
 
  const char *dir = apr_psprintf(iterpool, "%s/new/%ld%s",
2513
 
                                  fs->path, pack->base / fs->max_files_per_dir,
2514
 
                                  pack->info->nelts > 1 ? ".pack" : "");
2515
 
  SVN_ERR(svn_io_make_dir_recursively(dir, pool));
2516
 
  SVN_ERR(svn_io_file_open(&file,
2517
 
                            pack->info->nelts > 1
2518
 
                              ? apr_psprintf(iterpool, "%s/pack", dir)
2519
 
                              : apr_psprintf(iterpool, "%s/%ld", dir, pack->base),
2520
 
                            APR_WRITE | APR_CREATE | APR_BUFFERED,
2521
 
                            APR_OS_DEFAULT,
2522
 
                            iterpool));
2523
 
 
2524
 
  /* write all fragments */
2525
 
  for (i = 0; i < pack->fragments->nelts; ++i)
2526
 
    {
2527
 
      apr_size_t padding;
2528
 
 
2529
 
      /* get fragment content to write */
2530
 
      fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
2531
 
      SVN_ERR(get_fragment_content(&content, fs, fragment, itempool));
2532
 
      SVN_ERR_ASSERT(fragment->position >= current_pos);
2533
 
 
2534
 
      /* number of bytes between this and the previous fragment */
2535
 
      if (   fragment->kind == header_fragment
2536
 
          && i+1 < pack->fragments->nelts)
2537
 
        /* special case: header fragments are aligned to the slot end */
2538
 
        padding = APR_ARRAY_IDX(pack->fragments, i+1, fragment_t).position -
2539
 
                  content->len - current_pos;
2540
 
      else
2541
 
        /* standard case: fragments are aligned to the slot start */
2542
 
        padding = fragment->position - current_pos;
2543
 
 
2544
 
      /* write padding between fragments */
2545
 
      if (padding)
2546
 
        {
2547
 
          while (null_buffer->len < padding)
2548
 
            svn_stringbuf_appendbyte(null_buffer, 0);
2549
 
 
2550
 
          SVN_ERR(svn_io_file_write_full(file,
2551
 
                                         null_buffer->data,
2552
 
                                         padding,
2553
 
                                         NULL,
2554
 
                                         itempool));
2555
 
          current_pos += padding;
2556
 
        }
2557
 
 
2558
 
      /* write fragment content */
2559
 
      SVN_ERR(svn_io_file_write_full(file,
2560
 
                                     content->data,
2561
 
                                     content->len,
2562
 
                                     NULL,
2563
 
                                     itempool));
2564
 
      current_pos += content->len;
2565
 
 
2566
 
      svn_pool_clear(itempool);
2567
 
    }
2568
 
 
2569
 
  apr_file_close(file);
2570
 
 
2571
 
  /* write new manifest file */
2572
 
  if (pack->info->nelts > 1)
2573
 
    {
2574
 
      svn_stream_t *stream;
2575
 
      SVN_ERR(svn_io_file_open(&file,
2576
 
                                apr_psprintf(iterpool, "%s/manifest", dir),
2577
 
                                APR_WRITE | APR_CREATE | APR_BUFFERED,
2578
 
                                APR_OS_DEFAULT,
2579
 
                                iterpool));
2580
 
      stream = svn_stream_from_aprfile2(file, FALSE, iterpool);
2581
 
 
2582
 
      for (i = 0; i < pack->info->nelts; ++i)
2583
 
        {
2584
 
          revision_info_t *info = APR_ARRAY_IDX(pack->info, i,
2585
 
                                                revision_info_t *);
2586
 
          SVN_ERR(svn_stream_printf(stream, itempool,
2587
 
                                    "%" APR_SIZE_T_FMT "\n",
2588
 
                                    info->target.offset));
2589
 
          svn_pool_clear(itempool);
2590
 
        }
2591
 
    }
2592
 
 
2593
 
  /* cleanup */
2594
 
  svn_pool_destroy(itempool);
2595
 
  svn_pool_destroy(iterpool);
2596
 
 
2597
 
  return SVN_NO_ERROR;
2598
 
}
2599
 
 
2600
 
/* Write reorg'ed target content for all revisions in FS.  To maximize
2601
 
 * data locality, pack and write in one go per pack file.
2602
 
 * Use POOL for allocations.
2603
 
 */
2604
 
static svn_error_t *
2605
 
pack_and_write_revisions(fs_fs_t *fs,
2606
 
                         apr_pool_t *pool)
2607
 
{
2608
 
  int i;
2609
 
 
2610
 
  SVN_ERR(svn_io_make_dir_recursively(apr_psprintf(pool, "%s/new",
2611
 
                                                   fs->path),
2612
 
                                      pool));
2613
 
 
2614
 
  for (i = 0; i < fs->packs->nelts; ++i)
2615
 
    {
2616
 
      revision_pack_t *pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
2617
 
      if (pack->base % fs->max_files_per_dir == 0)
2618
 
        print_progress(pack->base);
2619
 
 
2620
 
      SVN_ERR(pack_revisions(fs, pack, pool));
2621
 
      SVN_ERR(write_revisions(fs, pack, pool));
2622
 
    }
2623
 
 
2624
 
  return SVN_NO_ERROR;
2625
 
}
2626
 
 
2627
 
/* For the directory REPRESENTATION in FS, construct the new (target)
2628
 
 * serialized plaintext representation and return it in *CONTENT.
2629
 
 * Allocate the result in POOL and temporaries in SCRATCH_POOL.
2630
 
 */
2631
 
static svn_error_t *
2632
 
get_updated_dir(svn_string_t **content,
2633
 
                fs_fs_t *fs,
2634
 
                representation_t *representation,
2635
 
                apr_pool_t *pool,
2636
 
                apr_pool_t *scratch_pool)
2637
 
{
2638
 
  apr_hash_t *hash;
2639
 
  apr_pool_t *hash_pool = svn_pool_create(scratch_pool);
2640
 
  apr_array_header_t *dir = representation->dir->entries;
2641
 
  int i;
2642
 
  svn_stream_t *stream;
2643
 
  svn_stringbuf_t *result;
2644
 
 
2645
 
  /* get the original content */
2646
 
  SVN_ERR(read_dir(&hash, fs, representation, scratch_pool));
2647
 
  hash = apr_hash_copy(hash_pool, hash);
2648
 
 
2649
 
  /* update all entries */
2650
 
  for (i = 0; i < dir->nelts; ++i)
2651
 
    {
2652
 
      char buffer[256];
2653
 
      svn_string_t *new_val;
2654
 
      apr_size_t pos;
2655
 
 
2656
 
      /* find the original entry for for the current name */
2657
 
      direntry_t *entry = APR_ARRAY_IDX(dir, i, direntry_t *);
2658
 
      svn_string_t *str_val = apr_hash_get(hash, entry->name, entry->name_len);
2659
 
      if (str_val == NULL)
2660
 
        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2661
 
                                 _("Dir entry '%s' not found"), entry->name);
2662
 
 
2663
 
      SVN_ERR_ASSERT(str_val->len < sizeof(buffer));
2664
 
 
2665
 
      /* create and updated node ID */
2666
 
      memcpy(buffer, str_val->data, str_val->len+1);
2667
 
      pos = strchr(buffer, '/') - buffer + 1;
2668
 
      pos += svn__ui64toa(buffer + pos, entry->node->target.offset - entry->node->revision->target.offset);
2669
 
      new_val = svn_string_ncreate(buffer, pos, hash_pool);
2670
 
 
2671
 
      /* store it in the hash */
2672
 
      apr_hash_set(hash, entry->name, entry->name_len, new_val);
2673
 
    }
2674
 
 
2675
 
  /* serialize the updated hash */
2676
 
  result = svn_stringbuf_create_ensure(representation->target.size, pool);
2677
 
  stream = svn_stream_from_stringbuf(result, hash_pool);
2678
 
  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, hash_pool));
2679
 
  svn_pool_destroy(hash_pool);
2680
 
 
2681
 
  /* done */
2682
 
  *content = svn_stringbuf__morph_into_string(result);
2683
 
 
2684
 
  return SVN_NO_ERROR;
2685
 
}
2686
 
 
2687
 
/* Calculate the delta representation for the given CONTENT and BASE.
2688
 
 * Return the rep in *DIFF.  Use POOL for allocations.
2689
 
 */
2690
 
static svn_error_t *
2691
 
diff_stringbufs(svn_stringbuf_t *diff,
2692
 
                svn_string_t *base,
2693
 
                svn_string_t *content,
2694
 
                apr_pool_t *pool)
2695
 
{
2696
 
  svn_txdelta_window_handler_t diff_wh;
2697
 
  void *diff_whb;
2698
 
 
2699
 
  svn_stream_t *stream;
2700
 
  svn_stream_t *source = svn_stream_from_string(base, pool);
2701
 
  svn_stream_t *target = svn_stream_from_stringbuf(diff, pool);
2702
 
 
2703
 
  /* Prepare to write the svndiff data. */
2704
 
  svn_txdelta_to_svndiff3(&diff_wh,
2705
 
                          &diff_whb,
2706
 
                          target,
2707
 
                          1,
2708
 
                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
2709
 
                          pool);
2710
 
 
2711
 
  /* create delta stream */
2712
 
  stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
2713
 
 
2714
 
  /* run delta */
2715
 
  SVN_ERR(svn_stream_write(stream, content->data, &content->len));
2716
 
  SVN_ERR(svn_stream_close(stream));
2717
 
 
2718
 
  return SVN_NO_ERROR;
2719
 
}
2720
 
 
2721
 
/* Update the noderev id value for KEY in the textual noderev representation
2722
 
 * in NODE_REV.  Take the new id from NODE.  This is a no-op if the KEY
2723
 
 * cannot be found.
2724
 
 */
2725
 
static void
2726
 
update_id(svn_stringbuf_t *node_rev,
2727
 
          const char *key,
2728
 
          noderev_t *node)
2729
 
{
2730
 
  char *newline_pos = 0;
2731
 
  char *pos;
2732
 
 
2733
 
  /* we need to update the offset only -> find its position */
2734
 
  pos = strstr(node_rev->data, key);
2735
 
  if (pos)
2736
 
    pos = strchr(pos, '/');
2737
 
  if (pos)
2738
 
    newline_pos = strchr(++pos, '\n');
2739
 
 
2740
 
  if (pos && newline_pos)
2741
 
    {
2742
 
      /* offset data has been found -> replace it */
2743
 
      char temp[SVN_INT64_BUFFER_SIZE];
2744
 
      apr_size_t len = svn__i64toa(temp, node->target.offset - node->revision->target.offset);
2745
 
      svn_stringbuf_replace(node_rev,
2746
 
                            pos - node_rev->data, newline_pos - pos,
2747
 
                            temp, len);
2748
 
    }
2749
 
}
2750
 
 
2751
 
/* Update the representation id value for KEY in the textual noderev
2752
 
 * representation in NODE_REV.  Take the offset, sizes and new MD5 from
2753
 
 * REPRESENTATION.  Use SCRATCH_POOL for allocations.
2754
 
 * This is a no-op if the KEY cannot be found.
2755
 
 */
2756
 
static void
2757
 
update_text(svn_stringbuf_t *node_rev,
2758
 
            const char *key,
2759
 
            representation_t *representation,
2760
 
            apr_pool_t *scratch_pool)
2761
 
{
2762
 
  apr_size_t key_len = strlen(key);
2763
 
  char *pos = strstr(node_rev->data, key);
2764
 
  char *val_pos;
2765
 
 
2766
 
  if (!pos)
2767
 
    return;
2768
 
 
2769
 
  val_pos = pos + key_len;
2770
 
  if (representation->dir)
2771
 
    {
2772
 
      /* for directories, we need to write all rep info anew */
2773
 
      char *newline_pos = strchr(val_pos, '\n');
2774
 
      svn_checksum_t checksum;
2775
 
      const char* temp = apr_psprintf(scratch_pool, "%ld %" APR_SIZE_T_FMT " %"
2776
 
                                      APR_SIZE_T_FMT" %" APR_SIZE_T_FMT " %s",
2777
 
                                      representation->revision->revision,
2778
 
                                      representation->target.offset - representation->revision->target.offset,
2779
 
                                      representation->target.size,
2780
 
                                      representation->dir->size,
2781
 
                                      svn_checksum_to_cstring(&checksum,
2782
 
                                                              scratch_pool));
2783
 
 
2784
 
      checksum.digest = representation->dir->target_md5;
2785
 
      checksum.kind = svn_checksum_md5;
2786
 
      svn_stringbuf_replace(node_rev,
2787
 
                            val_pos - node_rev->data, newline_pos - val_pos,
2788
 
                            temp, strlen(temp));
2789
 
    }
2790
 
  else
2791
 
    {
2792
 
      /* ordinary representation: replace offset and rep size only.
2793
 
       * Content size and checksums are unchanged. */
2794
 
      const char* temp;
2795
 
      char *end_pos = strchr(val_pos, ' ');
2796
 
 
2797
 
      val_pos = end_pos + 1;
2798
 
      end_pos = strchr(strchr(val_pos, ' ') + 1, ' ');
2799
 
      temp = apr_psprintf(scratch_pool, "%" APR_SIZE_T_FMT " %" APR_SIZE_T_FMT,
2800
 
                          representation->target.offset - representation->revision->target.offset,
2801
 
                          representation->target.size);
2802
 
 
2803
 
      svn_stringbuf_replace(node_rev,
2804
 
                            val_pos - node_rev->data, end_pos - val_pos,
2805
 
                            temp, strlen(temp));
2806
 
    }
2807
 
}
2808
 
 
2809
 
/* Get the target content (data block as to be written to the file) for
2810
 
 * the given FRAGMENT in FS.  Return the content in *CONTENT.  Use POOL
2811
 
 * for allocations.
2812
 
 *
2813
 
 * Note that, as a side-effect, this will update the target rep. info for
2814
 
 * directories.
2815
 
 */
2816
 
static svn_error_t *
2817
 
get_fragment_content(svn_string_t **content,
2818
 
                     fs_fs_t *fs,
2819
 
                     fragment_t *fragment,
2820
 
                     apr_pool_t *pool)
2821
 
{
2822
 
  revision_info_t *info;
2823
 
  representation_t *representation;
2824
 
  noderev_t *node;
2825
 
  svn_string_t *revision_content, *base_content;
2826
 
  svn_stringbuf_t *header, *node_rev, *text;
2827
 
  apr_size_t header_size;
2828
 
  svn_checksum_t *checksum = NULL;
2829
 
 
2830
 
  switch (fragment->kind)
2831
 
    {
2832
 
      /* revision headers can be constructed from target position info */
2833
 
      case header_fragment:
2834
 
        info = fragment->data;
2835
 
        *content = svn_string_createf(pool,
2836
 
                                      "\n%" APR_SIZE_T_FMT " %" APR_SIZE_T_FMT "\n",
2837
 
                                      info->root_noderev->target.offset - info->target.offset,
2838
 
                                      info->target.changes);
2839
 
        return SVN_NO_ERROR;
2840
 
 
2841
 
      /* The changes list remains untouched */
2842
 
      case changes_fragment:
2843
 
        info = fragment->data;
2844
 
        SVN_ERR(get_content(&revision_content, fs, info->revision, pool));
2845
 
 
2846
 
        *content = svn_string_create_empty(pool);
2847
 
        (*content)->data = revision_content->data + info->original.changes;
2848
 
        (*content)->len = info->target.changes_len;
2849
 
        return SVN_NO_ERROR;
2850
 
 
2851
 
      /* property and file reps get new headers any need to be rewritten,
2852
 
       * iff the base rep is a directory.  The actual (deltified) content
2853
 
       * remains unchanged, though.  MD5 etc. do not change. */
2854
 
      case property_fragment:
2855
 
      case file_fragment:
2856
 
        representation = fragment->data;
2857
 
        SVN_ERR(get_content(&revision_content, fs,
2858
 
                            representation->revision->revision, pool));
2859
 
 
2860
 
        if (representation->delta_base)
2861
 
          if (representation->delta_base->dir)
2862
 
            {
2863
 
              /* if the base happens to be a directory, reconstruct the
2864
 
               * full text and represent it as PLAIN rep. */
2865
 
              SVN_ERR(get_combined_window(&text, fs, representation, pool));
2866
 
              representation->target.size = text->len;
2867
 
 
2868
 
              svn_stringbuf_insert(text, 0, "PLAIN\n", 6);
2869
 
              svn_stringbuf_appendcstr(text, "ENDREP\n");
2870
 
              *content = svn_stringbuf__morph_into_string(text);
2871
 
 
2872
 
              return SVN_NO_ERROR;
2873
 
            }
2874
 
          else
2875
 
            /* construct a new rep header */
2876
 
            if (representation->delta_base == fs->null_base)
2877
 
              header = svn_stringbuf_create("DELTA\n", pool);
2878
 
            else
2879
 
              header = svn_stringbuf_createf(pool,
2880
 
                                             "DELTA %ld %" APR_SIZE_T_FMT " %" APR_SIZE_T_FMT "\n",
2881
 
                                             representation->delta_base->revision->revision,
2882
 
                                             representation->delta_base->target.offset
2883
 
                                             - representation->delta_base->revision->target.offset,
2884
 
                                             representation->delta_base->target.size);
2885
 
        else
2886
 
          header = svn_stringbuf_create("PLAIN\n", pool);
2887
 
 
2888
 
        /* if it exists, the actual delta base is unchanged. Hence, this
2889
 
         * rep is unchanged even if it has been deltified. */
2890
 
        header_size = strchr(revision_content->data +
2891
 
                             representation->original.offset, '\n') -
2892
 
                      revision_content->data -
2893
 
                      representation->original.offset + 1;
2894
 
        svn_stringbuf_appendbytes(header,
2895
 
                                  revision_content->data +
2896
 
                                  representation->original.offset +
2897
 
                                  header_size,
2898
 
                                  representation->original.size);
2899
 
        svn_stringbuf_appendcstr(header, "ENDREP\n");
2900
 
        *content = svn_stringbuf__morph_into_string(header);
2901
 
        return SVN_NO_ERROR;
2902
 
 
2903
 
      /* directory reps need to be rewritten (and deltified) completely.
2904
 
       * As a side-effect, update the MD5 and target content size. */
2905
 
      case dir_fragment:
2906
 
        /* construct new content and update MD5 */
2907
 
        representation = fragment->data;
2908
 
        SVN_ERR(get_updated_dir(&revision_content, fs, representation,
2909
 
                                pool, pool));
2910
 
        SVN_ERR(svn_checksum(&checksum, svn_checksum_md5,
2911
 
                             revision_content->data, revision_content->len,
2912
 
                             pool));
2913
 
        memcpy(representation->dir->target_md5,
2914
 
               checksum->digest,
2915
 
               sizeof(representation->dir->target_md5));
2916
 
 
2917
 
        /* deltify against the base rep if necessary */
2918
 
        if (representation->delta_base)
2919
 
          {
2920
 
            if (representation->delta_base->dir == NULL)
2921
 
              {
2922
 
                /* dummy or non-dir base rep -> self-compress only */
2923
 
                header = svn_stringbuf_create("DELTA\n", pool);
2924
 
                base_content = svn_string_create_empty(pool);
2925
 
              }
2926
 
            else
2927
 
              {
2928
 
                /* deltify against base rep (which is a directory, too)*/
2929
 
                representation_t *base_rep = representation->delta_base;
2930
 
                header = svn_stringbuf_createf(pool,
2931
 
                                               "DELTA %ld %" APR_SIZE_T_FMT " %" APR_SIZE_T_FMT "\n",
2932
 
                                               base_rep->revision->revision,
2933
 
                                               base_rep->target.offset - base_rep->revision->target.offset,
2934
 
                                               base_rep->target.size);
2935
 
                SVN_ERR(get_updated_dir(&base_content, fs, base_rep,
2936
 
                                        pool, pool));
2937
 
              }
2938
 
 
2939
 
            /* run deltification and update target content size */
2940
 
            header_size = header->len;
2941
 
            SVN_ERR(diff_stringbufs(header, base_content,
2942
 
                                    revision_content, pool));
2943
 
            representation->dir->size = revision_content->len;
2944
 
            representation->target.size = header->len - header_size;
2945
 
            svn_stringbuf_appendcstr(header, "ENDREP\n");
2946
 
            *content = svn_stringbuf__morph_into_string(header);
2947
 
          }
2948
 
        else
2949
 
          {
2950
 
            /* no delta base (not even a dummy) -> PLAIN rep */
2951
 
            representation->target.size = revision_content->len;
2952
 
            representation->dir->size = revision_content->len;
2953
 
            *content = svn_string_createf(pool, "PLAIN\n%sENDREP\n",
2954
 
                                          revision_content->data);
2955
 
          }
2956
 
 
2957
 
        return SVN_NO_ERROR;
2958
 
 
2959
 
      /* construct the new noderev content.  No side-effects.*/
2960
 
      case noderev_fragment:
2961
 
        /* get the original noderev as string */
2962
 
        node = fragment->data;
2963
 
        SVN_ERR(get_content(&revision_content, fs,
2964
 
                            node->revision->revision, pool));
2965
 
        node_rev = svn_stringbuf_ncreate(revision_content->data +
2966
 
                                         node->original.offset,
2967
 
                                         node->original.size,
2968
 
                                         pool);
2969
 
 
2970
 
        /* update the values that may have hanged for target */
2971
 
        update_id(node_rev, "id: ", node);
2972
 
        update_id(node_rev, "pred: ", node->predecessor);
2973
 
        update_text(node_rev, "text: ", node->text, pool);
2974
 
        update_text(node_rev, "props: ", node->props, pool);
2975
 
 
2976
 
        *content = svn_stringbuf__morph_into_string(node_rev);
2977
 
        return SVN_NO_ERROR;
2978
 
    }
2979
 
 
2980
 
  SVN_ERR_ASSERT(0);
2981
 
 
2982
 
  return SVN_NO_ERROR;
2983
 
}
2984
 
 
2985
 
/* In the repository at PATH, restore the original content in case we ran
2986
 
 * this reorg tool before.  Use POOL for allocations.
2987
 
 */
2988
 
static svn_error_t *
2989
 
prepare_repo(const char *path, apr_pool_t *pool)
2990
 
{
2991
 
  svn_node_kind_t kind;
2992
 
 
2993
 
  const char *old_path = svn_dirent_join(path, "db/old", pool);
2994
 
  const char *new_path = svn_dirent_join(path, "new", pool);
2995
 
  const char *revs_path = svn_dirent_join(path, "db/revs", pool);
2996
 
  const char *old_rep_cache_path = svn_dirent_join(path, "db/rep-cache.db.old", pool);
2997
 
  const char *rep_cache_path = svn_dirent_join(path, "db/rep-cache.db", pool);
2998
 
 
2999
 
  /* is there a backup? */
3000
 
  SVN_ERR(svn_io_check_path(old_path, &kind, pool));
3001
 
  if (kind == svn_node_dir)
3002
 
    {
3003
 
      /* yes, restore the org content from it */
3004
 
      SVN_ERR(svn_io_remove_dir2(new_path, TRUE, NULL, NULL, pool));
3005
 
      SVN_ERR(svn_io_file_move(revs_path, new_path, pool));
3006
 
      SVN_ERR(svn_io_file_move(old_path, revs_path, pool));
3007
 
      SVN_ERR(svn_io_remove_dir2(new_path, TRUE, NULL, NULL, pool));
3008
 
    }
3009
 
 
3010
 
  /* same for the rep cache db */
3011
 
  SVN_ERR(svn_io_check_path(old_rep_cache_path, &kind, pool));
3012
 
  if (kind == svn_node_file)
3013
 
    SVN_ERR(svn_io_file_move(old_rep_cache_path, rep_cache_path, pool));
3014
 
 
3015
 
  return SVN_NO_ERROR;
3016
 
}
3017
 
 
3018
 
/* In the repository at PATH, create a backup of the orig content and
3019
 
 * replace it with the reorg'ed. Use POOL for allocations.
3020
 
 */
3021
 
static svn_error_t *
3022
 
activate_new_revs(const char *path, apr_pool_t *pool)
3023
 
{
3024
 
  svn_node_kind_t kind;
3025
 
 
3026
 
  const char *old_path = svn_dirent_join(path, "db/old", pool);
3027
 
  const char *new_path = svn_dirent_join(path, "new", pool);
3028
 
  const char *revs_path = svn_dirent_join(path, "db/revs", pool);
3029
 
  const char *old_rep_cache_path = svn_dirent_join(path, "db/rep-cache.db.old", pool);
3030
 
  const char *rep_cache_path = svn_dirent_join(path, "db/rep-cache.db", pool);
3031
 
 
3032
 
  /* if there is no backup, yet, move the current repo content to the backup
3033
 
   * and place it with the new (reorg'ed) data. */
3034
 
  SVN_ERR(svn_io_check_path(old_path, &kind, pool));
3035
 
  if (kind == svn_node_none)
3036
 
    {
3037
 
      SVN_ERR(svn_io_file_move(revs_path, old_path, pool));
3038
 
      SVN_ERR(svn_io_file_move(new_path, revs_path, pool));
3039
 
    }
3040
 
 
3041
 
  /* same for the rep cache db */
3042
 
  SVN_ERR(svn_io_check_path(old_rep_cache_path, &kind, pool));
3043
 
  if (kind == svn_node_none)
3044
 
    SVN_ERR(svn_io_file_move(rep_cache_path, old_rep_cache_path, pool));
3045
 
 
3046
 
  return SVN_NO_ERROR;
3047
 
}
3048
 
 
3049
 
/* Write tool usage info text to OSTREAM using PROGNAME as a prefix and
3050
 
 * POOL for allocations.
3051
 
 */
3052
 
static void
3053
 
print_usage(svn_stream_t *ostream, const char *progname,
3054
 
            apr_pool_t *pool)
3055
 
{
3056
 
  svn_error_clear(svn_stream_printf(ostream, pool,
3057
 
     "\n"
3058
 
     "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
3059
 
     "!!! This is an experimental tool. Don't use it on production data !!!\n"
3060
 
     "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
3061
 
     "\n"
3062
 
     "Usage: %s <repo> <cachesize>\n"
3063
 
     "\n"
3064
 
     "Optimize the repository at local path <repo> staring from revision 0.\n"
3065
 
     "Use up to <cachesize> MB of memory for caching. This does not include\n"
3066
 
     "temporary representation of the repository structure, i.e. the actual\n"
3067
 
     "memory will be higher and <cachesize> be the lower limit.\n",
3068
 
     progname));
3069
 
}
3070
 
 
3071
 
/* linear control flow */
3072
 
int main(int argc, const char *argv[])
3073
 
{
3074
 
  apr_pool_t *pool;
3075
 
  svn_stream_t *ostream;
3076
 
  svn_error_t *svn_err;
3077
 
  const char *repo_path = NULL;
3078
 
  svn_revnum_t start_revision = 0;
3079
 
  apr_size_t memsize = 0;
3080
 
  apr_uint64_t temp = 0;
3081
 
  fs_fs_t *fs;
3082
 
 
3083
 
  apr_initialize();
3084
 
  atexit(apr_terminate);
3085
 
 
3086
 
  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
3087
 
 
3088
 
  svn_err = svn_stream_for_stdout(&ostream, pool);
3089
 
  if (svn_err)
3090
 
    {
3091
 
      svn_handle_error2(svn_err, stdout, FALSE, ERROR_TAG);
3092
 
      return 2;
3093
 
    }
3094
 
 
3095
 
  if (argc != 3)
3096
 
    {
3097
 
      print_usage(ostream, argv[0], pool);
3098
 
      return 2;
3099
 
    }
3100
 
 
3101
 
  svn_err = svn_cstring_strtoui64(&temp, argv[2], 0, APR_SIZE_MAX, 10);
3102
 
  if (svn_err)
3103
 
    {
3104
 
      print_usage(ostream, argv[0], pool);
3105
 
      svn_error_clear(svn_err);
3106
 
      return 2;
3107
 
    }
3108
 
 
3109
 
  memsize = (apr_size_t)temp;
3110
 
  repo_path = argv[1];
3111
 
  start_revision = 0;
3112
 
 
3113
 
  printf("\nPreparing repository\n");
3114
 
  svn_err = prepare_repo(repo_path, pool);
3115
 
 
3116
 
  if (!svn_err)
3117
 
    {
3118
 
      printf("Reading revisions\n");
3119
 
      svn_err = read_revisions(&fs, repo_path, start_revision, memsize, pool);
3120
 
    }
3121
 
 
3122
 
  if (!svn_err)
3123
 
    {
3124
 
      printf("\nReordering revision content\n");
3125
 
      svn_err = reorder_revisions(fs, pool);
3126
 
    }
3127
 
 
3128
 
  if (!svn_err)
3129
 
    {
3130
 
      printf("\nPacking and writing revisions\n");
3131
 
      svn_err = pack_and_write_revisions(fs, pool);
3132
 
    }
3133
 
 
3134
 
  if (!svn_err)
3135
 
    {
3136
 
      printf("\nSwitch to new revs\n");
3137
 
      svn_err = activate_new_revs(repo_path, pool);
3138
 
    }
3139
 
 
3140
 
  if (svn_err)
3141
 
    {
3142
 
      svn_handle_error2(svn_err, stdout, FALSE, ERROR_TAG);
3143
 
      return 2;
3144
 
    }
3145
 
 
3146
 
  return 0;
3147
 
}