1 /* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
2 *
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 * ====================================================================
21 */
22
23
24 #include <stdarg.h>
25
26 #include "svn_private_config.h"
27 #include "svn_pools.h"
28 #include "svn_error.h"
29 #include "svn_fs.h"
30 #include "svn_hash.h"
31 #include "svn_iter.h"
32 #include "svn_repos.h"
33 #include "svn_string.h"
34 #include "svn_dirent_uri.h"
35 #include "svn_path.h"
36 #include "svn_time.h"
37 #include "svn_checksum.h"
38 #include "svn_props.h"
39 #include "svn_sorts.h"
40
41 #include "private/svn_repos_private.h"
42 #include "private/svn_mergeinfo_private.h"
43 #include "private/svn_fs_private.h"
44 #include "private/svn_sorts_private.h"
45 #include "private/svn_utf_private.h"
46 #include "private/svn_cache.h"
47
48 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
49
50 /*----------------------------------------------------------------------*/
51
52
53 /* To be able to check whether a path exists in the current revision
54 (as changes come in), we need to track the relevant tree changes.
55
56 In particular, we remember deletions, additions and copies including
57 their copy-from info. Since the dump performs a pre-order tree walk,
58 we only need to store the data for the stack of parent folders.
59
60 The problem that we are trying to solve is that the dump receives
61 transforming operations whose validity depends on previous operations
62 in the same revision but cannot be checked against the final state
63 as stored in the repository as that is the state *after* we applied
64 the respective tree changes.
65
66 Note that the tracker functions don't perform any sanity or validity
67 checks. Those higher-level tests have to be done in the calling code.
68 However, there is no way to corrupt the data structure using the
69 provided functions.
70 */
71
72 /* Single entry in the path tracker. Not all levels along the path
73 hierarchy do need to have an instance of this struct but only those
74 that got changed by a tree modification.
75
76 Please note that the path info in this struct is stored in re-usable
77 stringbuf objects such that we don't need to allocate more memory than
78 the longest path we encounter.
79 */
80 typedef struct path_tracker_entry_t
81 {
82 /* path in the current tree */
83 svn_stringbuf_t *path;
84
85 /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */
86 svn_stringbuf_t *copyfrom_path;
87
88 /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements
89 that don't copy history, i.e. with no sub-tree) */
90 svn_revnum_t copyfrom_rev;
91
92 /* if FALSE, PATH has been deleted */
93 svn_boolean_t exists;
94 } path_tracker_entry_t;
95
96 /* Tracks all tree modifications above the current path.
97 */
98 typedef struct path_tracker_t
99 {
100 /* Container for all relevant tree changes in depth order.
101 May contain more entries than DEPTH to allow for reusing memory.
102 Only entries 0 .. DEPTH-1 are valid.
103 */
104 apr_array_header_t *stack;
105
106 /* Number of relevant entries in STACK. May be 0 */
107 int depth;
108
109 /* Revision that we current track. If DEPTH is 0, paths are exist in
110 REVISION exactly when they exist in REVISION-1. This applies only
111 to the current state of our tree walk.
112 */
113 svn_revnum_t revision;
114
115 /* Allocate container entries here. */
116 apr_pool_t *pool;
117 } path_tracker_t;
118
119 /* Return a new path tracker object for REVISION, allocated in POOL.
120 */
121 static path_tracker_t *
tracker_create(svn_revnum_t revision,apr_pool_t * pool)122 tracker_create(svn_revnum_t revision,
123 apr_pool_t *pool)
124 {
125 path_tracker_t *result = apr_pcalloc(pool, sizeof(*result));
126 result->stack = apr_array_make(pool, 16, sizeof(path_tracker_entry_t));
127 result->revision = revision;
128 result->pool = pool;
129
130 return result;
131 }
132
133 /* Remove all entries from TRACKER that are not relevant to PATH anymore.
134 * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to
135 * parent folders but not to PATH itself.
136 *
137 * This internal function implicitly updates the tracker state during the
138 * tree by removing "past" entries. Other functions will add entries when
139 * we encounter a new tree change.
140 */
141 static void
tracker_trim(path_tracker_t * tracker,const char * path,svn_boolean_t allow_exact_match)142 tracker_trim(path_tracker_t *tracker,
143 const char *path,
144 svn_boolean_t allow_exact_match)
145 {
146 /* remove everything that is unrelated to PATH.
147 Note that TRACKER->STACK is depth-ordered,
148 i.e. stack[N] is a (maybe indirect) parent of stack[N+1]
149 for N+1 < DEPTH.
150 */
151 for (; tracker->depth; --tracker->depth)
152 {
153 path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
154 tracker->depth - 1,
155 path_tracker_entry_t);
156 const char *rel_path
157 = svn_dirent_skip_ancestor(parent->path->data, path);
158
159 /* always keep parents. Keep exact matches when allowed. */
160 if (rel_path && (allow_exact_match || *rel_path != '\0'))
161 break;
162 }
163 }
164
165 /* Using TRACKER, check what path at what revision in the repository must
166 be checked to decide that whether PATH exists. Return the info in
167 *ORIG_PATH and *ORIG_REV, respectively.
168
169 If the path is known to not exist, *ORIG_PATH will be NULL and *ORIG_REV
170 will be SVN_INVALID_REVNUM. If *ORIG_REV is SVN_INVALID_REVNUM, PATH
171 has just been added in the revision currently being tracked.
172
173 Use POOL for allocations. Note that *ORIG_PATH may be allocated in POOL,
174 a reference to internal data with the same lifetime as TRACKER or just
175 PATH.
176 */
177 static void
tracker_lookup(const char ** orig_path,svn_revnum_t * orig_rev,path_tracker_t * tracker,const char * path,apr_pool_t * pool)178 tracker_lookup(const char **orig_path,
179 svn_revnum_t *orig_rev,
180 path_tracker_t *tracker,
181 const char *path,
182 apr_pool_t *pool)
183 {
184 tracker_trim(tracker, path, TRUE);
185 if (tracker->depth == 0)
186 {
187 /* no tree changes -> paths are the same as in the previous rev. */
188 *orig_path = path;
189 *orig_rev = tracker->revision - 1;
190 }
191 else
192 {
193 path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
194 tracker->depth - 1,
195 path_tracker_entry_t);
196 if (parent->exists)
197 {
198 const char *rel_path
199 = svn_dirent_skip_ancestor(parent->path->data, path);
200
201 if (parent->copyfrom_rev != SVN_INVALID_REVNUM)
202 {
203 /* parent is a copy with history. Translate path. */
204 *orig_path = svn_dirent_join(parent->copyfrom_path->data,
205 rel_path, pool);
206 *orig_rev = parent->copyfrom_rev;
207 }
208 else if (*rel_path == '\0')
209 {
210 /* added in this revision with no history */
211 *orig_path = path;
212 *orig_rev = tracker->revision;
213 }
214 else
215 {
216 /* parent got added but not this path */
217 *orig_path = NULL;
218 *orig_rev = SVN_INVALID_REVNUM;
219 }
220 }
221 else
222 {
223 /* (maybe parent) path has been deleted */
224 *orig_path = NULL;
225 *orig_rev = SVN_INVALID_REVNUM;
226 }
227 }
228 }
229
230 /* Return a reference to the stack entry in TRACKER for PATH. If no
231 suitable entry exists, add one. Implicitly updates the tracked tree
232 location.
233
234 Only the PATH member of the result is being updated. All other members
235 will have undefined values.
236 */
237 static path_tracker_entry_t *
tracker_add_entry(path_tracker_t * tracker,const char * path)238 tracker_add_entry(path_tracker_t *tracker,
239 const char *path)
240 {
241 path_tracker_entry_t *entry;
242 tracker_trim(tracker, path, FALSE);
243
244 if (tracker->depth == tracker->stack->nelts)
245 {
246 entry = apr_array_push(tracker->stack);
247 entry->path = svn_stringbuf_create_empty(tracker->pool);
248 entry->copyfrom_path = svn_stringbuf_create_empty(tracker->pool);
249 }
250 else
251 {
252 entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth,
253 path_tracker_entry_t);
254 }
255
256 svn_stringbuf_set(entry->path, path);
257 ++tracker->depth;
258
259 return entry;
260 }
261
262 /* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to
263 PATH in the tracked revision.
264 */
265 static void
tracker_path_copy(path_tracker_t * tracker,const char * path,const char * copyfrom_path,svn_revnum_t copyfrom_rev)266 tracker_path_copy(path_tracker_t *tracker,
267 const char *path,
268 const char *copyfrom_path,
269 svn_revnum_t copyfrom_rev)
270 {
271 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
272
273 svn_stringbuf_set(entry->copyfrom_path, copyfrom_path);
274 entry->copyfrom_rev = copyfrom_rev;
275 entry->exists = TRUE;
276 }
277
278 /* Update the TRACKER with a plain addition of PATH (without history).
279 */
280 static void
tracker_path_add(path_tracker_t * tracker,const char * path)281 tracker_path_add(path_tracker_t *tracker,
282 const char *path)
283 {
284 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
285
286 svn_stringbuf_setempty(entry->copyfrom_path);
287 entry->copyfrom_rev = SVN_INVALID_REVNUM;
288 entry->exists = TRUE;
289 }
290
291 /* Update the TRACKER with a replacement of PATH with a plain addition
292 (without history).
293 */
294 static void
tracker_path_replace(path_tracker_t * tracker,const char * path)295 tracker_path_replace(path_tracker_t *tracker,
296 const char *path)
297 {
298 /* this will implicitly purge all previous sub-tree info from STACK.
299 Thus, no need to tack the deletion explicitly. */
300 tracker_path_add(tracker, path);
301 }
302
303 /* Update the TRACKER with a deletion of PATH.
304 */
305 static void
tracker_path_delete(path_tracker_t * tracker,const char * path)306 tracker_path_delete(path_tracker_t *tracker,
307 const char *path)
308 {
309 path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
310
311 svn_stringbuf_setempty(entry->copyfrom_path);
312 entry->copyfrom_rev = SVN_INVALID_REVNUM;
313 entry->exists = FALSE;
314 }
315
316
317 /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
318 store it into a new temporary file *TEMPFILE. OLDROOT may be NULL,
319 in which case the delta will be computed against an empty file, as
320 per the svn_fs_get_file_delta_stream docstring. Record the length
321 of the temporary file in *LEN, and rewind the file before
322 returning. */
323 static svn_error_t *
store_delta(apr_file_t ** tempfile,svn_filesize_t * len,svn_fs_root_t * oldroot,const char * oldpath,svn_fs_root_t * newroot,const char * newpath,apr_pool_t * pool)324 store_delta(apr_file_t **tempfile, svn_filesize_t *len,
325 svn_fs_root_t *oldroot, const char *oldpath,
326 svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
327 {
328 svn_stream_t *temp_stream;
329 apr_off_t offset = 0;
330 svn_txdelta_stream_t *delta_stream;
331 svn_txdelta_window_handler_t wh;
332 void *whb;
333
334 /* Create a temporary file and open a stream to it. Note that we need
335 the file handle in order to rewind it. */
336 SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
337 svn_io_file_del_on_pool_cleanup,
338 pool, pool));
339 temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
340
341 /* Compute the delta and send it to the temporary file. */
342 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
343 newroot, newpath, pool));
344 svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
345 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
346 SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
347
348 /* Get the length of the temporary file and rewind it. */
349 SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
350 *len = offset;
351 offset = 0;
352 return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
353 }
354
355
356 /* Send a notification of type #svn_repos_notify_warning, subtype WARNING,
357 with message WARNING_FMT formatted with the remaining variable arguments.
358 Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON.
359 */
360 __attribute__((format(printf, 5, 6)))
361 static void
notify_warning(apr_pool_t * scratch_pool,svn_repos_notify_func_t notify_func,void * notify_baton,svn_repos_notify_warning_t warning,const char * warning_fmt,...)362 notify_warning(apr_pool_t *scratch_pool,
363 svn_repos_notify_func_t notify_func,
364 void *notify_baton,
365 svn_repos_notify_warning_t warning,
366 const char *warning_fmt,
367 ...)
368 {
369 va_list va;
370 svn_repos_notify_t *notify;
371
372 if (notify_func == NULL)
373 return;
374
375 notify = svn_repos_notify_create(svn_repos_notify_warning, scratch_pool);
376 notify->warning = warning;
377 va_start(va, warning_fmt);
378 notify->warning_str = apr_pvsprintf(scratch_pool, warning_fmt, va);
379 va_end(va);
380
381 notify_func(notify_baton, notify, scratch_pool);
382 }
383
384
385 /*----------------------------------------------------------------------*/
386
387 /* Write to STREAM the header in HEADERS named KEY, if present.
388 */
389 static svn_error_t *
write_header(svn_stream_t * stream,apr_hash_t * headers,const char * key,apr_pool_t * scratch_pool)390 write_header(svn_stream_t *stream,
391 apr_hash_t *headers,
392 const char *key,
393 apr_pool_t *scratch_pool)
394 {
395 const char *val = svn_hash_gets(headers, key);
396
397 if (val)
398 {
399 SVN_ERR(svn_stream_printf(stream, scratch_pool,
400 "%s: %s\n", key, val));
401 }
402 return SVN_NO_ERROR;
403 }
404
405 /* Write headers, in arbitrary order.
406 * ### TODO: use a stable order
407 * ### Modifies HEADERS.
408 */
409 static svn_error_t *
write_revision_headers(svn_stream_t * stream,apr_hash_t * headers,apr_pool_t * scratch_pool)410 write_revision_headers(svn_stream_t *stream,
411 apr_hash_t *headers,
412 apr_pool_t *scratch_pool)
413 {
414 const char **h;
415 apr_hash_index_t *hi;
416
417 static const char *revision_headers_order[] =
418 {
419 SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */
420 NULL
421 };
422
423 /* Write some headers in a given order */
424 for (h = revision_headers_order; *h; h++)
425 {
426 SVN_ERR(write_header(stream, headers, *h, scratch_pool));
427 svn_hash_sets(headers, *h, NULL);
428 }
429
430 /* Write any and all remaining headers except Content-length.
431 * ### TODO: use a stable order
432 */
433 for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi))
434 {
435 const char *key = apr_hash_this_key(hi);
436
437 if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0)
438 SVN_ERR(write_header(stream, headers, key, scratch_pool));
439 }
440
441 /* Content-length must be last */
442 SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
443 scratch_pool));
444
445 return SVN_NO_ERROR;
446 }
447
448 /* A header entry: the element type of the apr_array_header_t which is
449 * the real type of svn_repos__dumpfile_headers_t.
450 */
451 typedef struct svn_repos__dumpfile_header_entry_t {
452 const char *key, *val;
453 } svn_repos__dumpfile_header_entry_t;
454
455 svn_repos__dumpfile_headers_t *
svn_repos__dumpfile_headers_create(apr_pool_t * pool)456 svn_repos__dumpfile_headers_create(apr_pool_t *pool)
457 {
458 svn_repos__dumpfile_headers_t *headers
459 = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t));
460
461 return headers;
462 }
463
464 void
svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t * headers,const char * key,const char * val)465 svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers,
466 const char *key,
467 const char *val)
468 {
469 svn_repos__dumpfile_header_entry_t *h
470 = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
471
472 h->key = apr_pstrdup(headers->pool, key);
473 h->val = apr_pstrdup(headers->pool, val);
474 }
475
476 void
svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t * headers,const char * key,const char * val_fmt,...)477 svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers,
478 const char *key,
479 const char *val_fmt,
480 ...)
481 {
482 va_list ap;
483 svn_repos__dumpfile_header_entry_t *h
484 = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
485
486 h->key = apr_pstrdup(headers->pool, key);
487 va_start(ap, val_fmt);
488 h->val = apr_pvsprintf(headers->pool, val_fmt, ap);
489 va_end(ap);
490 }
491
492 svn_error_t *
svn_repos__dump_headers(svn_stream_t * stream,svn_repos__dumpfile_headers_t * headers,apr_pool_t * scratch_pool)493 svn_repos__dump_headers(svn_stream_t *stream,
494 svn_repos__dumpfile_headers_t *headers,
495 apr_pool_t *scratch_pool)
496 {
497 int i;
498
499 for (i = 0; i < headers->nelts; i++)
500 {
501 svn_repos__dumpfile_header_entry_t *h
502 = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t);
503
504 SVN_ERR(svn_stream_printf(stream, scratch_pool,
505 "%s: %s\n", h->key, h->val));
506 }
507
508 /* End of headers */
509 SVN_ERR(svn_stream_puts(stream, "\n"));
510
511 return SVN_NO_ERROR;
512 }
513
514 svn_error_t *
svn_repos__dump_revision_record(svn_stream_t * dump_stream,svn_revnum_t revision,apr_hash_t * extra_headers,apr_hash_t * revprops,svn_boolean_t props_section_always,apr_pool_t * scratch_pool)515 svn_repos__dump_revision_record(svn_stream_t *dump_stream,
516 svn_revnum_t revision,
517 apr_hash_t *extra_headers,
518 apr_hash_t *revprops,
519 svn_boolean_t props_section_always,
520 apr_pool_t *scratch_pool)
521 {
522 svn_stringbuf_t *propstring = NULL;
523 apr_hash_t *headers;
524
525 if (extra_headers)
526 headers = apr_hash_copy(scratch_pool, extra_headers);
527 else
528 headers = apr_hash_make(scratch_pool);
529
530 /* ### someday write a revision-content-checksum */
531
532 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
533 apr_psprintf(scratch_pool, "%ld", revision));
534
535 if (apr_hash_count(revprops) || props_section_always)
536 {
537 svn_stream_t *propstream;
538
539 propstring = svn_stringbuf_create_empty(scratch_pool);
540 propstream = svn_stream_from_stringbuf(propstring, scratch_pool);
541 SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", scratch_pool));
542 SVN_ERR(svn_stream_close(propstream));
543
544 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
545 apr_psprintf(scratch_pool,
546 "%" APR_SIZE_T_FMT, propstring->len));
547 }
548
549 /* Write out a regular Content-length header for the benefit of
550 non-Subversion RFC-822 parsers. */
551 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
552 apr_psprintf(scratch_pool,
553 "%" APR_SIZE_T_FMT, propstring->len));
554 SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
555
556 /* End of headers */
557 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
558
559 /* Property data. */
560 if (propstring)
561 {
562 SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
563 }
564
565 /* put an end to revision */
566 SVN_ERR(svn_stream_puts(dump_stream, "\n"));
567
568 return SVN_NO_ERROR;
569 }
570
571 svn_error_t *
svn_repos__dump_node_record(svn_stream_t * dump_stream,svn_repos__dumpfile_headers_t * headers,svn_stringbuf_t * props_str,svn_boolean_t has_text,svn_filesize_t text_content_length,svn_boolean_t content_length_always,apr_pool_t * scratch_pool)572 svn_repos__dump_node_record(svn_stream_t *dump_stream,
573 svn_repos__dumpfile_headers_t *headers,
574 svn_stringbuf_t *props_str,
575 svn_boolean_t has_text,
576 svn_filesize_t text_content_length,
577 svn_boolean_t content_length_always,
578 apr_pool_t *scratch_pool)
579 {
580 svn_filesize_t content_length = 0;
581
582 /* add content-length headers */
583 if (props_str)
584 {
585 svn_repos__dumpfile_header_pushf(
586 headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
587 "%" APR_SIZE_T_FMT, props_str->len);
588 content_length += props_str->len;
589 }
590 if (has_text)
591 {
592 svn_repos__dumpfile_header_pushf(
593 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
594 "%" SVN_FILESIZE_T_FMT, text_content_length);
595 content_length += text_content_length;
596 }
597 if (content_length_always || props_str || has_text)
598 {
599 svn_repos__dumpfile_header_pushf(
600 headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
601 "%" SVN_FILESIZE_T_FMT, content_length);
602 }
603
604 /* write the headers */
605 SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
606
607 /* write the props */
608 if (props_str)
609 {
610 SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
611 }
612 return SVN_NO_ERROR;
613 }
614
615 /*----------------------------------------------------------------------*/
616
617 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
618
619 /* Look, mom! No file batons! */
620
621 struct edit_baton
622 {
623 /* The relpath which implicitly prepends all full paths coming into
624 this editor. This will almost always be "". */
625 const char *path;
626
627 /* The stream to dump to. */
628 svn_stream_t *stream;
629
630 /* Send feedback here, if non-NULL */
631 svn_repos_notify_func_t notify_func;
632 void *notify_baton;
633
634 /* The fs revision root, so we can read the contents of paths. */
635 svn_fs_root_t *fs_root;
636 svn_revnum_t current_rev;
637
638 /* The fs, so we can grab historic information if needed. */
639 svn_fs_t *fs;
640
641 /* True if dumped nodes should output deltas instead of full text. */
642 svn_boolean_t use_deltas;
643
644 /* True if this "dump" is in fact a verify. */
645 svn_boolean_t verify;
646
647 /* True if checking UCS normalization during a verify. */
648 svn_boolean_t check_normalization;
649
650 /* The first revision dumped in this dumpstream. */
651 svn_revnum_t oldest_dumped_rev;
652
653 /* If not NULL, set to true if any references to revisions older than
654 OLDEST_DUMPED_REV were found in the dumpstream. */
655 svn_boolean_t *found_old_reference;
656
657 /* If not NULL, set to true if any mergeinfo was dumped which contains
658 revisions older than OLDEST_DUMPED_REV. */
659 svn_boolean_t *found_old_mergeinfo;
660
661 /* Structure allows us to verify the paths currently being dumped.
662 If NULL, validity checks are being skipped. */
663 path_tracker_t *path_tracker;
664 };
665
666 struct dir_baton
667 {
668 struct edit_baton *edit_baton;
669
670 /* has this directory been written to the output stream? */
671 svn_boolean_t written_out;
672
673 /* the repository relpath associated with this directory */
674 const char *path;
675
676 /* The comparison repository relpath and revision of this directory.
677 If both of these are valid, use them as a source against which to
678 compare the directory instead of the default comparison source of
679 PATH in the previous revision. */
680 const char *cmp_path;
681 svn_revnum_t cmp_rev;
682
683 /* hash of paths that need to be deleted, though some -might- be
684 replaced. maps const char * paths to this dir_baton. (they're
685 full paths, because that's what the editor driver gives us. but
686 really, they're all within this directory.) */
687 apr_hash_t *deleted_entries;
688
689 /* A flag indicating that new entries have been added to this
690 directory in this revision. Used to optimize detection of UCS
691 representation collisions; we will only check for that in
692 revisions where new names appear in the directory. */
693 svn_boolean_t check_name_collision;
694
695 /* pool to be used for deleting the hash items */
696 apr_pool_t *pool;
697 };
698
699
700 /* Make a directory baton to represent the directory was path
701 (relative to EDIT_BATON's path) is PATH.
702
703 CMP_PATH/CMP_REV are the path/revision against which this directory
704 should be compared for changes. If either is omitted (NULL for the
705 path, SVN_INVALID_REVNUM for the rev), just compare this directory
706 PATH against itself in the previous revision.
707
708 PB is the directory baton of this directory's parent,
709 or NULL if this is the top-level directory of the edit.
710
711 Perform all allocations in POOL. */
712 static struct dir_baton *
make_dir_baton(const char * path,const char * cmp_path,svn_revnum_t cmp_rev,void * edit_baton,struct dir_baton * pb,apr_pool_t * pool)713 make_dir_baton(const char *path,
714 const char *cmp_path,
715 svn_revnum_t cmp_rev,
716 void *edit_baton,
717 struct dir_baton *pb,
718 apr_pool_t *pool)
719 {
720 struct edit_baton *eb = edit_baton;
721 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
722 const char *full_path;
723
724 /* A path relative to nothing? I don't think so. */
725 SVN_ERR_ASSERT_NO_RETURN(!path || pb);
726
727 /* Construct the full path of this node. */
728 if (pb)
729 full_path = svn_relpath_join(eb->path, path, pool);
730 else
731 full_path = apr_pstrdup(pool, eb->path);
732
733 /* Remove leading slashes from copyfrom paths. */
734 if (cmp_path)
735 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
736
737 new_db->edit_baton = eb;
738 new_db->path = full_path;
739 new_db->cmp_path = cmp_path;
740 new_db->cmp_rev = cmp_rev;
741 new_db->written_out = FALSE;
742 new_db->deleted_entries = apr_hash_make(pool);
743 new_db->check_name_collision = FALSE;
744 new_db->pool = pool;
745
746 return new_db;
747 }
748
749 static svn_error_t *
750 fetch_kind_func(svn_node_kind_t *kind,
751 void *baton,
752 const char *path,
753 svn_revnum_t base_revision,
754 apr_pool_t *scratch_pool);
755
756 /* Return an error when PATH in REVISION does not exist or is of a
757 different kind than EXPECTED_KIND. If the latter is svn_node_unknown,
758 skip that check. Use EB for context information. If REVISION is the
759 current revision, use EB's path tracker to follow renames, deletions,
760 etc.
761
762 Use SCRATCH_POOL for temporary allocations.
763 No-op if EB's path tracker has not been initialized.
764 */
765 static svn_error_t *
node_must_exist(struct edit_baton * eb,const char * path,svn_revnum_t revision,svn_node_kind_t expected_kind,apr_pool_t * scratch_pool)766 node_must_exist(struct edit_baton *eb,
767 const char *path,
768 svn_revnum_t revision,
769 svn_node_kind_t expected_kind,
770 apr_pool_t *scratch_pool)
771 {
772 svn_node_kind_t kind = svn_node_none;
773
774 /* in case the caller is trying something stupid ... */
775 if (eb->path_tracker == NULL)
776 return SVN_NO_ERROR;
777
778 /* paths pertaining to the revision currently being processed must
779 be translated / checked using our path tracker. */
780 if (revision == eb->path_tracker->revision)
781 tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
782
783 /* determine the node type (default: no such node) */
784 if (path)
785 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
786
787 /* check results */
788 if (kind == svn_node_none)
789 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
790 _("Path '%s' not found in r%ld."),
791 path, revision);
792
793 if (expected_kind != kind && expected_kind != svn_node_unknown)
794 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
795 _("Unexpected node kind %d for '%s' at r%ld. "
796 "Expected kind was %d."),
797 kind, path, revision, expected_kind);
798
799 return SVN_NO_ERROR;
800 }
801
802 /* Return an error when PATH exists in REVISION. Use EB for context
803 information. If REVISION is the current revision, use EB's path
804 tracker to follow renames, deletions, etc.
805
806 Use SCRATCH_POOL for temporary allocations.
807 No-op if EB's path tracker has not been initialized.
808 */
809 static svn_error_t *
node_must_not_exist(struct edit_baton * eb,const char * path,svn_revnum_t revision,apr_pool_t * scratch_pool)810 node_must_not_exist(struct edit_baton *eb,
811 const char *path,
812 svn_revnum_t revision,
813 apr_pool_t *scratch_pool)
814 {
815 svn_node_kind_t kind = svn_node_none;
816
817 /* in case the caller is trying something stupid ... */
818 if (eb->path_tracker == NULL)
819 return SVN_NO_ERROR;
820
821 /* paths pertaining to the revision currently being processed must
822 be translated / checked using our path tracker. */
823 if (revision == eb->path_tracker->revision)
824 tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
825
826 /* determine the node type (default: no such node) */
827 if (path)
828 SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
829
830 /* check results */
831 if (kind != svn_node_none)
832 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
833 _("Path '%s' exists in r%ld."),
834 path, revision);
835
836 return SVN_NO_ERROR;
837 }
838
839 /* If the mergeinfo in MERGEINFO_STR refers to any revisions older than
840 * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE,
841 * otherwise leave *FOUND_OLD_MERGEINFO unchanged.
842 */
843 static svn_error_t *
verify_mergeinfo_revisions(svn_boolean_t * found_old_mergeinfo,const char * mergeinfo_str,svn_revnum_t oldest_dumped_rev,svn_repos_notify_func_t notify_func,void * notify_baton,apr_pool_t * pool)844 verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
845 const char *mergeinfo_str,
846 svn_revnum_t oldest_dumped_rev,
847 svn_repos_notify_func_t notify_func,
848 void *notify_baton,
849 apr_pool_t *pool)
850 {
851 svn_mergeinfo_t mergeinfo, old_mergeinfo;
852
853 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool));
854 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
855 &old_mergeinfo, mergeinfo,
856 oldest_dumped_rev - 1, 0,
857 TRUE, pool, pool));
858
859 if (apr_hash_count(old_mergeinfo))
860 {
861 notify_warning(pool, notify_func, notify_baton,
862 svn_repos_notify_warning_found_old_mergeinfo,
863 _("Mergeinfo referencing revision(s) prior "
864 "to the oldest dumped revision (r%ld). "
865 "Loading this dump may result in invalid "
866 "mergeinfo."),
867 oldest_dumped_rev);
868
869 if (found_old_mergeinfo)
870 *found_old_mergeinfo = TRUE;
871 }
872
873 return SVN_NO_ERROR;
874 }
875
876 /* Unique string pointers used by verify_mergeinfo_normalization()
877 and check_name_collision() */
878 static const char normalized_unique[] = "normalized_unique";
879 static const char normalized_collision[] = "normalized_collision";
880
881
882 /* Baton for extract_mergeinfo_paths */
883 struct extract_mergeinfo_paths_baton
884 {
885 apr_hash_t *result;
886 svn_boolean_t normalize;
887 svn_membuf_t buffer;
888 };
889
890 /* Hash iterator that uniquifies all keys into a single hash table,
891 optionally normalizing them first. */
892 static svn_error_t *
extract_mergeinfo_paths(void * baton,const void * key,apr_ssize_t klen,void * val,apr_pool_t * iterpool)893 extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
894 void *val, apr_pool_t *iterpool)
895 {
896 struct extract_mergeinfo_paths_baton *const xb = baton;
897 if (xb->normalize)
898 {
899 const char *normkey;
900 SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
901 svn_hash_sets(xb->result,
902 apr_pstrdup(xb->buffer.pool, normkey),
903 normalized_unique);
904 }
905 else
906 apr_hash_set(xb->result,
907 apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
908 normalized_unique);
909 return SVN_NO_ERROR;
910 }
911
912 /* Baton for filter_mergeinfo_paths */
913 struct filter_mergeinfo_paths_baton
914 {
915 apr_hash_t *paths;
916 };
917
918 /* Compare two sets of denormalized paths from mergeinfo entries,
919 removing duplicates. */
920 static svn_error_t *
filter_mergeinfo_paths(void * baton,const void * key,apr_ssize_t klen,void * val,apr_pool_t * iterpool)921 filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
922 void *val, apr_pool_t *iterpool)
923 {
924 struct filter_mergeinfo_paths_baton *const fb = baton;
925
926 if (apr_hash_get(fb->paths, key, klen))
927 apr_hash_set(fb->paths, key, klen, NULL);
928
929 return SVN_NO_ERROR;
930 }
931
932 /* Baton used by the check_mergeinfo_normalization hash iterator. */
933 struct verify_mergeinfo_normalization_baton
934 {
935 const char* path;
936 apr_hash_t *normalized_paths;
937 svn_membuf_t buffer;
938 svn_repos_notify_func_t notify_func;
939 void *notify_baton;
940 };
941
942 /* Hash iterator that verifies normalization and collision of paths in
943 an svn:mergeinfo property. */
944 static svn_error_t *
verify_mergeinfo_normalization(void * baton,const void * key,apr_ssize_t klen,void * val,apr_pool_t * iterpool)945 verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
946 void *val, apr_pool_t *iterpool)
947 {
948 struct verify_mergeinfo_normalization_baton *const vb = baton;
949
950 const char *const path = key;
951 const char *normpath;
952 const char *found;
953
954 SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
955 found = svn_hash_gets(vb->normalized_paths, normpath);
956 if (!found)
957 svn_hash_sets(vb->normalized_paths,
958 apr_pstrdup(vb->buffer.pool, normpath),
959 normalized_unique);
960 else if (found == normalized_collision)
961 /* Skip already reported collision */;
962 else
963 {
964 /* Report path collision in mergeinfo */
965 svn_hash_sets(vb->normalized_paths,
966 apr_pstrdup(vb->buffer.pool, normpath),
967 normalized_collision);
968
969 notify_warning(iterpool, vb->notify_func, vb->notify_baton,
970 svn_repos_notify_warning_mergeinfo_collision,
971 _("Duplicate representation of path '%s'"
972 " in %s property of '%s'"),
973 normpath, SVN_PROP_MERGEINFO, vb->path);
974 }
975 return SVN_NO_ERROR;
976 }
977
978 /* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the
979 svn:mergeinfo property value being set; OLD_MERGEINFO is the
980 previous property value, which may be NULL. Only the paths that
981 were added in are checked, including collision checks. This
982 minimizes the number of notifications we generate for a given
983 mergeinfo property. */
984 static svn_error_t *
check_mergeinfo_normalization(const char * path,const char * new_mergeinfo,const char * old_mergeinfo,svn_repos_notify_func_t notify_func,void * notify_baton,apr_pool_t * pool)985 check_mergeinfo_normalization(const char *path,
986 const char *new_mergeinfo,
987 const char *old_mergeinfo,
988 svn_repos_notify_func_t notify_func,
989 void *notify_baton,
990 apr_pool_t *pool)
991 {
992 svn_mergeinfo_t mergeinfo;
993 apr_hash_t *normalized_paths;
994 apr_hash_t *added_paths;
995 struct extract_mergeinfo_paths_baton extract_baton;
996 struct verify_mergeinfo_normalization_baton verify_baton;
997
998 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
999
1000 extract_baton.result = apr_hash_make(pool);
1001 extract_baton.normalize = FALSE;
1002 svn_membuf__create(&extract_baton.buffer, 0, pool);
1003 SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo,
1004 extract_mergeinfo_paths,
1005 &extract_baton, pool));
1006 added_paths = extract_baton.result;
1007
1008 if (old_mergeinfo)
1009 {
1010 struct filter_mergeinfo_paths_baton filter_baton;
1011 svn_mergeinfo_t oldinfo;
1012
1013 extract_baton.result = apr_hash_make(pool);
1014 extract_baton.normalize = TRUE;
1015 SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool));
1016 SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1017 extract_mergeinfo_paths,
1018 &extract_baton, pool));
1019 normalized_paths = extract_baton.result;
1020
1021 filter_baton.paths = added_paths;
1022 SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1023 filter_mergeinfo_paths,
1024 &filter_baton, pool));
1025 }
1026 else
1027 normalized_paths = apr_hash_make(pool);
1028
1029 verify_baton.path = path;
1030 verify_baton.normalized_paths = normalized_paths;
1031 verify_baton.buffer = extract_baton.buffer;
1032 verify_baton.notify_func = notify_func;
1033 verify_baton.notify_baton = notify_baton;
1034 SVN_ERR(svn_iter_apr_hash(NULL, added_paths,
1035 verify_mergeinfo_normalization,
1036 &verify_baton, pool));
1037
1038 return SVN_NO_ERROR;
1039 }
1040
1041
1042 /* A special case of dump_node(), for a delete record.
1043 *
1044 * The only thing special about this version is it only writes one blank
1045 * line, not two, after the headers. Why? Historical precedent for the
1046 * case where a delete record is used as part of a (delete + add-with-history)
1047 * in implementing a replacement.
1048 *
1049 * Also it doesn't do a path-tracker check.
1050 */
1051 static svn_error_t *
dump_node_delete(svn_stream_t * stream,const char * node_relpath,apr_pool_t * pool)1052 dump_node_delete(svn_stream_t *stream,
1053 const char *node_relpath,
1054 apr_pool_t *pool)
1055 {
1056 svn_repos__dumpfile_headers_t *headers
1057 = svn_repos__dumpfile_headers_create(pool);
1058
1059 /* Node-path: ... */
1060 svn_repos__dumpfile_header_push(
1061 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
1062
1063 /* Node-action: delete */
1064 svn_repos__dumpfile_header_push(
1065 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1066
1067 SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
1068 return SVN_NO_ERROR;
1069 }
1070
1071 /* This helper is the main "meat" of the editor -- it does all the
1072 work of writing a node record.
1073
1074 Write out a node record for PATH of type KIND under EB->FS_ROOT.
1075 ACTION describes what is happening to the node (see enum svn_node_action).
1076 Write record to writable EB->STREAM.
1077
1078 If the node was itself copied, IS_COPY is TRUE and the
1079 path/revision of the copy source are in CMP_PATH/CMP_REV. If
1080 IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
1081 of a copied subtree.
1082 */
1083 static svn_error_t *
dump_node(struct edit_baton * eb,const char * path,svn_node_kind_t kind,enum svn_node_action action,svn_boolean_t is_copy,const char * cmp_path,svn_revnum_t cmp_rev,apr_pool_t * pool)1084 dump_node(struct edit_baton *eb,
1085 const char *path,
1086 svn_node_kind_t kind,
1087 enum svn_node_action action,
1088 svn_boolean_t is_copy,
1089 const char *cmp_path,
1090 svn_revnum_t cmp_rev,
1091 apr_pool_t *pool)
1092 {
1093 svn_stringbuf_t *propstring;
1094 apr_size_t len;
1095 svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
1096 const char *compare_path = path;
1097 svn_revnum_t compare_rev = eb->current_rev - 1;
1098 svn_fs_root_t *compare_root = NULL;
1099 apr_file_t *delta_file = NULL;
1100 svn_repos__dumpfile_headers_t *headers
1101 = svn_repos__dumpfile_headers_create(pool);
1102 svn_filesize_t textlen;
1103
1104 /* Maybe validate the path. */
1105 if (eb->verify || eb->notify_func)
1106 {
1107 svn_error_t *err = svn_fs__path_valid(path, pool);
1108
1109 if (err)
1110 {
1111 if (eb->notify_func)
1112 {
1113 char errbuf[512]; /* ### svn_strerror() magic number */
1114
1115 notify_warning(pool, eb->notify_func, eb->notify_baton,
1116 svn_repos_notify_warning_invalid_fspath,
1117 _("E%06d: While validating fspath '%s': %s"),
1118 err->apr_err, path,
1119 svn_err_best_message(err, errbuf, sizeof(errbuf)));
1120 }
1121
1122 /* Return the error in addition to notifying about it. */
1123 if (eb->verify)
1124 return svn_error_trace(err);
1125 else
1126 svn_error_clear(err);
1127 }
1128 }
1129
1130 /* Write out metadata headers for this file node. */
1131 svn_repos__dumpfile_header_push(
1132 headers, SVN_REPOS_DUMPFILE_NODE_PATH, path);
1133 if (kind == svn_node_file)
1134 svn_repos__dumpfile_header_push(
1135 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
1136 else if (kind == svn_node_dir)
1137 svn_repos__dumpfile_header_push(
1138 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
1139
1140 /* Remove leading slashes from copyfrom paths. */
1141 if (cmp_path)
1142 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
1143
1144 /* Validate the comparison path/rev. */
1145 if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
1146 {
1147 compare_path = cmp_path;
1148 compare_rev = cmp_rev;
1149 }
1150
1151 switch (action)
1152 {
1153 case svn_node_action_change:
1154 if (eb->path_tracker)
1155 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1156 apr_psprintf(pool, _("Change invalid path '%s' in r%ld"),
1157 path, eb->current_rev));
1158
1159 svn_repos__dumpfile_header_push(
1160 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
1161
1162 /* either the text or props changed, or possibly both. */
1163 SVN_ERR(svn_fs_revision_root(&compare_root,
1164 svn_fs_root_fs(eb->fs_root),
1165 compare_rev, pool));
1166
1167 SVN_ERR(svn_fs_props_changed(&must_dump_props,
1168 compare_root, compare_path,
1169 eb->fs_root, path, pool));
1170 if (kind == svn_node_file)
1171 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1172 compare_root, compare_path,
1173 eb->fs_root, path, pool));
1174 break;
1175
1176 case svn_node_action_delete:
1177 if (eb->path_tracker)
1178 {
1179 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1180 apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"),
1181 path, eb->current_rev));
1182 tracker_path_delete(eb->path_tracker, path);
1183 }
1184
1185 svn_repos__dumpfile_header_push(
1186 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1187
1188 /* we can leave this routine quietly now, don't need to dump
1189 any content. */
1190 must_dump_text = FALSE;
1191 must_dump_props = FALSE;
1192 break;
1193
1194 case svn_node_action_replace:
1195 if (eb->path_tracker)
1196 SVN_ERR_W(node_must_exist(eb, path, eb->current_rev,
1197 svn_node_unknown, pool),
1198 apr_psprintf(pool,
1199 _("Replacing non-existent path '%s' in r%ld"),
1200 path, eb->current_rev));
1201
1202 if (! is_copy)
1203 {
1204 if (eb->path_tracker)
1205 tracker_path_replace(eb->path_tracker, path);
1206
1207 /* a simple delete+add, implied by a single 'replace' action. */
1208 svn_repos__dumpfile_header_push(
1209 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
1210
1211 /* definitely need to dump all content for a replace. */
1212 if (kind == svn_node_file)
1213 must_dump_text = TRUE;
1214 must_dump_props = TRUE;
1215 break;
1216 }
1217 else
1218 {
1219 /* more complex: delete original, then add-with-history. */
1220 /* ### Why not write a 'replace' record? Don't know. */
1221
1222 if (eb->path_tracker)
1223 {
1224 tracker_path_delete(eb->path_tracker, path);
1225 }
1226
1227 /* ### Unusually, we end this 'delete' node record with only a single
1228 blank line after the header block -- no extra blank line. */
1229 SVN_ERR(dump_node_delete(eb->stream, path, pool));
1230
1231 /* The remaining action is a non-replacing add-with-history */
1232 /* action = svn_node_action_add; */
1233 }
1234 /* FALL THROUGH to 'add' */
1235
1236 case svn_node_action_add:
1237 if (eb->path_tracker)
1238 SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool),
1239 apr_psprintf(pool,
1240 _("Adding already existing path '%s' in r%ld"),
1241 path, eb->current_rev));
1242
1243 svn_repos__dumpfile_header_push(
1244 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
1245
1246 if (! is_copy)
1247 {
1248 if (eb->path_tracker)
1249 tracker_path_add(eb->path_tracker, path);
1250
1251 /* Dump all contents for a simple 'add'. */
1252 if (kind == svn_node_file)
1253 must_dump_text = TRUE;
1254 must_dump_props = TRUE;
1255 }
1256 else
1257 {
1258 if (eb->path_tracker)
1259 {
1260 SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
1261 kind, pool),
1262 apr_psprintf(pool,
1263 _("Copying from invalid path to "
1264 "'%s' in r%ld"),
1265 path, eb->current_rev));
1266 tracker_path_copy(eb->path_tracker, path, compare_path,
1267 compare_rev);
1268 }
1269
1270 if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
1271 && eb->notify_func)
1272 {
1273 notify_warning(pool, eb->notify_func, eb->notify_baton,
1274 svn_repos_notify_warning_found_old_reference,
1275 _("Referencing data in revision %ld,"
1276 " which is older than the oldest"
1277 " dumped revision (r%ld). Loading this dump"
1278 " into an empty repository"
1279 " will fail."),
1280 cmp_rev, eb->oldest_dumped_rev);
1281 if (eb->found_old_reference)
1282 *eb->found_old_reference = TRUE;
1283 }
1284
1285 svn_repos__dumpfile_header_pushf(
1286 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev);
1287 svn_repos__dumpfile_header_push(
1288 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path);
1289
1290 SVN_ERR(svn_fs_revision_root(&compare_root,
1291 svn_fs_root_fs(eb->fs_root),
1292 compare_rev, pool));
1293
1294 /* Need to decide if the copied node had any extra textual or
1295 property mods as well. */
1296 SVN_ERR(svn_fs_props_changed(&must_dump_props,
1297 compare_root, compare_path,
1298 eb->fs_root, path, pool));
1299 if (kind == svn_node_file)
1300 {
1301 svn_checksum_t *checksum;
1302 const char *hex_digest;
1303 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1304 compare_root, compare_path,
1305 eb->fs_root, path, pool));
1306
1307 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1308 compare_root, compare_path,
1309 FALSE, pool));
1310 hex_digest = svn_checksum_to_cstring(checksum, pool);
1311 if (hex_digest)
1312 svn_repos__dumpfile_header_push(
1313 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
1314
1315 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1316 compare_root, compare_path,
1317 FALSE, pool));
1318 hex_digest = svn_checksum_to_cstring(checksum, pool);
1319 if (hex_digest)
1320 svn_repos__dumpfile_header_push(
1321 headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
1322 }
1323 }
1324 break;
1325 }
1326
1327 if ((! must_dump_text) && (! must_dump_props))
1328 {
1329 /* If we're not supposed to dump text or props, so be it, we can
1330 just go home. However, if either one needs to be dumped,
1331 then our dumpstream format demands that at a *minimum*, we
1332 see a lone "PROPS-END" as a divider between text and props
1333 content within the content-block. */
1334 SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool));
1335 len = 1;
1336 return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
1337 }
1338
1339 /*** Start prepping content to dump... ***/
1340
1341 /* If we are supposed to dump properties, write out a property
1342 length header and generate a stringbuf that contains those
1343 property values here. */
1344 if (must_dump_props)
1345 {
1346 apr_hash_t *prophash, *oldhash = NULL;
1347 svn_stream_t *propstream;
1348
1349 SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
1350
1351 /* If this is a partial dump, then issue a warning if we dump mergeinfo
1352 properties that refer to revisions older than the first revision
1353 dumped. */
1354 if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
1355 {
1356 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1357 SVN_PROP_MERGEINFO);
1358 if (mergeinfo_str)
1359 {
1360 /* An error in verifying the mergeinfo must not prevent dumping
1361 the data. Ignore any such error. */
1362 svn_error_clear(verify_mergeinfo_revisions(
1363 eb->found_old_mergeinfo,
1364 mergeinfo_str->data, eb->oldest_dumped_rev,
1365 eb->notify_func, eb->notify_baton,
1366 pool));
1367 }
1368 }
1369
1370 /* If we're checking UCS normalization, also parse any changed
1371 mergeinfo and warn about denormalized paths and name
1372 collisions there. */
1373 if (eb->verify && eb->check_normalization && eb->notify_func)
1374 {
1375 /* N.B.: This hash lookup happens only once; the conditions
1376 for verifying historic mergeinfo references and checking
1377 UCS normalization are mutually exclusive. */
1378 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1379 SVN_PROP_MERGEINFO);
1380 if (mergeinfo_str)
1381 {
1382 svn_string_t *oldinfo_str = NULL;
1383 if (compare_root)
1384 {
1385 SVN_ERR(svn_fs_node_proplist(&oldhash,
1386 compare_root, compare_path,
1387 pool));
1388 oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
1389 }
1390 SVN_ERR(check_mergeinfo_normalization(
1391 path, mergeinfo_str->data,
1392 (oldinfo_str ? oldinfo_str->data : NULL),
1393 eb->notify_func, eb->notify_baton, pool));
1394 }
1395 }
1396
1397 if (eb->use_deltas && compare_root)
1398 {
1399 /* Fetch the old property hash to diff against and output a header
1400 saying that our property contents are a delta. */
1401 if (!oldhash) /* May have been set for normalization check */
1402 SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
1403 pool));
1404 svn_repos__dumpfile_header_push(
1405 headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
1406 }
1407 else
1408 oldhash = apr_hash_make(pool);
1409 propstring = svn_stringbuf_create_ensure(0, pool);
1410 propstream = svn_stream_from_stringbuf(propstring, pool);
1411 SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
1412 "PROPS-END", pool));
1413 SVN_ERR(svn_stream_close(propstream));
1414 }
1415
1416 /* If we are supposed to dump text, write out a text length header
1417 here, and an MD5 checksum (if available). */
1418 if (must_dump_text && (kind == svn_node_file))
1419 {
1420 svn_checksum_t *checksum;
1421 const char *hex_digest;
1422
1423 if (eb->use_deltas)
1424 {
1425 /* Compute the text delta now and write it into a temporary
1426 file, so that we can find its length. Output a header
1427 saying our text contents are a delta. */
1428 SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
1429 compare_path, eb->fs_root, path, pool));
1430 svn_repos__dumpfile_header_push(
1431 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
1432
1433 if (compare_root)
1434 {
1435 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1436 compare_root, compare_path,
1437 FALSE, pool));
1438 hex_digest = svn_checksum_to_cstring(checksum, pool);
1439 if (hex_digest)
1440 svn_repos__dumpfile_header_push(
1441 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
1442
1443 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1444 compare_root, compare_path,
1445 FALSE, pool));
1446 hex_digest = svn_checksum_to_cstring(checksum, pool);
1447 if (hex_digest)
1448 svn_repos__dumpfile_header_push(
1449 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
1450 }
1451 }
1452 else
1453 {
1454 /* Just fetch the length of the file. */
1455 SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
1456 }
1457
1458 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1459 eb->fs_root, path, FALSE, pool));
1460 hex_digest = svn_checksum_to_cstring(checksum, pool);
1461 if (hex_digest)
1462 svn_repos__dumpfile_header_push(
1463 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
1464
1465 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1466 eb->fs_root, path, FALSE, pool));
1467 hex_digest = svn_checksum_to_cstring(checksum, pool);
1468 if (hex_digest)
1469 svn_repos__dumpfile_header_push(
1470 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
1471 }
1472
1473 /* 'Content-length:' is the last header before we dump the content,
1474 and is the sum of the text and prop contents lengths. We write
1475 this only for the benefit of non-Subversion RFC-822 parsers. */
1476 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers,
1477 must_dump_props ? propstring : NULL,
1478 must_dump_text,
1479 must_dump_text ? textlen : 0,
1480 TRUE /*content_length_always*/,
1481 pool));
1482
1483 /* Dump text content */
1484 if (must_dump_text && (kind == svn_node_file))
1485 {
1486 svn_stream_t *contents;
1487
1488 if (delta_file)
1489 {
1490 /* Make sure to close the underlying file when the stream is
1491 closed. */
1492 contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
1493 }
1494 else
1495 SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
1496
1497 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
1498 NULL, NULL, pool));
1499 }
1500
1501 len = 2;
1502 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
1503 }
1504
1505
1506 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** root_baton)1507 open_root(void *edit_baton,
1508 svn_revnum_t base_revision,
1509 apr_pool_t *pool,
1510 void **root_baton)
1511 {
1512 *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
1513 edit_baton, NULL, pool);
1514 return SVN_NO_ERROR;
1515 }
1516
1517
1518 static svn_error_t *
delete_entry(const char * path,svn_revnum_t revision,void * parent_baton,apr_pool_t * pool)1519 delete_entry(const char *path,
1520 svn_revnum_t revision,
1521 void *parent_baton,
1522 apr_pool_t *pool)
1523 {
1524 struct dir_baton *pb = parent_baton;
1525 const char *mypath = apr_pstrdup(pb->pool, path);
1526
1527 /* remember this path needs to be deleted. */
1528 svn_hash_sets(pb->deleted_entries, mypath, pb);
1529
1530 return SVN_NO_ERROR;
1531 }
1532
1533
1534 static svn_error_t *
add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_rev,apr_pool_t * pool,void ** child_baton)1535 add_directory(const char *path,
1536 void *parent_baton,
1537 const char *copyfrom_path,
1538 svn_revnum_t copyfrom_rev,
1539 apr_pool_t *pool,
1540 void **child_baton)
1541 {
1542 struct dir_baton *pb = parent_baton;
1543 struct edit_baton *eb = pb->edit_baton;
1544 void *was_deleted;
1545 svn_boolean_t is_copy = FALSE;
1546 struct dir_baton *new_db
1547 = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool);
1548
1549 /* This might be a replacement -- is the path already deleted? */
1550 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1551
1552 /* Detect an add-with-history. */
1553 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1554
1555 /* Dump the node. */
1556 SVN_ERR(dump_node(eb, path,
1557 svn_node_dir,
1558 was_deleted ? svn_node_action_replace : svn_node_action_add,
1559 is_copy,
1560 is_copy ? copyfrom_path : NULL,
1561 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1562 pool));
1563
1564 if (was_deleted)
1565 /* Delete the path, it's now been dumped. */
1566 svn_hash_sets(pb->deleted_entries, path, NULL);
1567
1568 /* Check for normalized name clashes, but only if this is actually a
1569 new name in the parent, not a replacement. */
1570 if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1571 {
1572 pb->check_name_collision = TRUE;
1573 }
1574
1575 new_db->written_out = TRUE;
1576
1577 *child_baton = new_db;
1578 return SVN_NO_ERROR;
1579 }
1580
1581
1582 static svn_error_t *
open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** child_baton)1583 open_directory(const char *path,
1584 void *parent_baton,
1585 svn_revnum_t base_revision,
1586 apr_pool_t *pool,
1587 void **child_baton)
1588 {
1589 struct dir_baton *pb = parent_baton;
1590 struct edit_baton *eb = pb->edit_baton;
1591 struct dir_baton *new_db;
1592 const char *cmp_path = NULL;
1593 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1594
1595 /* If the parent directory has explicit comparison path and rev,
1596 record the same for this one. */
1597 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1598 {
1599 cmp_path = svn_relpath_join(pb->cmp_path,
1600 svn_relpath_basename(path, pool), pool);
1601 cmp_rev = pb->cmp_rev;
1602 }
1603
1604 new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool);
1605 *child_baton = new_db;
1606 return SVN_NO_ERROR;
1607 }
1608
1609
1610 static svn_error_t *
close_directory(void * dir_baton,apr_pool_t * pool)1611 close_directory(void *dir_baton,
1612 apr_pool_t *pool)
1613 {
1614 struct dir_baton *db = dir_baton;
1615 struct edit_baton *eb = db->edit_baton;
1616 apr_pool_t *subpool = svn_pool_create(pool);
1617 int i;
1618 apr_array_header_t *sorted_entries;
1619
1620 /* Sort entries lexically instead of as paths. Even though the entries
1621 * are full paths they're all in the same directory (see comment in struct
1622 * dir_baton definition). So we really want to sort by basename, in which
1623 * case the lexical sort function is more efficient. */
1624 sorted_entries = svn_sort__hash(db->deleted_entries,
1625 svn_sort_compare_items_lexically, pool);
1626 for (i = 0; i < sorted_entries->nelts; i++)
1627 {
1628 const char *path = APR_ARRAY_IDX(sorted_entries, i,
1629 svn_sort__item_t).key;
1630
1631 svn_pool_clear(subpool);
1632
1633 /* By sending 'svn_node_unknown', the Node-kind: header simply won't
1634 be written out. No big deal at all, really. The loader
1635 shouldn't care. */
1636 SVN_ERR(dump_node(eb, path,
1637 svn_node_unknown, svn_node_action_delete,
1638 FALSE, NULL, SVN_INVALID_REVNUM, subpool));
1639 }
1640
1641 svn_pool_destroy(subpool);
1642 return SVN_NO_ERROR;
1643 }
1644
1645
1646 static svn_error_t *
add_file(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_rev,apr_pool_t * pool,void ** file_baton)1647 add_file(const char *path,
1648 void *parent_baton,
1649 const char *copyfrom_path,
1650 svn_revnum_t copyfrom_rev,
1651 apr_pool_t *pool,
1652 void **file_baton)
1653 {
1654 struct dir_baton *pb = parent_baton;
1655 struct edit_baton *eb = pb->edit_baton;
1656 void *was_deleted;
1657 svn_boolean_t is_copy = FALSE;
1658
1659 /* This might be a replacement -- is the path already deleted? */
1660 was_deleted = svn_hash_gets(pb->deleted_entries, path);
1661
1662 /* Detect add-with-history. */
1663 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1664
1665 /* Dump the node. */
1666 SVN_ERR(dump_node(eb, path,
1667 svn_node_file,
1668 was_deleted ? svn_node_action_replace : svn_node_action_add,
1669 is_copy,
1670 is_copy ? copyfrom_path : NULL,
1671 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1672 pool));
1673
1674 if (was_deleted)
1675 /* delete the path, it's now been dumped. */
1676 svn_hash_sets(pb->deleted_entries, path, NULL);
1677
1678 /* Check for normalized name clashes, but only if this is actually a
1679 new name in the parent, not a replacement. */
1680 if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1681 {
1682 pb->check_name_collision = TRUE;
1683 }
1684
1685 *file_baton = NULL; /* muhahahaha */
1686 return SVN_NO_ERROR;
1687 }
1688
1689
1690 static svn_error_t *
open_file(const char * path,void * parent_baton,svn_revnum_t ancestor_revision,apr_pool_t * pool,void ** file_baton)1691 open_file(const char *path,
1692 void *parent_baton,
1693 svn_revnum_t ancestor_revision,
1694 apr_pool_t *pool,
1695 void **file_baton)
1696 {
1697 struct dir_baton *pb = parent_baton;
1698 struct edit_baton *eb = pb->edit_baton;
1699 const char *cmp_path = NULL;
1700 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1701
1702 /* If the parent directory has explicit comparison path and rev,
1703 record the same for this one. */
1704 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1705 {
1706 cmp_path = svn_relpath_join(pb->cmp_path,
1707 svn_relpath_basename(path, pool), pool);
1708 cmp_rev = pb->cmp_rev;
1709 }
1710
1711 SVN_ERR(dump_node(eb, path,
1712 svn_node_file, svn_node_action_change,
1713 FALSE, cmp_path, cmp_rev, pool));
1714
1715 *file_baton = NULL; /* muhahahaha again */
1716 return SVN_NO_ERROR;
1717 }
1718
1719
1720 static svn_error_t *
change_dir_prop(void * parent_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)1721 change_dir_prop(void *parent_baton,
1722 const char *name,
1723 const svn_string_t *value,
1724 apr_pool_t *pool)
1725 {
1726 struct dir_baton *db = parent_baton;
1727 struct edit_baton *eb = db->edit_baton;
1728
1729 /* This function is what distinguishes between a directory that is
1730 opened to merely get somewhere, vs. one that is opened because it
1731 *actually* changed by itself.
1732
1733 Instead of recording the prop changes here, we just use this method
1734 to trigger writing the node; dump_node() finds all the changes. */
1735 if (! db->written_out)
1736 {
1737 SVN_ERR(dump_node(eb, db->path,
1738 svn_node_dir, svn_node_action_change,
1739 /* ### We pass is_copy=FALSE; this might be wrong
1740 but the parameter isn't used when action=change. */
1741 FALSE, db->cmp_path, db->cmp_rev, pool));
1742 db->written_out = TRUE;
1743 }
1744 return SVN_NO_ERROR;
1745 }
1746
1747 static svn_error_t *
fetch_props_func(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1748 fetch_props_func(apr_hash_t **props,
1749 void *baton,
1750 const char *path,
1751 svn_revnum_t base_revision,
1752 apr_pool_t *result_pool,
1753 apr_pool_t *scratch_pool)
1754 {
1755 struct edit_baton *eb = baton;
1756 svn_error_t *err;
1757 svn_fs_root_t *fs_root;
1758
1759 if (!SVN_IS_VALID_REVNUM(base_revision))
1760 base_revision = eb->current_rev - 1;
1761
1762 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1763
1764 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
1765 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1766 {
1767 svn_error_clear(err);
1768 *props = apr_hash_make(result_pool);
1769 return SVN_NO_ERROR;
1770 }
1771 else if (err)
1772 return svn_error_trace(err);
1773
1774 return SVN_NO_ERROR;
1775 }
1776
1777 static svn_error_t *
fetch_kind_func(svn_node_kind_t * kind,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * scratch_pool)1778 fetch_kind_func(svn_node_kind_t *kind,
1779 void *baton,
1780 const char *path,
1781 svn_revnum_t base_revision,
1782 apr_pool_t *scratch_pool)
1783 {
1784 struct edit_baton *eb = baton;
1785 svn_fs_root_t *fs_root;
1786
1787 if (!SVN_IS_VALID_REVNUM(base_revision))
1788 base_revision = eb->current_rev - 1;
1789
1790 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1791
1792 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
1793
1794 return SVN_NO_ERROR;
1795 }
1796
1797 static svn_error_t *
fetch_base_func(const char ** filename,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1798 fetch_base_func(const char **filename,
1799 void *baton,
1800 const char *path,
1801 svn_revnum_t base_revision,
1802 apr_pool_t *result_pool,
1803 apr_pool_t *scratch_pool)
1804 {
1805 struct edit_baton *eb = baton;
1806 svn_stream_t *contents;
1807 svn_stream_t *file_stream;
1808 const char *tmp_filename;
1809 svn_error_t *err;
1810 svn_fs_root_t *fs_root;
1811
1812 if (!SVN_IS_VALID_REVNUM(base_revision))
1813 base_revision = eb->current_rev - 1;
1814
1815 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1816
1817 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
1818 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1819 {
1820 svn_error_clear(err);
1821 *filename = NULL;
1822 return SVN_NO_ERROR;
1823 }
1824 else if (err)
1825 return svn_error_trace(err);
1826 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
1827 svn_io_file_del_on_pool_cleanup,
1828 scratch_pool, scratch_pool));
1829 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
1830
1831 *filename = apr_pstrdup(result_pool, tmp_filename);
1832
1833 return SVN_NO_ERROR;
1834 }
1835
1836
1837 static svn_error_t *
get_dump_editor(const svn_delta_editor_t ** editor,void ** edit_baton,svn_fs_t * fs,svn_revnum_t to_rev,const char * root_path,svn_stream_t * stream,svn_boolean_t * found_old_reference,svn_boolean_t * found_old_mergeinfo,svn_error_t * (* custom_close_directory)(void * dir_baton,apr_pool_t * scratch_pool),svn_repos_notify_func_t notify_func,void * notify_baton,svn_revnum_t oldest_dumped_rev,svn_boolean_t use_deltas,svn_boolean_t verify,svn_boolean_t check_normalization,apr_pool_t * pool)1838 get_dump_editor(const svn_delta_editor_t **editor,
1839 void **edit_baton,
1840 svn_fs_t *fs,
1841 svn_revnum_t to_rev,
1842 const char *root_path,
1843 svn_stream_t *stream,
1844 svn_boolean_t *found_old_reference,
1845 svn_boolean_t *found_old_mergeinfo,
1846 svn_error_t *(*custom_close_directory)(void *dir_baton,
1847 apr_pool_t *scratch_pool),
1848 svn_repos_notify_func_t notify_func,
1849 void *notify_baton,
1850 svn_revnum_t oldest_dumped_rev,
1851 svn_boolean_t use_deltas,
1852 svn_boolean_t verify,
1853 svn_boolean_t check_normalization,
1854 apr_pool_t *pool)
1855 {
1856 /* Allocate an edit baton to be stored in every directory baton.
1857 Set it up for the directory baton we create here, which is the
1858 root baton. */
1859 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1860 svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
1861 svn_delta_shim_callbacks_t *shim_callbacks =
1862 svn_delta_shim_callbacks_default(pool);
1863
1864 /* Set up the edit baton. */
1865 eb->stream = stream;
1866 eb->notify_func = notify_func;
1867 eb->notify_baton = notify_baton;
1868 eb->oldest_dumped_rev = oldest_dumped_rev;
1869 eb->path = apr_pstrdup(pool, root_path);
1870 SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
1871 eb->fs = fs;
1872 eb->current_rev = to_rev;
1873 eb->use_deltas = use_deltas;
1874 eb->verify = verify;
1875 eb->check_normalization = check_normalization;
1876 eb->found_old_reference = found_old_reference;
1877 eb->found_old_mergeinfo = found_old_mergeinfo;
1878
1879 /* In non-verification mode, we will allow anything to be dumped because
1880 it might be an incremental dump with possible manual intervention.
1881 Also, this might be the last resort when it comes to data recovery.
1882
1883 Else, make sure that all paths exists at their respective revisions.
1884 */
1885 eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
1886
1887 /* Set up the editor. */
1888 dump_editor->open_root = open_root;
1889 dump_editor->delete_entry = delete_entry;
1890 dump_editor->add_directory = add_directory;
1891 dump_editor->open_directory = open_directory;
1892 if (custom_close_directory)
1893 dump_editor->close_directory = custom_close_directory;
1894 else
1895 dump_editor->close_directory = close_directory;
1896 dump_editor->change_dir_prop = change_dir_prop;
1897 dump_editor->add_file = add_file;
1898 dump_editor->open_file = open_file;
1899
1900 *edit_baton = eb;
1901 *editor = dump_editor;
1902
1903 shim_callbacks->fetch_kind_func = fetch_kind_func;
1904 shim_callbacks->fetch_props_func = fetch_props_func;
1905 shim_callbacks->fetch_base_func = fetch_base_func;
1906 shim_callbacks->fetch_baton = eb;
1907
1908 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1909 NULL, NULL, shim_callbacks, pool, pool));
1910
1911 return SVN_NO_ERROR;
1912 }
1913
1914 /*----------------------------------------------------------------------*/
1915
1916 /** The main dumping routine, svn_repos_dump_fs. **/
1917
1918
1919 /* Helper for svn_repos_dump_fs.
1920
1921 Write a revision record of REV in FS to writable STREAM, using POOL.
1922 */
1923 static svn_error_t *
write_revision_record(svn_stream_t * stream,svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * pool)1924 write_revision_record(svn_stream_t *stream,
1925 svn_fs_t *fs,
1926 svn_revnum_t rev,
1927 apr_pool_t *pool)
1928 {
1929 apr_hash_t *props;
1930 apr_time_t timetemp;
1931 svn_string_t *datevalue;
1932
1933 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1934
1935 /* Run revision date properties through the time conversion to
1936 canonicalize them. */
1937 /* ### Remove this when it is no longer needed for sure. */
1938 datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1939 if (datevalue)
1940 {
1941 SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1942 datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1943 pool);
1944 svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1945 }
1946
1947 SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1948 TRUE /*props_section_always*/,
1949 pool));
1950 return SVN_NO_ERROR;
1951 }
1952
1953
1954
1955 /* The main dumper. */
1956 svn_error_t *
svn_repos_dump_fs3(svn_repos_t * repos,svn_stream_t * stream,svn_revnum_t start_rev,svn_revnum_t end_rev,svn_boolean_t incremental,svn_boolean_t use_deltas,svn_repos_notify_func_t notify_func,void * notify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1957 svn_repos_dump_fs3(svn_repos_t *repos,
1958 svn_stream_t *stream,
1959 svn_revnum_t start_rev,
1960 svn_revnum_t end_rev,
1961 svn_boolean_t incremental,
1962 svn_boolean_t use_deltas,
1963 svn_repos_notify_func_t notify_func,
1964 void *notify_baton,
1965 svn_cancel_func_t cancel_func,
1966 void *cancel_baton,
1967 apr_pool_t *pool)
1968 {
1969 const svn_delta_editor_t *dump_editor;
1970 void *dump_edit_baton = NULL;
1971 svn_revnum_t rev;
1972 svn_fs_t *fs = svn_repos_fs(repos);
1973 apr_pool_t *subpool = svn_pool_create(pool);
1974 svn_revnum_t youngest;
1975 const char *uuid;
1976 int version;
1977 svn_boolean_t found_old_reference = FALSE;
1978 svn_boolean_t found_old_mergeinfo = FALSE;
1979 svn_repos_notify_t *notify;
1980
1981 /* Determine the current youngest revision of the filesystem. */
1982 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1983
1984 /* Use default vals if necessary. */
1985 if (! SVN_IS_VALID_REVNUM(start_rev))
1986 start_rev = 0;
1987 if (! SVN_IS_VALID_REVNUM(end_rev))
1988 end_rev = youngest;
1989 if (! stream)
1990 stream = svn_stream_empty(pool);
1991
1992 /* Validate the revisions. */
1993 if (start_rev > end_rev)
1994 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1995 _("Start revision %ld"
1996 " is greater than end revision %ld"),
1997 start_rev, end_rev);
1998 if (end_rev > youngest)
1999 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2000 _("End revision %ld is invalid "
2001 "(youngest revision is %ld)"),
2002 end_rev, youngest);
2003
2004 /* Write out the UUID. */
2005 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2006
2007 /* If we're not using deltas, use the previous version, for
2008 compatibility with svn 1.0.x. */
2009 version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
2010 if (!use_deltas)
2011 version--;
2012
2013 /* Write out "general" metadata for the dumpfile. In this case, a
2014 magic header followed by a dumpfile format version. */
2015 SVN_ERR(svn_stream_printf(stream, pool,
2016 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
2017 version));
2018 SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
2019 ": %s\n\n", uuid));
2020
2021 /* Create a notify object that we can reuse in the loop. */
2022 if (notify_func)
2023 notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2024 pool);
2025
2026 /* Main loop: we're going to dump revision REV. */
2027 for (rev = start_rev; rev <= end_rev; rev++)
2028 {
2029 svn_fs_root_t *to_root;
2030 svn_boolean_t use_deltas_for_rev;
2031
2032 svn_pool_clear(subpool);
2033
2034 /* Check for cancellation. */
2035 if (cancel_func)
2036 SVN_ERR(cancel_func(cancel_baton));
2037
2038 /* Write the revision record. */
2039 SVN_ERR(write_revision_record(stream, fs, rev, subpool));
2040
2041 /* When dumping revision 0, we just write out the revision record.
2042 The parser might want to use its properties. */
2043 if (rev == 0)
2044 goto loop_end;
2045
2046 /* Fetch the editor which dumps nodes to a file. Regardless of
2047 what we've been told, don't use deltas for the first rev of a
2048 non-incremental dump. */
2049 use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
2050 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
2051 "", stream, &found_old_reference,
2052 &found_old_mergeinfo, NULL,
2053 notify_func, notify_baton,
2054 start_rev, use_deltas_for_rev, FALSE, FALSE,
2055 subpool));
2056
2057 /* Drive the editor in one way or another. */
2058 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool));
2059
2060 /* If this is the first revision of a non-incremental dump,
2061 we're in for a full tree dump. Otherwise, we want to simply
2062 replay the revision. */
2063 if ((rev == start_rev) && (! incremental))
2064 {
2065 /* Compare against revision 0, so everything appears to be added. */
2066 svn_fs_root_t *from_root;
2067 SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool));
2068 SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
2069 to_root, "",
2070 dump_editor, dump_edit_baton,
2071 NULL,
2072 NULL,
2073 FALSE, /* don't send text-deltas */
2074 svn_depth_infinity,
2075 FALSE, /* don't send entry props */
2076 FALSE, /* don't ignore ancestry */
2077 subpool));
2078 }
2079 else
2080 {
2081 /* The normal case: compare consecutive revs. */
2082 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2083 dump_editor, dump_edit_baton,
2084 NULL, NULL, subpool));
2085
2086 /* While our editor close_edit implementation is a no-op, we still
2087 do this for completeness. */
2088 SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
2089 }
2090
2091 loop_end:
2092 if (notify_func)
2093 {
2094 notify->revision = rev;
2095 notify_func(notify_baton, notify, subpool);
2096 }
2097 }
2098
2099 if (notify_func)
2100 {
2101 /* Did we issue any warnings about references to revisions older than
2102 the oldest dumped revision? If so, then issue a final generic
2103 warning, since the inline warnings already issued might easily be
2104 missed. */
2105
2106 notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
2107 notify_func(notify_baton, notify, subpool);
2108
2109 if (found_old_reference)
2110 {
2111 notify_warning(subpool, notify_func, notify_baton,
2112 svn_repos_notify_warning_found_old_reference,
2113 _("The range of revisions dumped "
2114 "contained references to "
2115 "copy sources outside that "
2116 "range."));
2117 }
2118
2119 /* Ditto if we issued any warnings about old revisions referenced
2120 in dumped mergeinfo. */
2121 if (found_old_mergeinfo)
2122 {
2123 notify_warning(subpool, notify_func, notify_baton,
2124 svn_repos_notify_warning_found_old_mergeinfo,
2125 _("The range of revisions dumped "
2126 "contained mergeinfo "
2127 "which reference revisions outside "
2128 "that range."));
2129 }
2130 }
2131
2132 svn_pool_destroy(subpool);
2133
2134 return SVN_NO_ERROR;
2135 }
2136
2137
2138 /*----------------------------------------------------------------------*/
2139
2140 /* verify, based on dump */
2141
2142
2143 /* Creating a new revision that changes /A/B/E/bravo means creating new
2144 directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
2145 each entry not changed in the new revision a link back to the entry in a
2146 previous revision. svn_repos_replay()ing a revision does not verify that
2147 those links are correct.
2148
2149 For paths actually changed in the revision we verify, we get directory
2150 contents or file length twice: once in the dump editor, and once here.
2151 We could create a new verify baton, store in it the changed paths, and
2152 skip those here, but that means building an entire wrapper editor and
2153 managing two levels of batons. The impact from checking these entries
2154 twice should be minimal, while the code to avoid it is not.
2155 */
2156
2157 static svn_error_t *
verify_directory_entry(void * baton,const void * key,apr_ssize_t klen,void * val,apr_pool_t * pool)2158 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
2159 void *val, apr_pool_t *pool)
2160 {
2161 struct dir_baton *db = baton;
2162 svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2163 char *path;
2164 svn_boolean_t right_kind;
2165
2166 path = svn_relpath_join(db->path, (const char *)key, pool);
2167
2168 /* since we can't access the directory entries directly by their ID,
2169 we need to navigate from the FS_ROOT to them (relatively expensive
2170 because we may start at a never rev than the last change to node).
2171 We check that the node kind stored in the noderev matches the dir
2172 entry. This also ensures that all entries point to valid noderevs.
2173 */
2174 switch (dirent->kind) {
2175 case svn_node_dir:
2176 SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2177 if (!right_kind)
2178 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2179 _("Node '%s' is not a directory."),
2180 path);
2181
2182 break;
2183 case svn_node_file:
2184 SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2185 if (!right_kind)
2186 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2187 _("Node '%s' is not a file."),
2188 path);
2189 break;
2190 default:
2191 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2192 _("Unexpected node kind %d for '%s'"),
2193 dirent->kind, path);
2194 }
2195
2196 return SVN_NO_ERROR;
2197 }
2198
2199 /* Baton used by the check_name_collision hash iterator. */
2200 struct check_name_collision_baton
2201 {
2202 struct dir_baton *dir_baton;
2203 apr_hash_t *normalized;
2204 svn_membuf_t buffer;
2205 };
2206
2207 /* Scan the directory and report all entry names that differ only in
2208 Unicode character representation. */
2209 static svn_error_t *
check_name_collision(void * baton,const void * key,apr_ssize_t klen,void * val,apr_pool_t * iterpool)2210 check_name_collision(void *baton, const void *key, apr_ssize_t klen,
2211 void *val, apr_pool_t *iterpool)
2212 {
2213 struct check_name_collision_baton *const cb = baton;
2214 const char *name;
2215 const char *found;
2216
2217 SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2218
2219 found = svn_hash_gets(cb->normalized, name);
2220 if (!found)
2221 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2222 normalized_unique);
2223 else if (found == normalized_collision)
2224 /* Skip already reported collision */;
2225 else
2226 {
2227 struct dir_baton *const db = cb->dir_baton;
2228 struct edit_baton *const eb = db->edit_baton;
2229 const char* normpath;
2230
2231 svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2232 normalized_collision);
2233
2234 SVN_ERR(svn_utf__normalize(
2235 &normpath, svn_relpath_join(db->path, name, iterpool),
2236 SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
2237 notify_warning(iterpool, eb->notify_func, eb->notify_baton,
2238 svn_repos_notify_warning_name_collision,
2239 _("Duplicate representation of path '%s'"), normpath);
2240 }
2241 return SVN_NO_ERROR;
2242 }
2243
2244
2245 static svn_error_t *
verify_close_directory(void * dir_baton,apr_pool_t * pool)2246 verify_close_directory(void *dir_baton, apr_pool_t *pool)
2247 {
2248 struct dir_baton *db = dir_baton;
2249 apr_hash_t *dirents;
2250 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
2251 db->path, pool));
2252 SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2253 dir_baton, pool));
2254
2255 if (db->check_name_collision)
2256 {
2257 struct check_name_collision_baton check_baton;
2258 check_baton.dir_baton = db;
2259 check_baton.normalized = apr_hash_make(pool);
2260 svn_membuf__create(&check_baton.buffer, 0, pool);
2261 SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
2262 &check_baton, pool));
2263 }
2264
2265 return close_directory(dir_baton, pool);
2266 }
2267
2268 /* Verify revision REV in file system FS. */
2269 static svn_error_t *
verify_one_revision(svn_fs_t * fs,svn_revnum_t rev,svn_repos_notify_func_t notify_func,void * notify_baton,svn_revnum_t start_rev,svn_boolean_t check_normalization,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)2270 verify_one_revision(svn_fs_t *fs,
2271 svn_revnum_t rev,
2272 svn_repos_notify_func_t notify_func,
2273 void *notify_baton,
2274 svn_revnum_t start_rev,
2275 svn_boolean_t check_normalization,
2276 svn_cancel_func_t cancel_func,
2277 void *cancel_baton,
2278 apr_pool_t *scratch_pool)
2279 {
2280 const svn_delta_editor_t *dump_editor;
2281 void *dump_edit_baton;
2282 svn_fs_root_t *to_root;
2283 apr_hash_t *props;
2284 const svn_delta_editor_t *cancel_editor;
2285 void *cancel_edit_baton;
2286
2287 /* Get cancellable dump editor, but with our close_directory handler.*/
2288 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2289 fs, rev, "",
2290 svn_stream_empty(scratch_pool),
2291 NULL, NULL,
2292 verify_close_directory,
2293 notify_func, notify_baton,
2294 start_rev,
2295 FALSE, TRUE, /* use_deltas, verify */
2296 check_normalization,
2297 scratch_pool));
2298 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2299 dump_editor, dump_edit_baton,
2300 &cancel_editor,
2301 &cancel_edit_baton,
2302 scratch_pool));
2303 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
2304 SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
2305 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2306 cancel_editor, cancel_edit_baton,
2307 NULL, NULL, scratch_pool));
2308
2309 /* While our editor close_edit implementation is a no-op, we still
2310 do this for completeness. */
2311 SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
2312
2313 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool));
2314
2315 return SVN_NO_ERROR;
2316 }
2317
2318 /* Baton type used for forwarding notifications from FS API to REPOS API. */
2319 struct verify_fs_notify_func_baton_t
2320 {
2321 /* notification function to call (must not be NULL) */
2322 svn_repos_notify_func_t notify_func;
2323
2324 /* baton to use for it */
2325 void *notify_baton;
2326
2327 /* type of notification to send (we will simply plug in the revision) */
2328 svn_repos_notify_t *notify;
2329 };
2330
2331 /* Forward the notification to BATON. */
2332 static void
verify_fs_notify_func(svn_revnum_t revision,void * baton,apr_pool_t * pool)2333 verify_fs_notify_func(svn_revnum_t revision,
2334 void *baton,
2335 apr_pool_t *pool)
2336 {
2337 struct verify_fs_notify_func_baton_t *notify_baton = baton;
2338
2339 notify_baton->notify->revision = revision;
2340 notify_baton->notify_func(notify_baton->notify_baton,
2341 notify_baton->notify, pool);
2342 }
2343
2344 static svn_error_t *
report_error(svn_revnum_t revision,svn_error_t * verify_err,svn_repos_verify_callback_t verify_callback,void * verify_baton,apr_pool_t * pool)2345 report_error(svn_revnum_t revision,
2346 svn_error_t *verify_err,
2347 svn_repos_verify_callback_t verify_callback,
2348 void *verify_baton,
2349 apr_pool_t *pool)
2350 {
2351 if (verify_callback)
2352 {
2353 svn_error_t *cb_err;
2354
2355 /* The caller provided us with a callback, so make him responsible
2356 for what's going to happen with the error. */
2357 cb_err = verify_callback(verify_baton, revision, verify_err, pool);
2358 svn_error_clear(verify_err);
2359 SVN_ERR(cb_err);
2360
2361 return SVN_NO_ERROR;
2362 }
2363 else
2364 {
2365 /* No callback -- no second guessing. Just return the error. */
2366 return svn_error_trace(verify_err);
2367 }
2368 }
2369
2370 svn_error_t *
svn_repos_verify_fs3(svn_repos_t * repos,svn_revnum_t start_rev,svn_revnum_t end_rev,svn_boolean_t check_normalization,svn_boolean_t metadata_only,svn_repos_notify_func_t notify_func,void * notify_baton,svn_repos_verify_callback_t verify_callback,void * verify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)2371 svn_repos_verify_fs3(svn_repos_t *repos,
2372 svn_revnum_t start_rev,
2373 svn_revnum_t end_rev,
2374 svn_boolean_t check_normalization,
2375 svn_boolean_t metadata_only,
2376 svn_repos_notify_func_t notify_func,
2377 void *notify_baton,
2378 svn_repos_verify_callback_t verify_callback,
2379 void *verify_baton,
2380 svn_cancel_func_t cancel_func,
2381 void *cancel_baton,
2382 apr_pool_t *pool)
2383 {
2384 svn_fs_t *fs = svn_repos_fs(repos);
2385 svn_revnum_t youngest;
2386 svn_revnum_t rev;
2387 apr_pool_t *iterpool = svn_pool_create(pool);
2388 svn_repos_notify_t *notify;
2389 svn_fs_progress_notify_func_t verify_notify = NULL;
2390 struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
2391 svn_error_t *err;
2392
2393 /* Determine the current youngest revision of the filesystem. */
2394 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2395
2396 /* Use default vals if necessary. */
2397 if (! SVN_IS_VALID_REVNUM(start_rev))
2398 start_rev = 0;
2399 if (! SVN_IS_VALID_REVNUM(end_rev))
2400 end_rev = youngest;
2401
2402 /* Validate the revisions. */
2403 if (start_rev > end_rev)
2404 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2405 _("Start revision %ld"
2406 " is greater than end revision %ld"),
2407 start_rev, end_rev);
2408 if (end_rev > youngest)
2409 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2410 _("End revision %ld is invalid "
2411 "(youngest revision is %ld)"),
2412 end_rev, youngest);
2413
2414 /* Create a notify object that we can reuse within the loop and a
2415 forwarding structure for notifications from inside svn_fs_verify(). */
2416 if (notify_func)
2417 {
2418 notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
2419
2420 verify_notify = verify_fs_notify_func;
2421 verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
2422 verify_notify_baton->notify_func = notify_func;
2423 verify_notify_baton->notify_baton = notify_baton;
2424 verify_notify_baton->notify
2425 = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
2426 }
2427
2428 /* Verify global metadata and backend-specific data first. */
2429 err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2430 start_rev, end_rev,
2431 verify_notify, verify_notify_baton,
2432 cancel_func, cancel_baton, pool);
2433
2434 if (err && err->apr_err == SVN_ERR_CANCELLED)
2435 {
2436 return svn_error_trace(err);
2437 }
2438 else if (err)
2439 {
2440 SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2441 verify_baton, iterpool));
2442 }
2443
2444 if (!metadata_only)
2445 for (rev = start_rev; rev <= end_rev; rev++)
2446 {
2447 svn_pool_clear(iterpool);
2448
2449 /* Wrapper function to catch the possible errors. */
2450 err = verify_one_revision(fs, rev, notify_func, notify_baton,
2451 start_rev, check_normalization,
2452 cancel_func, cancel_baton,
2453 iterpool);
2454
2455 if (err && err->apr_err == SVN_ERR_CANCELLED)
2456 {
2457 return svn_error_trace(err);
2458 }
2459 else if (err)
2460 {
2461 SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2462 iterpool));
2463 }
2464 else if (notify_func)
2465 {
2466 /* Tell the caller that we're done with this revision. */
2467 notify->revision = rev;
2468 notify_func(notify_baton, notify, iterpool);
2469 }
2470 }
2471
2472 /* We're done. */
2473 if (notify_func)
2474 {
2475 notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2476 notify_func(notify_baton, notify, iterpool);
2477 }
2478
2479 svn_pool_destroy(iterpool);
2480
2481 return SVN_NO_ERROR;
2482 }
2483