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