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