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 "svn_private_config.h"
25#include "svn_pools.h"
26#include "svn_error.h"
27#include "svn_fs.h"
28#include "svn_hash.h"
29#include "svn_iter.h"
30#include "svn_repos.h"
31#include "svn_string.h"
32#include "svn_dirent_uri.h"
33#include "svn_path.h"
34#include "svn_time.h"
35#include "svn_checksum.h"
36#include "svn_props.h"
37#include "svn_sorts.h"
38
39#include "private/svn_mergeinfo_private.h"
40#include "private/svn_fs_private.h"
41
42#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
43
44/*----------------------------------------------------------------------*/
45
46
47
48/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
49   store it into a new temporary file *TEMPFILE.  OLDROOT may be NULL,
50   in which case the delta will be computed against an empty file, as
51   per the svn_fs_get_file_delta_stream docstring.  Record the length
52   of the temporary file in *LEN, and rewind the file before
53   returning. */
54static svn_error_t *
55store_delta(apr_file_t **tempfile, svn_filesize_t *len,
56            svn_fs_root_t *oldroot, const char *oldpath,
57            svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
58{
59  svn_stream_t *temp_stream;
60  apr_off_t offset = 0;
61  svn_txdelta_stream_t *delta_stream;
62  svn_txdelta_window_handler_t wh;
63  void *whb;
64
65  /* Create a temporary file and open a stream to it. Note that we need
66     the file handle in order to rewind it. */
67  SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
68                                   svn_io_file_del_on_pool_cleanup,
69                                   pool, pool));
70  temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
71
72  /* Compute the delta and send it to the temporary file. */
73  SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
74                                       newroot, newpath, pool));
75  svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
76                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
77  SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
78
79  /* Get the length of the temporary file and rewind it. */
80  SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
81  *len = offset;
82  offset = 0;
83  return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
84}
85
86
87/*----------------------------------------------------------------------*/
88
89/** An editor which dumps node-data in 'dumpfile format' to a file. **/
90
91/* Look, mom!  No file batons! */
92
93struct edit_baton
94{
95  /* The relpath which implicitly prepends all full paths coming into
96     this editor.  This will almost always be "".  */
97  const char *path;
98
99  /* The stream to dump to. */
100  svn_stream_t *stream;
101
102  /* Send feedback here, if non-NULL */
103  svn_repos_notify_func_t notify_func;
104  void *notify_baton;
105
106  /* The fs revision root, so we can read the contents of paths. */
107  svn_fs_root_t *fs_root;
108  svn_revnum_t current_rev;
109
110  /* The fs, so we can grab historic information if needed. */
111  svn_fs_t *fs;
112
113  /* True if dumped nodes should output deltas instead of full text. */
114  svn_boolean_t use_deltas;
115
116  /* True if this "dump" is in fact a verify. */
117  svn_boolean_t verify;
118
119  /* The first revision dumped in this dumpstream. */
120  svn_revnum_t oldest_dumped_rev;
121
122  /* If not NULL, set to true if any references to revisions older than
123     OLDEST_DUMPED_REV were found in the dumpstream. */
124  svn_boolean_t *found_old_reference;
125
126  /* If not NULL, set to true if any mergeinfo was dumped which contains
127     revisions older than OLDEST_DUMPED_REV. */
128  svn_boolean_t *found_old_mergeinfo;
129
130  /* reusable buffer for writing file contents */
131  char buffer[SVN__STREAM_CHUNK_SIZE];
132  apr_size_t bufsize;
133};
134
135struct dir_baton
136{
137  struct edit_baton *edit_baton;
138  struct dir_baton *parent_dir_baton;
139
140  /* is this directory a new addition to this revision? */
141  svn_boolean_t added;
142
143  /* has this directory been written to the output stream? */
144  svn_boolean_t written_out;
145
146  /* the repository relpath associated with this directory */
147  const char *path;
148
149  /* The comparison repository relpath and revision of this directory.
150     If both of these are valid, use them as a source against which to
151     compare the directory instead of the default comparison source of
152     PATH in the previous revision. */
153  const char *cmp_path;
154  svn_revnum_t cmp_rev;
155
156  /* hash of paths that need to be deleted, though some -might- be
157     replaced.  maps const char * paths to this dir_baton.  (they're
158     full paths, because that's what the editor driver gives us.  but
159     really, they're all within this directory.) */
160  apr_hash_t *deleted_entries;
161
162  /* pool to be used for deleting the hash items */
163  apr_pool_t *pool;
164};
165
166
167/* Make a directory baton to represent the directory was path
168   (relative to EDIT_BATON's path) is PATH.
169
170   CMP_PATH/CMP_REV are the path/revision against which this directory
171   should be compared for changes.  If either is omitted (NULL for the
172   path, SVN_INVALID_REVNUM for the rev), just compare this directory
173   PATH against itself in the previous revision.
174
175   PARENT_DIR_BATON is the directory baton of this directory's parent,
176   or NULL if this is the top-level directory of the edit.  ADDED
177   indicated if this directory is newly added in this revision.
178   Perform all allocations in POOL.  */
179static struct dir_baton *
180make_dir_baton(const char *path,
181               const char *cmp_path,
182               svn_revnum_t cmp_rev,
183               void *edit_baton,
184               void *parent_dir_baton,
185               svn_boolean_t added,
186               apr_pool_t *pool)
187{
188  struct edit_baton *eb = edit_baton;
189  struct dir_baton *pb = parent_dir_baton;
190  struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
191  const char *full_path;
192
193  /* A path relative to nothing?  I don't think so. */
194  SVN_ERR_ASSERT_NO_RETURN(!path || pb);
195
196  /* Construct the full path of this node. */
197  if (pb)
198    full_path = svn_relpath_join(eb->path, path, pool);
199  else
200    full_path = apr_pstrdup(pool, eb->path);
201
202  /* Remove leading slashes from copyfrom paths. */
203  if (cmp_path)
204    cmp_path = svn_relpath_canonicalize(cmp_path, pool);
205
206  new_db->edit_baton = eb;
207  new_db->parent_dir_baton = pb;
208  new_db->path = full_path;
209  new_db->cmp_path = cmp_path;
210  new_db->cmp_rev = cmp_rev;
211  new_db->added = added;
212  new_db->written_out = FALSE;
213  new_db->deleted_entries = apr_hash_make(pool);
214  new_db->pool = pool;
215
216  return new_db;
217}
218
219
220/* This helper is the main "meat" of the editor -- it does all the
221   work of writing a node record.
222
223   Write out a node record for PATH of type KIND under EB->FS_ROOT.
224   ACTION describes what is happening to the node (see enum svn_node_action).
225   Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
226
227   If the node was itself copied, IS_COPY is TRUE and the
228   path/revision of the copy source are in CMP_PATH/CMP_REV.  If
229   IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
230   of a copied subtree.
231  */
232static svn_error_t *
233dump_node(struct edit_baton *eb,
234          const char *path,
235          svn_node_kind_t kind,
236          enum svn_node_action action,
237          svn_boolean_t is_copy,
238          const char *cmp_path,
239          svn_revnum_t cmp_rev,
240          apr_pool_t *pool)
241{
242  svn_stringbuf_t *propstring;
243  svn_filesize_t content_length = 0;
244  apr_size_t len;
245  svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
246  const char *compare_path = path;
247  svn_revnum_t compare_rev = eb->current_rev - 1;
248  svn_fs_root_t *compare_root = NULL;
249  apr_file_t *delta_file = NULL;
250
251  /* Maybe validate the path. */
252  if (eb->verify || eb->notify_func)
253    {
254      svn_error_t *err = svn_fs__path_valid(path, pool);
255
256      if (err)
257        {
258          if (eb->notify_func)
259            {
260              char errbuf[512]; /* ### svn_strerror() magic number  */
261              svn_repos_notify_t *notify;
262              notify = svn_repos_notify_create(svn_repos_notify_warning, pool);
263
264              notify->warning = svn_repos_notify_warning_invalid_fspath;
265              notify->warning_str = apr_psprintf(
266                     pool,
267                     _("E%06d: While validating fspath '%s': %s"),
268                     err->apr_err, path,
269                     svn_err_best_message(err, errbuf, sizeof(errbuf)));
270
271              eb->notify_func(eb->notify_baton, notify, pool);
272            }
273
274          /* Return the error in addition to notifying about it. */
275          if (eb->verify)
276            return svn_error_trace(err);
277          else
278            svn_error_clear(err);
279        }
280    }
281
282  /* Write out metadata headers for this file node. */
283  SVN_ERR(svn_stream_printf(eb->stream, pool,
284                            SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
285                            path));
286  if (kind == svn_node_file)
287    SVN_ERR(svn_stream_puts(eb->stream,
288                            SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
289  else if (kind == svn_node_dir)
290    SVN_ERR(svn_stream_puts(eb->stream,
291                            SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
292
293  /* Remove leading slashes from copyfrom paths. */
294  if (cmp_path)
295    cmp_path = svn_relpath_canonicalize(cmp_path, pool);
296
297  /* Validate the comparison path/rev. */
298  if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
299    {
300      compare_path = cmp_path;
301      compare_rev = cmp_rev;
302    }
303
304  if (action == svn_node_action_change)
305    {
306      SVN_ERR(svn_stream_puts(eb->stream,
307                              SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
308
309      /* either the text or props changed, or possibly both. */
310      SVN_ERR(svn_fs_revision_root(&compare_root,
311                                   svn_fs_root_fs(eb->fs_root),
312                                   compare_rev, pool));
313
314      SVN_ERR(svn_fs_props_changed(&must_dump_props,
315                                   compare_root, compare_path,
316                                   eb->fs_root, path, pool));
317      if (kind == svn_node_file)
318        SVN_ERR(svn_fs_contents_changed(&must_dump_text,
319                                        compare_root, compare_path,
320                                        eb->fs_root, path, pool));
321    }
322  else if (action == svn_node_action_replace)
323    {
324      if (! is_copy)
325        {
326          /* a simple delete+add, implied by a single 'replace' action. */
327          SVN_ERR(svn_stream_puts(eb->stream,
328                                  SVN_REPOS_DUMPFILE_NODE_ACTION
329                                  ": replace\n"));
330
331          /* definitely need to dump all content for a replace. */
332          if (kind == svn_node_file)
333            must_dump_text = TRUE;
334          must_dump_props = TRUE;
335        }
336      else
337        {
338          /* more complex:  delete original, then add-with-history.  */
339
340          /* the path & kind headers have already been printed;  just
341             add a delete action, and end the current record.*/
342          SVN_ERR(svn_stream_puts(eb->stream,
343                                  SVN_REPOS_DUMPFILE_NODE_ACTION
344                                  ": delete\n\n"));
345
346          /* recurse:  print an additional add-with-history record. */
347          SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
348                            is_copy, compare_path, compare_rev, pool));
349
350          /* we can leave this routine quietly now, don't need to dump
351             any content;  that was already done in the second record. */
352          must_dump_text = FALSE;
353          must_dump_props = FALSE;
354        }
355    }
356  else if (action == svn_node_action_delete)
357    {
358      SVN_ERR(svn_stream_puts(eb->stream,
359                              SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
360
361      /* we can leave this routine quietly now, don't need to dump
362         any content. */
363      must_dump_text = FALSE;
364      must_dump_props = FALSE;
365    }
366  else if (action == svn_node_action_add)
367    {
368      SVN_ERR(svn_stream_puts(eb->stream,
369                              SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
370
371      if (! is_copy)
372        {
373          /* Dump all contents for a simple 'add'. */
374          if (kind == svn_node_file)
375            must_dump_text = TRUE;
376          must_dump_props = TRUE;
377        }
378      else
379        {
380          if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
381              && eb->notify_func)
382            {
383              svn_repos_notify_t *notify =
384                    svn_repos_notify_create(svn_repos_notify_warning, pool);
385
386              notify->warning = svn_repos_notify_warning_found_old_reference;
387              notify->warning_str = apr_psprintf(
388                     pool,
389                     _("Referencing data in revision %ld,"
390                       " which is older than the oldest"
391                       " dumped revision (r%ld).  Loading this dump"
392                       " into an empty repository"
393                       " will fail."),
394                     cmp_rev, eb->oldest_dumped_rev);
395              if (eb->found_old_reference)
396                *eb->found_old_reference = TRUE;
397              eb->notify_func(eb->notify_baton, notify, pool);
398            }
399
400          SVN_ERR(svn_stream_printf(eb->stream, pool,
401                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
402                                    ": %ld\n"
403                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
404                                    ": %s\n",
405                                    cmp_rev, cmp_path));
406
407          SVN_ERR(svn_fs_revision_root(&compare_root,
408                                       svn_fs_root_fs(eb->fs_root),
409                                       compare_rev, pool));
410
411          /* Need to decide if the copied node had any extra textual or
412             property mods as well.  */
413          SVN_ERR(svn_fs_props_changed(&must_dump_props,
414                                       compare_root, compare_path,
415                                       eb->fs_root, path, pool));
416          if (kind == svn_node_file)
417            {
418              svn_checksum_t *checksum;
419              const char *hex_digest;
420              SVN_ERR(svn_fs_contents_changed(&must_dump_text,
421                                              compare_root, compare_path,
422                                              eb->fs_root, path, pool));
423
424              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
425                                           compare_root, compare_path,
426                                           FALSE, pool));
427              hex_digest = svn_checksum_to_cstring(checksum, pool);
428              if (hex_digest)
429                SVN_ERR(svn_stream_printf(eb->stream, pool,
430                                      SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
431                                      ": %s\n", hex_digest));
432
433              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
434                                           compare_root, compare_path,
435                                           FALSE, pool));
436              hex_digest = svn_checksum_to_cstring(checksum, pool);
437              if (hex_digest)
438                SVN_ERR(svn_stream_printf(eb->stream, pool,
439                                      SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1
440                                      ": %s\n", hex_digest));
441            }
442        }
443    }
444
445  if ((! must_dump_text) && (! must_dump_props))
446    {
447      /* If we're not supposed to dump text or props, so be it, we can
448         just go home.  However, if either one needs to be dumped,
449         then our dumpstream format demands that at a *minimum*, we
450         see a lone "PROPS-END" as a divider between text and props
451         content within the content-block. */
452      len = 2;
453      return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
454    }
455
456  /*** Start prepping content to dump... ***/
457
458  /* If we are supposed to dump properties, write out a property
459     length header and generate a stringbuf that contains those
460     property values here. */
461  if (must_dump_props)
462    {
463      apr_hash_t *prophash, *oldhash = NULL;
464      apr_size_t proplen;
465      svn_stream_t *propstream;
466
467      SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
468
469      /* If this is a partial dump, then issue a warning if we dump mergeinfo
470         properties that refer to revisions older than the first revision
471         dumped. */
472      if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
473        {
474          svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
475                                                      SVN_PROP_MERGEINFO);
476          if (mergeinfo_str)
477            {
478              svn_mergeinfo_t mergeinfo, old_mergeinfo;
479
480              SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data,
481                                          pool));
482              SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
483                &old_mergeinfo, mergeinfo,
484                eb->oldest_dumped_rev - 1, 0,
485                TRUE, pool, pool));
486              if (apr_hash_count(old_mergeinfo))
487                {
488                  svn_repos_notify_t *notify =
489                    svn_repos_notify_create(svn_repos_notify_warning, pool);
490
491                  notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
492                  notify->warning_str = apr_psprintf(
493                    pool,
494                    _("Mergeinfo referencing revision(s) prior "
495                      "to the oldest dumped revision (r%ld). "
496                      "Loading this dump may result in invalid "
497                      "mergeinfo."),
498                    eb->oldest_dumped_rev);
499
500                  if (eb->found_old_mergeinfo)
501                    *eb->found_old_mergeinfo = TRUE;
502                  eb->notify_func(eb->notify_baton, notify, pool);
503                }
504            }
505        }
506
507      if (eb->use_deltas && compare_root)
508        {
509          /* Fetch the old property hash to diff against and output a header
510             saying that our property contents are a delta. */
511          SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
512                                       pool));
513          SVN_ERR(svn_stream_puts(eb->stream,
514                                  SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n"));
515        }
516      else
517        oldhash = apr_hash_make(pool);
518      propstring = svn_stringbuf_create_ensure(0, pool);
519      propstream = svn_stream_from_stringbuf(propstring, pool);
520      SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
521                                         "PROPS-END", pool));
522      SVN_ERR(svn_stream_close(propstream));
523      proplen = propstring->len;
524      content_length += proplen;
525      SVN_ERR(svn_stream_printf(eb->stream, pool,
526                                SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
527                                ": %" APR_SIZE_T_FMT "\n", proplen));
528    }
529
530  /* If we are supposed to dump text, write out a text length header
531     here, and an MD5 checksum (if available). */
532  if (must_dump_text && (kind == svn_node_file))
533    {
534      svn_checksum_t *checksum;
535      const char *hex_digest;
536      svn_filesize_t textlen;
537
538      if (eb->use_deltas)
539        {
540          /* Compute the text delta now and write it into a temporary
541             file, so that we can find its length.  Output a header
542             saying our text contents are a delta. */
543          SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
544                              compare_path, eb->fs_root, path, pool));
545          SVN_ERR(svn_stream_puts(eb->stream,
546                                  SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n"));
547
548          if (compare_root)
549            {
550              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
551                                           compare_root, compare_path,
552                                           FALSE, pool));
553              hex_digest = svn_checksum_to_cstring(checksum, pool);
554              if (hex_digest)
555                SVN_ERR(svn_stream_printf(eb->stream, pool,
556                                          SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
557                                          ": %s\n", hex_digest));
558
559              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
560                                           compare_root, compare_path,
561                                           FALSE, pool));
562              hex_digest = svn_checksum_to_cstring(checksum, pool);
563              if (hex_digest)
564                SVN_ERR(svn_stream_printf(eb->stream, pool,
565                                      SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1
566                                      ": %s\n", hex_digest));
567            }
568        }
569      else
570        {
571          /* Just fetch the length of the file. */
572          SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
573        }
574
575      content_length += textlen;
576      SVN_ERR(svn_stream_printf(eb->stream, pool,
577                                SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
578                                ": %" SVN_FILESIZE_T_FMT "\n", textlen));
579
580      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
581                                   eb->fs_root, path, FALSE, pool));
582      hex_digest = svn_checksum_to_cstring(checksum, pool);
583      if (hex_digest)
584        SVN_ERR(svn_stream_printf(eb->stream, pool,
585                                  SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
586                                  ": %s\n", hex_digest));
587
588      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
589                                   eb->fs_root, path, FALSE, pool));
590      hex_digest = svn_checksum_to_cstring(checksum, pool);
591      if (hex_digest)
592        SVN_ERR(svn_stream_printf(eb->stream, pool,
593                                  SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1
594                                  ": %s\n", hex_digest));
595    }
596
597  /* 'Content-length:' is the last header before we dump the content,
598     and is the sum of the text and prop contents lengths.  We write
599     this only for the benefit of non-Subversion RFC-822 parsers. */
600  SVN_ERR(svn_stream_printf(eb->stream, pool,
601                            SVN_REPOS_DUMPFILE_CONTENT_LENGTH
602                            ": %" SVN_FILESIZE_T_FMT "\n\n",
603                            content_length));
604
605  /* Dump property content if we're supposed to do so. */
606  if (must_dump_props)
607    {
608      len = propstring->len;
609      SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
610    }
611
612  /* Dump text content */
613  if (must_dump_text && (kind == svn_node_file))
614    {
615      svn_stream_t *contents;
616
617      if (delta_file)
618        {
619          /* Make sure to close the underlying file when the stream is
620             closed. */
621          contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
622        }
623      else
624        SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
625
626      SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
627                               NULL, NULL, pool));
628    }
629
630  len = 2;
631  return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
632}
633
634
635static svn_error_t *
636open_root(void *edit_baton,
637          svn_revnum_t base_revision,
638          apr_pool_t *pool,
639          void **root_baton)
640{
641  *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
642                               edit_baton, NULL, FALSE, pool);
643  return SVN_NO_ERROR;
644}
645
646
647static svn_error_t *
648delete_entry(const char *path,
649             svn_revnum_t revision,
650             void *parent_baton,
651             apr_pool_t *pool)
652{
653  struct dir_baton *pb = parent_baton;
654  const char *mypath = apr_pstrdup(pb->pool, path);
655
656  /* remember this path needs to be deleted. */
657  svn_hash_sets(pb->deleted_entries, mypath, pb);
658
659  return SVN_NO_ERROR;
660}
661
662
663static svn_error_t *
664add_directory(const char *path,
665              void *parent_baton,
666              const char *copyfrom_path,
667              svn_revnum_t copyfrom_rev,
668              apr_pool_t *pool,
669              void **child_baton)
670{
671  struct dir_baton *pb = parent_baton;
672  struct edit_baton *eb = pb->edit_baton;
673  void *val;
674  svn_boolean_t is_copy = FALSE;
675  struct dir_baton *new_db
676    = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
677
678  /* This might be a replacement -- is the path already deleted? */
679  val = svn_hash_gets(pb->deleted_entries, path);
680
681  /* Detect an add-with-history. */
682  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
683
684  /* Dump the node. */
685  SVN_ERR(dump_node(eb, path,
686                    svn_node_dir,
687                    val ? svn_node_action_replace : svn_node_action_add,
688                    is_copy,
689                    is_copy ? copyfrom_path : NULL,
690                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
691                    pool));
692
693  if (val)
694    /* Delete the path, it's now been dumped. */
695    svn_hash_sets(pb->deleted_entries, path, NULL);
696
697  new_db->written_out = TRUE;
698
699  *child_baton = new_db;
700  return SVN_NO_ERROR;
701}
702
703
704static svn_error_t *
705open_directory(const char *path,
706               void *parent_baton,
707               svn_revnum_t base_revision,
708               apr_pool_t *pool,
709               void **child_baton)
710{
711  struct dir_baton *pb = parent_baton;
712  struct edit_baton *eb = pb->edit_baton;
713  struct dir_baton *new_db;
714  const char *cmp_path = NULL;
715  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
716
717  /* If the parent directory has explicit comparison path and rev,
718     record the same for this one. */
719  if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
720    {
721      cmp_path = svn_relpath_join(pb->cmp_path,
722                                  svn_relpath_basename(path, pool), pool);
723      cmp_rev = pb->cmp_rev;
724    }
725
726  new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
727  *child_baton = new_db;
728  return SVN_NO_ERROR;
729}
730
731
732static svn_error_t *
733close_directory(void *dir_baton,
734                apr_pool_t *pool)
735{
736  struct dir_baton *db = dir_baton;
737  struct edit_baton *eb = db->edit_baton;
738  apr_pool_t *subpool = svn_pool_create(pool);
739  int i;
740  apr_array_header_t *sorted_entries;
741
742  /* Sort entries lexically instead of as paths. Even though the entries
743   * are full paths they're all in the same directory (see comment in struct
744   * dir_baton definition). So we really want to sort by basename, in which
745   * case the lexical sort function is more efficient. */
746  sorted_entries = svn_sort__hash(db->deleted_entries,
747                                  svn_sort_compare_items_lexically, pool);
748  for (i = 0; i < sorted_entries->nelts; i++)
749    {
750      const char *path = APR_ARRAY_IDX(sorted_entries, i,
751                                       svn_sort__item_t).key;
752
753      svn_pool_clear(subpool);
754
755      /* By sending 'svn_node_unknown', the Node-kind: header simply won't
756         be written out.  No big deal at all, really.  The loader
757         shouldn't care.  */
758      SVN_ERR(dump_node(eb, path,
759                        svn_node_unknown, svn_node_action_delete,
760                        FALSE, NULL, SVN_INVALID_REVNUM, subpool));
761    }
762
763  svn_pool_destroy(subpool);
764  return SVN_NO_ERROR;
765}
766
767
768static svn_error_t *
769add_file(const char *path,
770         void *parent_baton,
771         const char *copyfrom_path,
772         svn_revnum_t copyfrom_rev,
773         apr_pool_t *pool,
774         void **file_baton)
775{
776  struct dir_baton *pb = parent_baton;
777  struct edit_baton *eb = pb->edit_baton;
778  void *val;
779  svn_boolean_t is_copy = FALSE;
780
781  /* This might be a replacement -- is the path already deleted? */
782  val = svn_hash_gets(pb->deleted_entries, path);
783
784  /* Detect add-with-history. */
785  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
786
787  /* Dump the node. */
788  SVN_ERR(dump_node(eb, path,
789                    svn_node_file,
790                    val ? svn_node_action_replace : svn_node_action_add,
791                    is_copy,
792                    is_copy ? copyfrom_path : NULL,
793                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
794                    pool));
795
796  if (val)
797    /* delete the path, it's now been dumped. */
798    svn_hash_sets(pb->deleted_entries, path, NULL);
799
800  *file_baton = NULL;  /* muhahahaha */
801  return SVN_NO_ERROR;
802}
803
804
805static svn_error_t *
806open_file(const char *path,
807          void *parent_baton,
808          svn_revnum_t ancestor_revision,
809          apr_pool_t *pool,
810          void **file_baton)
811{
812  struct dir_baton *pb = parent_baton;
813  struct edit_baton *eb = pb->edit_baton;
814  const char *cmp_path = NULL;
815  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
816
817  /* If the parent directory has explicit comparison path and rev,
818     record the same for this one. */
819  if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
820    {
821      cmp_path = svn_relpath_join(pb->cmp_path,
822                                  svn_relpath_basename(path, pool), pool);
823      cmp_rev = pb->cmp_rev;
824    }
825
826  SVN_ERR(dump_node(eb, path,
827                    svn_node_file, svn_node_action_change,
828                    FALSE, cmp_path, cmp_rev, pool));
829
830  *file_baton = NULL;  /* muhahahaha again */
831  return SVN_NO_ERROR;
832}
833
834
835static svn_error_t *
836change_dir_prop(void *parent_baton,
837                const char *name,
838                const svn_string_t *value,
839                apr_pool_t *pool)
840{
841  struct dir_baton *db = parent_baton;
842  struct edit_baton *eb = db->edit_baton;
843
844  /* This function is what distinguishes between a directory that is
845     opened to merely get somewhere, vs. one that is opened because it
846     *actually* changed by itself.  */
847  if (! db->written_out)
848    {
849      SVN_ERR(dump_node(eb, db->path,
850                        svn_node_dir, svn_node_action_change,
851                        FALSE, db->cmp_path, db->cmp_rev, pool));
852      db->written_out = TRUE;
853    }
854  return SVN_NO_ERROR;
855}
856
857static svn_error_t *
858fetch_props_func(apr_hash_t **props,
859                 void *baton,
860                 const char *path,
861                 svn_revnum_t base_revision,
862                 apr_pool_t *result_pool,
863                 apr_pool_t *scratch_pool)
864{
865  struct edit_baton *eb = baton;
866  svn_error_t *err;
867  svn_fs_root_t *fs_root;
868
869  if (!SVN_IS_VALID_REVNUM(base_revision))
870    base_revision = eb->current_rev - 1;
871
872  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
873
874  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
875  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
876    {
877      svn_error_clear(err);
878      *props = apr_hash_make(result_pool);
879      return SVN_NO_ERROR;
880    }
881  else if (err)
882    return svn_error_trace(err);
883
884  return SVN_NO_ERROR;
885}
886
887static svn_error_t *
888fetch_kind_func(svn_node_kind_t *kind,
889                void *baton,
890                const char *path,
891                svn_revnum_t base_revision,
892                apr_pool_t *scratch_pool)
893{
894  struct edit_baton *eb = baton;
895  svn_fs_root_t *fs_root;
896
897  if (!SVN_IS_VALID_REVNUM(base_revision))
898    base_revision = eb->current_rev - 1;
899
900  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
901
902  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
903
904  return SVN_NO_ERROR;
905}
906
907static svn_error_t *
908fetch_base_func(const char **filename,
909                void *baton,
910                const char *path,
911                svn_revnum_t base_revision,
912                apr_pool_t *result_pool,
913                apr_pool_t *scratch_pool)
914{
915  struct edit_baton *eb = baton;
916  svn_stream_t *contents;
917  svn_stream_t *file_stream;
918  const char *tmp_filename;
919  svn_error_t *err;
920  svn_fs_root_t *fs_root;
921
922  if (!SVN_IS_VALID_REVNUM(base_revision))
923    base_revision = eb->current_rev - 1;
924
925  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
926
927  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
928  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
929    {
930      svn_error_clear(err);
931      *filename = NULL;
932      return SVN_NO_ERROR;
933    }
934  else if (err)
935    return svn_error_trace(err);
936  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
937                                 svn_io_file_del_on_pool_cleanup,
938                                 scratch_pool, scratch_pool));
939  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
940
941  *filename = apr_pstrdup(result_pool, tmp_filename);
942
943  return SVN_NO_ERROR;
944}
945
946
947static svn_error_t *
948get_dump_editor(const svn_delta_editor_t **editor,
949                void **edit_baton,
950                svn_fs_t *fs,
951                svn_revnum_t to_rev,
952                const char *root_path,
953                svn_stream_t *stream,
954                svn_boolean_t *found_old_reference,
955                svn_boolean_t *found_old_mergeinfo,
956                svn_error_t *(*custom_close_directory)(void *dir_baton,
957                                  apr_pool_t *scratch_pool),
958                svn_repos_notify_func_t notify_func,
959                void *notify_baton,
960                svn_revnum_t oldest_dumped_rev,
961                svn_boolean_t use_deltas,
962                svn_boolean_t verify,
963                apr_pool_t *pool)
964{
965  /* Allocate an edit baton to be stored in every directory baton.
966     Set it up for the directory baton we create here, which is the
967     root baton. */
968  struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
969  svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
970  svn_delta_shim_callbacks_t *shim_callbacks =
971                                svn_delta_shim_callbacks_default(pool);
972
973  /* Set up the edit baton. */
974  eb->stream = stream;
975  eb->notify_func = notify_func;
976  eb->notify_baton = notify_baton;
977  eb->oldest_dumped_rev = oldest_dumped_rev;
978  eb->bufsize = sizeof(eb->buffer);
979  eb->path = apr_pstrdup(pool, root_path);
980  SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
981  eb->fs = fs;
982  eb->current_rev = to_rev;
983  eb->use_deltas = use_deltas;
984  eb->verify = verify;
985  eb->found_old_reference = found_old_reference;
986  eb->found_old_mergeinfo = found_old_mergeinfo;
987
988  /* Set up the editor. */
989  dump_editor->open_root = open_root;
990  dump_editor->delete_entry = delete_entry;
991  dump_editor->add_directory = add_directory;
992  dump_editor->open_directory = open_directory;
993  if (custom_close_directory)
994    dump_editor->close_directory = custom_close_directory;
995  else
996    dump_editor->close_directory = close_directory;
997  dump_editor->change_dir_prop = change_dir_prop;
998  dump_editor->add_file = add_file;
999  dump_editor->open_file = open_file;
1000
1001  *edit_baton = eb;
1002  *editor = dump_editor;
1003
1004  shim_callbacks->fetch_kind_func = fetch_kind_func;
1005  shim_callbacks->fetch_props_func = fetch_props_func;
1006  shim_callbacks->fetch_base_func = fetch_base_func;
1007  shim_callbacks->fetch_baton = eb;
1008
1009  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1010                                   NULL, NULL, shim_callbacks, pool, pool));
1011
1012  return SVN_NO_ERROR;
1013}
1014
1015/*----------------------------------------------------------------------*/
1016
1017/** The main dumping routine, svn_repos_dump_fs. **/
1018
1019
1020/* Helper for svn_repos_dump_fs.
1021
1022   Write a revision record of REV in FS to writable STREAM, using POOL.
1023 */
1024static svn_error_t *
1025write_revision_record(svn_stream_t *stream,
1026                      svn_fs_t *fs,
1027                      svn_revnum_t rev,
1028                      apr_pool_t *pool)
1029{
1030  apr_size_t len;
1031  apr_hash_t *props;
1032  svn_stringbuf_t *encoded_prophash;
1033  apr_time_t timetemp;
1034  svn_string_t *datevalue;
1035  svn_stream_t *propstream;
1036
1037  /* Read the revision props even if we're aren't going to dump
1038     them for verification purposes */
1039  SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1040
1041  /* Run revision date properties through the time conversion to
1042     canonicalize them. */
1043  /* ### Remove this when it is no longer needed for sure. */
1044  datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1045  if (datevalue)
1046    {
1047      SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1048      datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1049                                    pool);
1050      svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1051    }
1052
1053  encoded_prophash = svn_stringbuf_create_ensure(0, pool);
1054  propstream = svn_stream_from_stringbuf(encoded_prophash, pool);
1055  SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool));
1056  SVN_ERR(svn_stream_close(propstream));
1057
1058  /* ### someday write a revision-content-checksum */
1059
1060  SVN_ERR(svn_stream_printf(stream, pool,
1061                            SVN_REPOS_DUMPFILE_REVISION_NUMBER
1062                            ": %ld\n", rev));
1063  SVN_ERR(svn_stream_printf(stream, pool,
1064                            SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
1065                            ": %" APR_SIZE_T_FMT "\n",
1066                            encoded_prophash->len));
1067
1068  /* Write out a regular Content-length header for the benefit of
1069     non-Subversion RFC-822 parsers. */
1070  SVN_ERR(svn_stream_printf(stream, pool,
1071                            SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1072                            ": %" APR_SIZE_T_FMT "\n\n",
1073                            encoded_prophash->len));
1074
1075  len = encoded_prophash->len;
1076  SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
1077
1078  len = 1;
1079  return svn_stream_write(stream, "\n", &len);
1080}
1081
1082
1083
1084/* The main dumper. */
1085svn_error_t *
1086svn_repos_dump_fs3(svn_repos_t *repos,
1087                   svn_stream_t *stream,
1088                   svn_revnum_t start_rev,
1089                   svn_revnum_t end_rev,
1090                   svn_boolean_t incremental,
1091                   svn_boolean_t use_deltas,
1092                   svn_repos_notify_func_t notify_func,
1093                   void *notify_baton,
1094                   svn_cancel_func_t cancel_func,
1095                   void *cancel_baton,
1096                   apr_pool_t *pool)
1097{
1098  const svn_delta_editor_t *dump_editor;
1099  void *dump_edit_baton = NULL;
1100  svn_revnum_t i;
1101  svn_fs_t *fs = svn_repos_fs(repos);
1102  apr_pool_t *subpool = svn_pool_create(pool);
1103  svn_revnum_t youngest;
1104  const char *uuid;
1105  int version;
1106  svn_boolean_t found_old_reference = FALSE;
1107  svn_boolean_t found_old_mergeinfo = FALSE;
1108  svn_repos_notify_t *notify;
1109
1110  /* Determine the current youngest revision of the filesystem. */
1111  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1112
1113  /* Use default vals if necessary. */
1114  if (! SVN_IS_VALID_REVNUM(start_rev))
1115    start_rev = 0;
1116  if (! SVN_IS_VALID_REVNUM(end_rev))
1117    end_rev = youngest;
1118  if (! stream)
1119    stream = svn_stream_empty(pool);
1120
1121  /* Validate the revisions. */
1122  if (start_rev > end_rev)
1123    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1124                             _("Start revision %ld"
1125                               " is greater than end revision %ld"),
1126                             start_rev, end_rev);
1127  if (end_rev > youngest)
1128    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1129                             _("End revision %ld is invalid "
1130                               "(youngest revision is %ld)"),
1131                             end_rev, youngest);
1132  if ((start_rev == 0) && incremental)
1133    incremental = FALSE; /* revision 0 looks the same regardless of
1134                            whether or not this is an incremental
1135                            dump, so just simplify things. */
1136
1137  /* Write out the UUID. */
1138  SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1139
1140  /* If we're not using deltas, use the previous version, for
1141     compatibility with svn 1.0.x. */
1142  version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
1143  if (!use_deltas)
1144    version--;
1145
1146  /* Write out "general" metadata for the dumpfile.  In this case, a
1147     magic header followed by a dumpfile format version. */
1148  SVN_ERR(svn_stream_printf(stream, pool,
1149                            SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
1150                            version));
1151  SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
1152                            ": %s\n\n", uuid));
1153
1154  /* Create a notify object that we can reuse in the loop. */
1155  if (notify_func)
1156    notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
1157                                     pool);
1158
1159  /* Main loop:  we're going to dump revision i.  */
1160  for (i = start_rev; i <= end_rev; i++)
1161    {
1162      svn_revnum_t from_rev, to_rev;
1163      svn_fs_root_t *to_root;
1164      svn_boolean_t use_deltas_for_rev;
1165
1166      svn_pool_clear(subpool);
1167
1168      /* Check for cancellation. */
1169      if (cancel_func)
1170        SVN_ERR(cancel_func(cancel_baton));
1171
1172      /* Special-case the initial revision dump: it needs to contain
1173         *all* nodes, because it's the foundation of all future
1174         revisions in the dumpfile. */
1175      if ((i == start_rev) && (! incremental))
1176        {
1177          /* Special-special-case a dump of revision 0. */
1178          if (i == 0)
1179            {
1180              /* Just write out the one revision 0 record and move on.
1181                 The parser might want to use its properties. */
1182              SVN_ERR(write_revision_record(stream, fs, 0, subpool));
1183              to_rev = 0;
1184              goto loop_end;
1185            }
1186
1187          /* Compare START_REV to revision 0, so that everything
1188             appears to be added.  */
1189          from_rev = 0;
1190          to_rev = i;
1191        }
1192      else
1193        {
1194          /* In the normal case, we want to compare consecutive revs. */
1195          from_rev = i - 1;
1196          to_rev = i;
1197        }
1198
1199      /* Write the revision record. */
1200      SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
1201
1202      /* Fetch the editor which dumps nodes to a file.  Regardless of
1203         what we've been told, don't use deltas for the first rev of a
1204         non-incremental dump. */
1205      use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
1206      SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
1207                              "", stream, &found_old_reference,
1208                              &found_old_mergeinfo, NULL,
1209                              notify_func, notify_baton,
1210                              start_rev, use_deltas_for_rev, FALSE, subpool));
1211
1212      /* Drive the editor in one way or another. */
1213      SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
1214
1215      /* If this is the first revision of a non-incremental dump,
1216         we're in for a full tree dump.  Otherwise, we want to simply
1217         replay the revision.  */
1218      if ((i == start_rev) && (! incremental))
1219        {
1220          svn_fs_root_t *from_root;
1221          SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
1222          SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
1223                                       to_root, "",
1224                                       dump_editor, dump_edit_baton,
1225                                       NULL,
1226                                       NULL,
1227                                       FALSE, /* don't send text-deltas */
1228                                       svn_depth_infinity,
1229                                       FALSE, /* don't send entry props */
1230                                       FALSE, /* don't ignore ancestry */
1231                                       subpool));
1232        }
1233      else
1234        {
1235          SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1236                                    dump_editor, dump_edit_baton,
1237                                    NULL, NULL, subpool));
1238
1239          /* While our editor close_edit implementation is a no-op, we still
1240             do this for completeness. */
1241          SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
1242        }
1243
1244    loop_end:
1245      if (notify_func)
1246        {
1247          notify->revision = to_rev;
1248          notify_func(notify_baton, notify, subpool);
1249        }
1250    }
1251
1252  if (notify_func)
1253    {
1254      /* Did we issue any warnings about references to revisions older than
1255         the oldest dumped revision?  If so, then issue a final generic
1256         warning, since the inline warnings already issued might easily be
1257         missed. */
1258
1259      notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
1260      notify_func(notify_baton, notify, subpool);
1261
1262      if (found_old_reference)
1263        {
1264          notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1265
1266          notify->warning = svn_repos_notify_warning_found_old_reference;
1267          notify->warning_str = _("The range of revisions dumped "
1268                                  "contained references to "
1269                                  "copy sources outside that "
1270                                  "range.");
1271          notify_func(notify_baton, notify, subpool);
1272        }
1273
1274      /* Ditto if we issued any warnings about old revisions referenced
1275         in dumped mergeinfo. */
1276      if (found_old_mergeinfo)
1277        {
1278          notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1279
1280          notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
1281          notify->warning_str = _("The range of revisions dumped "
1282                                  "contained mergeinfo "
1283                                  "which reference revisions outside "
1284                                  "that range.");
1285          notify_func(notify_baton, notify, subpool);
1286        }
1287    }
1288
1289  svn_pool_destroy(subpool);
1290
1291  return SVN_NO_ERROR;
1292}
1293
1294
1295/*----------------------------------------------------------------------*/
1296
1297/* verify, based on dump */
1298
1299
1300/* Creating a new revision that changes /A/B/E/bravo means creating new
1301   directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
1302   each entry not changed in the new revision a link back to the entry in a
1303   previous revision.  svn_repos_replay()ing a revision does not verify that
1304   those links are correct.
1305
1306   For paths actually changed in the revision we verify, we get directory
1307   contents or file length twice: once in the dump editor, and once here.
1308   We could create a new verify baton, store in it the changed paths, and
1309   skip those here, but that means building an entire wrapper editor and
1310   managing two levels of batons.  The impact from checking these entries
1311   twice should be minimal, while the code to avoid it is not.
1312*/
1313
1314static svn_error_t *
1315verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
1316                       void *val, apr_pool_t *pool)
1317{
1318  struct dir_baton *db = baton;
1319  svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
1320  char *path = svn_relpath_join(db->path, (const char *)key, pool);
1321  apr_hash_t *dirents;
1322  svn_filesize_t len;
1323
1324  /* since we can't access the directory entries directly by their ID,
1325     we need to navigate from the FS_ROOT to them (relatively expensive
1326     because we may start at a never rev than the last change to node). */
1327  switch (dirent->kind) {
1328  case svn_node_dir:
1329    /* Getting this directory's contents is enough to ensure that our
1330       link to it is correct. */
1331    SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool));
1332    break;
1333  case svn_node_file:
1334    /* Getting this file's size is enough to ensure that our link to it
1335       is correct. */
1336    SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool));
1337    break;
1338  default:
1339    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1340                             _("Unexpected node kind %d for '%s'"),
1341                             dirent->kind, path);
1342  }
1343
1344  return SVN_NO_ERROR;
1345}
1346
1347static svn_error_t *
1348verify_close_directory(void *dir_baton,
1349                apr_pool_t *pool)
1350{
1351  struct dir_baton *db = dir_baton;
1352  apr_hash_t *dirents;
1353  SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
1354                             db->path, pool));
1355  SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
1356                            dir_baton, pool));
1357  return close_directory(dir_baton, pool);
1358}
1359
1360/* Baton type used for forwarding notifications from FS API to REPOS API. */
1361struct verify_fs2_notify_func_baton_t
1362{
1363   /* notification function to call (must not be NULL) */
1364   svn_repos_notify_func_t notify_func;
1365
1366   /* baton to use for it */
1367   void *notify_baton;
1368
1369   /* type of notification to send (we will simply plug in the revision) */
1370   svn_repos_notify_t *notify;
1371};
1372
1373/* Forward the notification to BATON. */
1374static void
1375verify_fs2_notify_func(svn_revnum_t revision,
1376                       void *baton,
1377                       apr_pool_t *pool)
1378{
1379  struct verify_fs2_notify_func_baton_t *notify_baton = baton;
1380
1381  notify_baton->notify->revision = revision;
1382  notify_baton->notify_func(notify_baton->notify_baton,
1383                            notify_baton->notify, pool);
1384}
1385
1386svn_error_t *
1387svn_repos_verify_fs2(svn_repos_t *repos,
1388                     svn_revnum_t start_rev,
1389                     svn_revnum_t end_rev,
1390                     svn_repos_notify_func_t notify_func,
1391                     void *notify_baton,
1392                     svn_cancel_func_t cancel_func,
1393                     void *cancel_baton,
1394                     apr_pool_t *pool)
1395{
1396  svn_fs_t *fs = svn_repos_fs(repos);
1397  svn_revnum_t youngest;
1398  svn_revnum_t rev;
1399  apr_pool_t *iterpool = svn_pool_create(pool);
1400  svn_repos_notify_t *notify;
1401  svn_fs_progress_notify_func_t verify_notify = NULL;
1402  struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL;
1403
1404  /* Determine the current youngest revision of the filesystem. */
1405  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1406
1407  /* Use default vals if necessary. */
1408  if (! SVN_IS_VALID_REVNUM(start_rev))
1409    start_rev = 0;
1410  if (! SVN_IS_VALID_REVNUM(end_rev))
1411    end_rev = youngest;
1412
1413  /* Validate the revisions. */
1414  if (start_rev > end_rev)
1415    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1416                             _("Start revision %ld"
1417                               " is greater than end revision %ld"),
1418                             start_rev, end_rev);
1419  if (end_rev > youngest)
1420    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1421                             _("End revision %ld is invalid "
1422                               "(youngest revision is %ld)"),
1423                             end_rev, youngest);
1424
1425  /* Create a notify object that we can reuse within the loop and a
1426     forwarding structure for notifications from inside svn_fs_verify(). */
1427  if (notify_func)
1428    {
1429      notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end,
1430                                       pool);
1431
1432      verify_notify = verify_fs2_notify_func;
1433      verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
1434      verify_notify_baton->notify_func = notify_func;
1435      verify_notify_baton->notify_baton = notify_baton;
1436      verify_notify_baton->notify
1437        = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
1438    }
1439
1440  /* Verify global metadata and backend-specific data first. */
1441  SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
1442                        start_rev, end_rev,
1443                        verify_notify, verify_notify_baton,
1444                        cancel_func, cancel_baton, pool));
1445
1446  for (rev = start_rev; rev <= end_rev; rev++)
1447    {
1448      const svn_delta_editor_t *dump_editor;
1449      void *dump_edit_baton;
1450      const svn_delta_editor_t *cancel_editor;
1451      void *cancel_edit_baton;
1452      svn_fs_root_t *to_root;
1453      apr_hash_t *props;
1454
1455      svn_pool_clear(iterpool);
1456
1457      /* Get cancellable dump editor, but with our close_directory handler. */
1458      SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
1459                              fs, rev, "",
1460                              svn_stream_empty(iterpool),
1461                              NULL, NULL,
1462                              verify_close_directory,
1463                              notify_func, notify_baton,
1464                              start_rev,
1465                              FALSE, TRUE, /* use_deltas, verify */
1466                              iterpool));
1467      SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1468                                                dump_editor, dump_edit_baton,
1469                                                &cancel_editor,
1470                                                &cancel_edit_baton,
1471                                                iterpool));
1472
1473      SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
1474      SVN_ERR(svn_fs_verify_root(to_root, iterpool));
1475
1476      SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1477                                cancel_editor, cancel_edit_baton,
1478                                NULL, NULL, iterpool));
1479      /* While our editor close_edit implementation is a no-op, we still
1480         do this for completeness. */
1481      SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool));
1482
1483      SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool));
1484
1485      if (notify_func)
1486        {
1487          notify->revision = rev;
1488          notify_func(notify_baton, notify, iterpool);
1489        }
1490    }
1491
1492  /* We're done. */
1493  if (notify_func)
1494    {
1495      notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
1496      notify_func(notify_baton, notify, iterpool);
1497    }
1498
1499  /* Per-backend verification. */
1500  svn_pool_destroy(iterpool);
1501
1502  return SVN_NO_ERROR;
1503}
1504