]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/low_level.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / low_level.c
1 /* low_level.c --- low level r/w access to fs_x file structures
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include "svn_private_config.h"
24 #include "svn_hash.h"
25 #include "svn_pools.h"
26 #include "svn_sorts.h"
27 #include "private/svn_sorts_private.h"
28 #include "private/svn_string_private.h"
29 #include "private/svn_subr_private.h"
30 #include "private/svn_fspath.h"
31
32 #include "../libsvn_fs/fs-loader.h"
33
34 #include "low_level.h"
35 #include "util.h"
36 #include "pack.h"
37 #include "cached_data.h"
38
39 /* Headers used to describe node-revision in the revision file. */
40 #define HEADER_ID          "id"
41 #define HEADER_NODE        "node"
42 #define HEADER_COPY        "copy"
43 #define HEADER_TYPE        "type"
44 #define HEADER_COUNT       "count"
45 #define HEADER_PROPS       "props"
46 #define HEADER_TEXT        "text"
47 #define HEADER_CPATH       "cpath"
48 #define HEADER_PRED        "pred"
49 #define HEADER_COPYFROM    "copyfrom"
50 #define HEADER_COPYROOT    "copyroot"
51 #define HEADER_MINFO_HERE  "minfo-here"
52 #define HEADER_MINFO_CNT   "minfo-cnt"
53
54 /* Kinds that a change can be. */
55 #define ACTION_MODIFY      "modify"
56 #define ACTION_ADD         "add"
57 #define ACTION_DELETE      "delete"
58 #define ACTION_REPLACE     "replace"
59 #define ACTION_RESET       "reset"
60
61 /* True and False flags. */
62 #define FLAG_TRUE          "true"
63 #define FLAG_FALSE         "false"
64
65 /* Kinds of representation. */
66 #define REP_DELTA          "DELTA"
67
68 /* An arbitrary maximum path length, so clients can't run us out of memory
69  * by giving us arbitrarily large paths. */
70 #define FSX_MAX_PATH_LEN 4096
71
72 /* The 256 is an arbitrary size large enough to hold the node id and the
73  * various flags. */
74 #define MAX_CHANGE_LINE_LEN FSX_MAX_PATH_LEN + 256
75
76 /* Convert the C string in *TEXT to a revision number and return it in *REV.
77  * Overflows, negative values other than -1 and terminating characters other
78  * than 0x20 or 0x0 will cause an error.  Set *TEXT to the first char after
79  * the initial separator or to EOS.
80  */
81 static svn_error_t *
82 parse_revnum(svn_revnum_t *rev,
83              const char **text)
84 {
85   const char *string = *text;
86   if ((string[0] == '-') && (string[1] == '1'))
87     {
88       *rev = SVN_INVALID_REVNUM;
89       string += 2;
90     }
91   else
92     {
93       SVN_ERR(svn_revnum_parse(rev, string, &string));
94     }
95
96   if (*string == ' ')
97     ++string;
98   else if (*string != '\0')
99     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
100                             _("Invalid character in revision number"));
101
102   *text = string;
103   return SVN_NO_ERROR;
104 }
105
106 svn_error_t *
107 svn_fs_x__parse_footer(apr_off_t *l2p_offset,
108                        svn_checksum_t **l2p_checksum,
109                        apr_off_t *p2l_offset,
110                        svn_checksum_t **p2l_checksum,
111                        svn_stringbuf_t *footer,
112                        svn_revnum_t rev,
113                        apr_pool_t *result_pool)
114 {
115   apr_int64_t val;
116   char *last_str = footer->data;
117
118   /* Get the L2P offset. */
119   const char *str = svn_cstring_tokenize(" ", &last_str);
120   if (str == NULL)
121     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
122                             _("Invalid revision footer"));
123
124   SVN_ERR(svn_cstring_atoi64(&val, str));
125   *l2p_offset = (apr_off_t)val;
126
127   /* Get the L2P checksum. */
128   str = svn_cstring_tokenize(" ", &last_str);
129   if (str == NULL)
130     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
131                             _("Invalid revision footer"));
132
133   SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
134                                  result_pool));
135
136   /* Get the P2L offset. */
137   str = svn_cstring_tokenize(" ", &last_str);
138   if (str == NULL)
139     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
140                             _("Invalid revision footer"));
141
142   SVN_ERR(svn_cstring_atoi64(&val, str));
143   *p2l_offset = (apr_off_t)val;
144
145   /* Get the P2L checksum. */
146   str = svn_cstring_tokenize(" ", &last_str);
147   if (str == NULL)
148     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
149                             _("Invalid revision footer"));
150
151   SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
152                                  result_pool));
153
154   return SVN_NO_ERROR;
155 }
156
157 svn_stringbuf_t *
158 svn_fs_x__unparse_footer(apr_off_t l2p_offset,
159                          svn_checksum_t *l2p_checksum,
160                          apr_off_t p2l_offset,
161                          svn_checksum_t *p2l_checksum,
162                          apr_pool_t *result_pool,
163                          apr_pool_t *scratch_pool)
164 {
165   return svn_stringbuf_createf(result_pool,
166                                "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
167                                l2p_offset,
168                                svn_checksum_to_cstring(l2p_checksum,
169                                                        scratch_pool),
170                                p2l_offset,
171                                svn_checksum_to_cstring(p2l_checksum,
172                                                        scratch_pool));
173 }
174
175 /* Given a revision file FILE that has been pre-positioned at the
176    beginning of a Node-Rev header block, read in that header block and
177    store it in the apr_hash_t HEADERS.  All allocations will be from
178    RESULT_POOL. */
179 static svn_error_t *
180 read_header_block(apr_hash_t **headers,
181                   svn_stream_t *stream,
182                   apr_pool_t *result_pool)
183 {
184   *headers = svn_hash__make(result_pool);
185
186   while (1)
187     {
188       svn_stringbuf_t *header_str;
189       const char *name, *value;
190       apr_size_t i = 0;
191       apr_size_t name_len;
192       svn_boolean_t eof;
193
194       SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
195                                   result_pool));
196
197       if (eof || header_str->len == 0)
198         break; /* end of header block */
199
200       while (header_str->data[i] != ':')
201         {
202           if (header_str->data[i] == '\0')
203             return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
204                                      _("Found malformed header '%s' in "
205                                        "revision file"),
206                                      header_str->data);
207           i++;
208         }
209
210       /* Create a 'name' string and point to it. */
211       header_str->data[i] = '\0';
212       name = header_str->data;
213       name_len = i;
214
215       /* Check if we have enough data to parse. */
216       if (i + 2 > header_str->len)
217         {
218           /* Restore the original line for the error. */
219           header_str->data[i] = ':';
220           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
221                                    _("Found malformed header '%s' in "
222                                      "revision file"),
223                                    header_str->data);
224         }
225
226       /* Skip over the NULL byte and the space following it. */
227       i += 2;
228
229       value = header_str->data + i;
230
231       /* header_str is safely in our pool, so we can use bits of it as
232          key and value. */
233       apr_hash_set(*headers, name, name_len, value);
234     }
235
236   return SVN_NO_ERROR;
237 }
238
239 svn_error_t *
240 svn_fs_x__parse_representation(svn_fs_x__representation_t **rep_p,
241                                svn_stringbuf_t *text,
242                                apr_pool_t *result_pool,
243                                apr_pool_t *scratch_pool)
244 {
245   svn_fs_x__representation_t *rep;
246   char *str;
247   apr_int64_t val;
248   char *string = text->data;
249   svn_checksum_t *checksum;
250
251   rep = apr_pcalloc(result_pool, sizeof(*rep));
252   *rep_p = rep;
253
254   str = svn_cstring_tokenize(" ", &string);
255   if (str == NULL)
256     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
257                             _("Malformed text representation offset line in node-rev"));
258
259   SVN_ERR(svn_cstring_atoi64(&rep->id.change_set, str));
260
261   /* while in transactions, it is legal to simply write "-1" */
262   if (rep->id.change_set == -1)
263     return SVN_NO_ERROR;
264
265   str = svn_cstring_tokenize(" ", &string);
266   if (str == NULL)
267     {
268       if (rep->id.change_set == SVN_FS_X__INVALID_CHANGE_SET)
269         return SVN_NO_ERROR;
270
271       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
272                               _("Malformed text representation offset line in node-rev"));
273     }
274
275   SVN_ERR(svn_cstring_atoi64(&val, str));
276   rep->id.number = (apr_off_t)val;
277
278   str = svn_cstring_tokenize(" ", &string);
279   if (str == NULL)
280     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
281                             _("Malformed text representation offset line in node-rev"));
282
283   SVN_ERR(svn_cstring_atoi64(&val, str));
284   rep->size = (svn_filesize_t)val;
285
286   str = svn_cstring_tokenize(" ", &string);
287   if (str == NULL)
288     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
289                             _("Malformed text representation offset line in node-rev"));
290
291   SVN_ERR(svn_cstring_atoi64(&val, str));
292   rep->expanded_size = (svn_filesize_t)val;
293
294   /* Read in the MD5 hash. */
295   str = svn_cstring_tokenize(" ", &string);
296   if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
297     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
298                             _("Malformed text representation offset line in node-rev"));
299
300   SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
301                                  scratch_pool));
302   if (checksum)
303     memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
304
305   /* The remaining fields are only used for formats >= 4, so check that. */
306   str = svn_cstring_tokenize(" ", &string);
307   if (str == NULL)
308     return SVN_NO_ERROR;
309
310   /* Read the SHA1 hash. */
311   if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
312     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
313                             _("Malformed text representation offset line in node-rev"));
314
315   SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
316                                  scratch_pool));
317   rep->has_sha1 = checksum != NULL;
318   if (checksum)
319     memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
320
321   return SVN_NO_ERROR;
322 }
323
324 /* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
325    and adding an error message. */
326 static svn_error_t *
327 read_rep_offsets(svn_fs_x__representation_t **rep_p,
328                  char *string,
329                  const svn_fs_x__id_t *noderev_id,
330                  apr_pool_t *result_pool,
331                  apr_pool_t *scratch_pool)
332 {
333   svn_error_t *err
334     = svn_fs_x__parse_representation(rep_p,
335                                      svn_stringbuf_create_wrap(string,
336                                                                scratch_pool),
337                                      result_pool,
338                                      scratch_pool);
339   if (err)
340     {
341       const svn_string_t *id_unparsed;
342       const char *where;
343
344       id_unparsed = svn_fs_x__id_unparse(noderev_id, scratch_pool);
345       where = apr_psprintf(scratch_pool,
346                            _("While reading representation offsets "
347                              "for node-revision '%s':"),
348                            id_unparsed->data);
349
350       return svn_error_quick_wrap(err, where);
351     }
352
353   return SVN_NO_ERROR;
354 }
355
356 /* If PATH needs to be escaped, return an escaped version of it, allocated
357  * from RESULT_POOL. Otherwise, return PATH directly. */
358 static const char *
359 auto_escape_path(const char *path,
360                  apr_pool_t *result_pool)
361 {
362   apr_size_t len = strlen(path);
363   apr_size_t i;
364   const char esc = '\x1b';
365
366   for (i = 0; i < len; ++i)
367     if (path[i] < ' ')
368       {
369         svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len,
370                                                                result_pool);
371         for (i = 0; i < len; ++i)
372           if (path[i] < ' ')
373             {
374               svn_stringbuf_appendbyte(escaped, esc);
375               svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1);
376             }
377           else
378             {
379               svn_stringbuf_appendbyte(escaped, path[i]);
380             }
381
382         return escaped->data;
383       }
384
385    return path;
386 }
387
388 /* If PATH has been escaped, return the un-escaped version of it, allocated
389  * from RESULT_POOL. Otherwise, return PATH directly. */
390 static const char *
391 auto_unescape_path(const char *path,
392                    apr_pool_t *result_pool)
393 {
394   const char esc = '\x1b';
395   if (strchr(path, esc))
396     {
397       apr_size_t len = strlen(path);
398       apr_size_t i;
399
400       svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len,
401                                                                result_pool);
402       for (i = 0; i < len; ++i)
403         if (path[i] == esc)
404           svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A');
405         else
406           svn_stringbuf_appendbyte(unescaped, path[i]);
407
408       return unescaped->data;
409     }
410
411    return path;
412 }
413
414 /* Find entry HEADER_NAME in HEADERS and parse its value into *ID. */
415 static svn_error_t *
416 read_id_part(svn_fs_x__id_t *id,
417              apr_hash_t *headers,
418              const char *header_name)
419 {
420   const char *value = svn_hash_gets(headers, header_name);
421   if (value == NULL)
422     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
423                              _("Missing %s field in node-rev"),
424                              header_name);
425
426   SVN_ERR(svn_fs_x__id_parse(id, value));
427   return SVN_NO_ERROR;
428 }
429
430 svn_error_t *
431 svn_fs_x__read_noderev(svn_fs_x__noderev_t **noderev_p,
432                        svn_stream_t *stream,
433                        apr_pool_t *result_pool,
434                        apr_pool_t *scratch_pool)
435 {
436   apr_hash_t *headers;
437   svn_fs_x__noderev_t *noderev;
438   char *value;
439   const char *noderev_id;
440
441   SVN_ERR(read_header_block(&headers, stream, scratch_pool));
442   SVN_ERR(svn_stream_close(stream));
443
444   noderev = apr_pcalloc(result_pool, sizeof(*noderev));
445
446   /* for error messages later */
447   noderev_id = svn_hash_gets(headers, HEADER_ID);
448
449   /* Read the node-rev id. */
450   SVN_ERR(read_id_part(&noderev->noderev_id, headers, HEADER_ID));
451   SVN_ERR(read_id_part(&noderev->node_id, headers, HEADER_NODE));
452   SVN_ERR(read_id_part(&noderev->copy_id, headers, HEADER_COPY));
453
454   /* Read the type. */
455   value = svn_hash_gets(headers, HEADER_TYPE);
456
457   if ((value == NULL) ||
458       (   strcmp(value, SVN_FS_X__KIND_FILE)
459        && strcmp(value, SVN_FS_X__KIND_DIR)))
460     /* ### s/kind/type/ */
461     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
462                              _("Missing kind field in node-rev '%s'"),
463                              noderev_id);
464
465   noderev->kind = (strcmp(value, SVN_FS_X__KIND_FILE) == 0)
466                 ? svn_node_file
467                 : svn_node_dir;
468
469   /* Read the 'count' field. */
470   value = svn_hash_gets(headers, HEADER_COUNT);
471   if (value)
472     SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
473   else
474     noderev->predecessor_count = 0;
475
476   /* Get the properties location. */
477   value = svn_hash_gets(headers, HEADER_PROPS);
478   if (value)
479     {
480       SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
481                                &noderev->noderev_id, result_pool,
482                                scratch_pool));
483     }
484
485   /* Get the data location. */
486   value = svn_hash_gets(headers, HEADER_TEXT);
487   if (value)
488     {
489       SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
490                                &noderev->noderev_id, result_pool,
491                                scratch_pool));
492     }
493
494   /* Get the created path. */
495   value = svn_hash_gets(headers, HEADER_CPATH);
496   if (value == NULL)
497     {
498       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
499                                _("Missing cpath field in node-rev '%s'"),
500                                noderev_id);
501     }
502   else
503     {
504       if (!svn_fspath__is_canonical(value))
505         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
506                             _("Non-canonical cpath field in node-rev '%s'"),
507                             noderev_id);
508
509       noderev->created_path = auto_unescape_path(apr_pstrdup(result_pool,
510                                                               value),
511                                                  result_pool);
512     }
513
514   /* Get the predecessor ID. */
515   value = svn_hash_gets(headers, HEADER_PRED);
516   if (value)
517     SVN_ERR(svn_fs_x__id_parse(&noderev->predecessor_id, value));
518   else
519     svn_fs_x__id_reset(&noderev->predecessor_id);
520
521   /* Get the copyroot. */
522   value = svn_hash_gets(headers, HEADER_COPYROOT);
523   if (value == NULL)
524     {
525       noderev->copyroot_path = noderev->created_path;
526       noderev->copyroot_rev
527         = svn_fs_x__get_revnum(noderev->noderev_id.change_set);
528     }
529   else
530     {
531       SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
532
533       if (!svn_fspath__is_canonical(value))
534         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
535                                  _("Malformed copyroot line in node-rev '%s'"),
536                                  noderev_id);
537       noderev->copyroot_path = auto_unescape_path(apr_pstrdup(result_pool,
538                                                               value),
539                                                   result_pool);
540     }
541
542   /* Get the copyfrom. */
543   value = svn_hash_gets(headers, HEADER_COPYFROM);
544   if (value == NULL)
545     {
546       noderev->copyfrom_path = NULL;
547       noderev->copyfrom_rev = SVN_INVALID_REVNUM;
548     }
549   else
550     {
551       SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
552
553       if (*value == 0)
554         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
555                                  _("Malformed copyfrom line in node-rev '%s'"),
556                                  noderev_id);
557       noderev->copyfrom_path = auto_unescape_path(apr_pstrdup(result_pool,
558                                                               value),
559                                                   result_pool);
560     }
561
562   /* Get the mergeinfo count. */
563   value = svn_hash_gets(headers, HEADER_MINFO_CNT);
564   if (value)
565     SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
566   else
567     noderev->mergeinfo_count = 0;
568
569   /* Get whether *this* node has mergeinfo. */
570   value = svn_hash_gets(headers, HEADER_MINFO_HERE);
571   noderev->has_mergeinfo = (value != NULL);
572
573   *noderev_p = noderev;
574
575   return SVN_NO_ERROR;
576 }
577
578 /* Return a textual representation of the DIGEST of given KIND.
579  * If IS_NULL is TRUE, no digest is available.
580  * Allocate the result in RESULT_POOL.
581  */
582 static const char *
583 format_digest(const unsigned char *digest,
584               svn_checksum_kind_t kind,
585               svn_boolean_t is_null,
586               apr_pool_t *result_pool)
587 {
588   svn_checksum_t checksum;
589   checksum.digest = digest;
590   checksum.kind = kind;
591
592   if (is_null)
593     return "(null)";
594
595   return svn_checksum_to_cstring_display(&checksum, result_pool);
596 }
597
598 svn_stringbuf_t *
599 svn_fs_x__unparse_representation(svn_fs_x__representation_t *rep,
600                                  svn_boolean_t mutable_rep_truncated,
601                                  apr_pool_t *result_pool,
602                                  apr_pool_t *scratch_pool)
603 {
604   if (!rep->has_sha1)
605     return svn_stringbuf_createf
606             (result_pool,
607              "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
608              " %" SVN_FILESIZE_T_FMT " %s",
609              rep->id.change_set, rep->id.number, rep->size,
610              rep->expanded_size,
611              format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
612                            scratch_pool));
613
614   return svn_stringbuf_createf
615           (result_pool,
616            "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
617            " %" SVN_FILESIZE_T_FMT " %s %s",
618            rep->id.change_set, rep->id.number, rep->size,
619            rep->expanded_size,
620            format_digest(rep->md5_digest, svn_checksum_md5,
621                          FALSE, scratch_pool),
622            format_digest(rep->sha1_digest, svn_checksum_sha1,
623                          !rep->has_sha1, scratch_pool));
624 }
625
626
627 svn_error_t *
628 svn_fs_x__write_noderev(svn_stream_t *outfile,
629                         svn_fs_x__noderev_t *noderev,
630                         apr_pool_t *scratch_pool)
631 {
632   svn_string_t *str_id;
633
634   str_id = svn_fs_x__id_unparse(&noderev->noderev_id, scratch_pool);
635   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
636                             str_id->data));
637   str_id = svn_fs_x__id_unparse(&noderev->node_id, scratch_pool);
638   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_NODE ": %s\n",
639                             str_id->data));
640   str_id = svn_fs_x__id_unparse(&noderev->copy_id, scratch_pool);
641   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPY ": %s\n",
642                             str_id->data));
643
644   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
645                             (noderev->kind == svn_node_file) ?
646                             SVN_FS_X__KIND_FILE : SVN_FS_X__KIND_DIR));
647
648   if (svn_fs_x__id_used(&noderev->predecessor_id))
649     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
650                               svn_fs_x__id_unparse(&noderev->predecessor_id,
651                                                    scratch_pool)->data));
652
653   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
654                             noderev->predecessor_count));
655
656   if (noderev->data_rep)
657     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
658                               svn_fs_x__unparse_representation
659                                 (noderev->data_rep,
660                                  noderev->kind == svn_node_dir,
661                                  scratch_pool, scratch_pool)->data));
662
663   if (noderev->prop_rep)
664     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
665                               svn_fs_x__unparse_representation
666                                 (noderev->prop_rep,
667                                  TRUE, scratch_pool, scratch_pool)->data));
668
669   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
670                             auto_escape_path(noderev->created_path,
671                                              scratch_pool)));
672
673   if (noderev->copyfrom_path)
674     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
675                               " %s\n",
676                               noderev->copyfrom_rev,
677                               auto_escape_path(noderev->copyfrom_path,
678                                                scratch_pool)));
679
680   if (   (   noderev->copyroot_rev
681            != svn_fs_x__get_revnum(noderev->noderev_id.change_set))
682       || (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
683     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
684                               " %s\n",
685                               noderev->copyroot_rev,
686                               auto_escape_path(noderev->copyroot_path,
687                                                scratch_pool)));
688
689   if (noderev->mergeinfo_count > 0)
690     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT ": %"
691                               APR_INT64_T_FMT "\n",
692                               noderev->mergeinfo_count));
693
694   if (noderev->has_mergeinfo)
695     SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
696
697   return svn_stream_puts(outfile, "\n");
698 }
699
700 svn_error_t *
701 svn_fs_x__read_rep_header(svn_fs_x__rep_header_t **header,
702                           svn_stream_t *stream,
703                           apr_pool_t *result_pool,
704                           apr_pool_t *scratch_pool)
705 {
706   svn_stringbuf_t *buffer;
707   char *str, *last_str;
708   apr_int64_t val;
709   svn_boolean_t eol = FALSE;
710
711   SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
712
713   *header = apr_pcalloc(result_pool, sizeof(**header));
714   (*header)->header_size = buffer->len + 1;
715   if (strcmp(buffer->data, REP_DELTA) == 0)
716     {
717       /* This is a delta against the empty stream. */
718       (*header)->type = svn_fs_x__rep_self_delta;
719       return SVN_NO_ERROR;
720     }
721
722   (*header)->type = svn_fs_x__rep_delta;
723
724   /* We have hopefully a DELTA vs. a non-empty base revision. */
725   last_str = buffer->data;
726   str = svn_cstring_tokenize(" ", &last_str);
727   if (! str || (strcmp(str, REP_DELTA) != 0))
728     goto error;
729
730   SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
731
732   str = svn_cstring_tokenize(" ", &last_str);
733   if (! str)
734     goto error;
735   SVN_ERR(svn_cstring_atoi64(&val, str));
736   (*header)->base_item_index = (apr_off_t)val;
737
738   str = svn_cstring_tokenize(" ", &last_str);
739   if (! str)
740     goto error;
741   SVN_ERR(svn_cstring_atoi64(&val, str));
742   (*header)->base_length = (svn_filesize_t)val;
743
744   return SVN_NO_ERROR;
745
746  error:
747   return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
748                            _("Malformed representation header"));
749 }
750
751 svn_error_t *
752 svn_fs_x__write_rep_header(svn_fs_x__rep_header_t *header,
753                            svn_stream_t *stream,
754                            apr_pool_t *scratch_pool)
755 {
756   const char *text;
757
758   switch (header->type)
759     {
760       case svn_fs_x__rep_self_delta:
761         text = REP_DELTA "\n";
762         break;
763
764       default:
765         text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
766                                           " %" SVN_FILESIZE_T_FMT "\n",
767                             header->base_revision, header->base_item_index,
768                             header->base_length);
769     }
770
771   return svn_error_trace(svn_stream_puts(stream, text));
772 }
773
774 /* Read the next entry in the changes record from file FILE and store
775    the resulting change in *CHANGE_P.  If there is no next record,
776    store NULL there.  Perform all allocations from POOL. */
777 static svn_error_t *
778 read_change(svn_fs_x__change_t **change_p,
779             svn_stream_t *stream,
780             apr_pool_t *result_pool,
781             apr_pool_t *scratch_pool)
782 {
783   svn_stringbuf_t *line;
784   svn_boolean_t eof = TRUE;
785   svn_fs_x__change_t *change;
786   char *str, *last_str, *kind_str;
787
788   /* Default return value. */
789   *change_p = NULL;
790
791   SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
792
793   /* Check for a blank line. */
794   if (eof || (line->len == 0))
795     return SVN_NO_ERROR;
796
797   change = apr_pcalloc(result_pool, sizeof(*change));
798   last_str = line->data;
799
800   /* Get the node-id of the change. */
801   str = svn_cstring_tokenize(" ", &last_str);
802   if (str == NULL)
803     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
804                             _("Invalid changes line in rev-file"));
805
806   SVN_ERR(svn_fs_x__id_parse(&change->noderev_id, str));
807
808   /* Get the change type. */
809   str = svn_cstring_tokenize(" ", &last_str);
810   if (str == NULL)
811     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
812                             _("Invalid changes line in rev-file"));
813
814   /* Don't bother to check the format number before looking for
815    * node-kinds: just read them if you find them. */
816   change->node_kind = svn_node_unknown;
817   kind_str = strchr(str, '-');
818   if (kind_str)
819     {
820       /* Cap off the end of "str" (the action). */
821       *kind_str = '\0';
822       kind_str++;
823       if (strcmp(kind_str, SVN_FS_X__KIND_FILE) == 0)
824         change->node_kind = svn_node_file;
825       else if (strcmp(kind_str, SVN_FS_X__KIND_DIR) == 0)
826         change->node_kind = svn_node_dir;
827       else
828         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
829                                 _("Invalid changes line in rev-file"));
830     }
831
832   if (strcmp(str, ACTION_MODIFY) == 0)
833     {
834       change->change_kind = svn_fs_path_change_modify;
835     }
836   else if (strcmp(str, ACTION_ADD) == 0)
837     {
838       change->change_kind = svn_fs_path_change_add;
839     }
840   else if (strcmp(str, ACTION_DELETE) == 0)
841     {
842       change->change_kind = svn_fs_path_change_delete;
843     }
844   else if (strcmp(str, ACTION_REPLACE) == 0)
845     {
846       change->change_kind = svn_fs_path_change_replace;
847     }
848   else if (strcmp(str, ACTION_RESET) == 0)
849     {
850       change->change_kind = svn_fs_path_change_reset;
851     }
852   else
853     {
854       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
855                               _("Invalid change kind in rev file"));
856     }
857
858   /* Get the text-mod flag. */
859   str = svn_cstring_tokenize(" ", &last_str);
860   if (str == NULL)
861     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
862                             _("Invalid changes line in rev-file"));
863
864   if (strcmp(str, FLAG_TRUE) == 0)
865     {
866       change->text_mod = TRUE;
867     }
868   else if (strcmp(str, FLAG_FALSE) == 0)
869     {
870       change->text_mod = FALSE;
871     }
872   else
873     {
874       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
875                               _("Invalid text-mod flag in rev-file"));
876     }
877
878   /* Get the prop-mod flag. */
879   str = svn_cstring_tokenize(" ", &last_str);
880   if (str == NULL)
881     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
882                             _("Invalid changes line in rev-file"));
883
884   if (strcmp(str, FLAG_TRUE) == 0)
885     {
886       change->prop_mod = TRUE;
887     }
888   else if (strcmp(str, FLAG_FALSE) == 0)
889     {
890       change->prop_mod = FALSE;
891     }
892   else
893     {
894       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
895                               _("Invalid prop-mod flag in rev-file"));
896     }
897
898   /* Get the mergeinfo-mod flag. */
899   str = svn_cstring_tokenize(" ", &last_str);
900   if (str == NULL)
901     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
902                             _("Invalid changes line in rev-file"));
903
904   if (strcmp(str, FLAG_TRUE) == 0)
905     {
906       change->mergeinfo_mod = svn_tristate_true;
907     }
908   else if (strcmp(str, FLAG_FALSE) == 0)
909     {
910       change->mergeinfo_mod = svn_tristate_false;
911     }
912   else
913     {
914       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
915                               _("Invalid mergeinfo-mod flag in rev-file"));
916     }
917
918   /* Get the changed path. */
919   if (!svn_fspath__is_canonical(last_str))
920     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
921                             _("Invalid path in changes line"));
922
923   change->path.data = auto_unescape_path(apr_pstrmemdup(result_pool,
924                                                         last_str,
925                                                         strlen(last_str)),
926                                          result_pool);
927   change->path.len = strlen(change->path.data);
928
929   /* Read the next line, the copyfrom line. */
930   SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
931   change->copyfrom_known = TRUE;
932   if (eof || line->len == 0)
933     {
934       change->copyfrom_rev = SVN_INVALID_REVNUM;
935       change->copyfrom_path = NULL;
936     }
937   else
938     {
939       last_str = line->data;
940       SVN_ERR(parse_revnum(&change->copyfrom_rev, (const char **)&last_str));
941
942       if (!svn_fspath__is_canonical(last_str))
943         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
944                                 _("Invalid copy-from path in changes line"));
945
946       change->copyfrom_path = auto_unescape_path(last_str, result_pool);
947     }
948
949   *change_p = change;
950
951   return SVN_NO_ERROR;
952 }
953
954 svn_error_t *
955 svn_fs_x__read_changes(apr_array_header_t **changes,
956                        svn_stream_t *stream,
957                        apr_pool_t *result_pool,
958                        apr_pool_t *scratch_pool)
959 {
960   svn_fs_x__change_t *change;
961   apr_pool_t *iterpool;
962
963   /* Pre-allocate enough room for most change lists.
964      (will be auto-expanded as necessary).
965
966      Chose the default to just below 2^N such that the doubling reallocs
967      will request roughly 2^M bytes from the OS without exceeding the
968      respective two-power by just a few bytes (leaves room array and APR
969      node overhead for large enough M).
970    */
971   *changes = apr_array_make(result_pool, 63, sizeof(svn_fs_x__change_t *));
972
973   SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
974   iterpool = svn_pool_create(scratch_pool);
975   while (change)
976     {
977       APR_ARRAY_PUSH(*changes, svn_fs_x__change_t*) = change;
978       SVN_ERR(read_change(&change, stream, result_pool, iterpool));
979       svn_pool_clear(iterpool);
980     }
981   svn_pool_destroy(iterpool);
982
983   return SVN_NO_ERROR;
984 }
985
986 svn_error_t *
987 svn_fs_x__read_changes_incrementally(svn_stream_t *stream,
988                                      svn_fs_x__change_receiver_t
989                                        change_receiver,
990                                      void *change_receiver_baton,
991                                      apr_pool_t *scratch_pool)
992 {
993   svn_fs_x__change_t *change;
994   apr_pool_t *iterpool;
995
996   iterpool = svn_pool_create(scratch_pool);
997   do
998     {
999       svn_pool_clear(iterpool);
1000
1001       SVN_ERR(read_change(&change, stream, iterpool, iterpool));
1002       if (change)
1003         SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
1004     }
1005   while (change);
1006   svn_pool_destroy(iterpool);
1007
1008   return SVN_NO_ERROR;
1009 }
1010
1011 /* Write a single change entry, path PATH, change CHANGE, to STREAM.
1012
1013    All temporary allocations are in SCRATCH_POOL. */
1014 static svn_error_t *
1015 write_change_entry(svn_stream_t *stream,
1016                    svn_fs_x__change_t *change,
1017                    apr_pool_t *scratch_pool)
1018 {
1019   const char *idstr;
1020   const char *change_string = NULL;
1021   const char *kind_string = "";
1022   svn_stringbuf_t *buf;
1023   apr_size_t len;
1024
1025   switch (change->change_kind)
1026     {
1027     case svn_fs_path_change_modify:
1028       change_string = ACTION_MODIFY;
1029       break;
1030     case svn_fs_path_change_add:
1031       change_string = ACTION_ADD;
1032       break;
1033     case svn_fs_path_change_delete:
1034       change_string = ACTION_DELETE;
1035       break;
1036     case svn_fs_path_change_replace:
1037       change_string = ACTION_REPLACE;
1038       break;
1039     case svn_fs_path_change_reset:
1040       change_string = ACTION_RESET;
1041       break;
1042     default:
1043       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1044                                _("Invalid change type %d"),
1045                                change->change_kind);
1046     }
1047
1048   idstr = svn_fs_x__id_unparse(&change->noderev_id, scratch_pool)->data;
1049
1050   SVN_ERR_ASSERT(change->node_kind == svn_node_dir
1051                  || change->node_kind == svn_node_file);
1052   kind_string = apr_psprintf(scratch_pool, "-%s",
1053                              change->node_kind == svn_node_dir
1054                              ? SVN_FS_X__KIND_DIR
1055                              : SVN_FS_X__KIND_FILE);
1056
1057   buf = svn_stringbuf_createf(scratch_pool, "%s %s%s %s %s %s %s\n",
1058                               idstr, change_string, kind_string,
1059                               change->text_mod ? FLAG_TRUE : FLAG_FALSE,
1060                               change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
1061                               change->mergeinfo_mod == svn_tristate_true
1062                                                ? FLAG_TRUE : FLAG_FALSE,
1063                               auto_escape_path(change->path.data, scratch_pool));
1064
1065   if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
1066     {
1067       svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
1068                                change->copyfrom_rev,
1069                                auto_escape_path(change->copyfrom_path,
1070                                                 scratch_pool)));
1071     }
1072
1073   svn_stringbuf_appendbyte(buf, '\n');
1074
1075   /* Write all change info in one write call. */
1076   len = buf->len;
1077   return svn_error_trace(svn_stream_write(stream, buf->data, &len));
1078 }
1079
1080 svn_error_t *
1081 svn_fs_x__write_changes(svn_stream_t *stream,
1082                         svn_fs_t *fs,
1083                         apr_hash_t *changes,
1084                         svn_boolean_t terminate_list,
1085                         apr_pool_t *scratch_pool)
1086 {
1087   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1088   apr_array_header_t *sorted_changed_paths;
1089   int i;
1090
1091   /* For the sake of the repository administrator sort the changes so
1092      that the final file is deterministic and repeatable, however the
1093      rest of the FSX code doesn't require any particular order here.
1094
1095      Also, this sorting is only effective in writing all entries with
1096      a single call as write_final_changed_path_info() does.  For the
1097      list being written incrementally during transaction, we actually
1098      *must not* change the order of entries from different calls.
1099    */
1100   sorted_changed_paths = svn_sort__hash(changes,
1101                                         svn_sort_compare_items_lexically,
1102                                         scratch_pool);
1103
1104   /* Write all items to disk in the new order. */
1105   for (i = 0; i < sorted_changed_paths->nelts; ++i)
1106     {
1107       svn_fs_x__change_t *change;
1108
1109       svn_pool_clear(iterpool);
1110       change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
1111
1112       /* Write out the new entry into the final rev-file. */
1113       SVN_ERR(write_change_entry(stream, change, iterpool));
1114     }
1115
1116   if (terminate_list)
1117     svn_stream_puts(stream, "\n");
1118
1119   svn_pool_destroy(iterpool);
1120
1121   return SVN_NO_ERROR;
1122 }
1123