1 /* low_level.c --- low level r/w access to fs_fs file structures
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
20 * ====================================================================
23 #include "svn_private_config.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"
32 #include "../libsvn_fs/fs-loader.h"
34 #include "low_level.h"
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"
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"
57 /* True and False flags. */
58 #define FLAG_TRUE "true"
59 #define FLAG_FALSE "false"
61 /* Kinds of representation. */
62 #define REP_PLAIN "PLAIN"
63 #define REP_DELTA "DELTA"
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
69 /* The 256 is an arbitrary size large enough to hold the node id and the
71 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
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.
79 parse_revnum(svn_revnum_t *rev,
82 const char *string = *text;
83 if ((string[0] == '-') && (string[1] == '1'))
85 *rev = SVN_INVALID_REVNUM;
90 SVN_ERR(svn_revnum_parse(rev, string, &string));
95 else if (*string != '\0')
96 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
97 _("Invalid character in revision number"));
104 svn_fs_fs__parse_revision_trailer(apr_off_t *root_offset,
105 apr_off_t *changes_offset,
106 svn_stringbuf_t *trailer,
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;
116 /* The last byte should be a newline. */
117 if (trailer->len == 0 || trailer->data[trailer->len - 1] != '\n')
119 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
120 _("Revision file (r%ld) lacks trailing newline"),
124 /* Look for the next previous newline. */
125 for (i = num_bytes - 2; i >= 0; i--)
127 if (trailer->data[i] == '\n')
133 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
134 _("Final line in revision file (r%ld) longer "
135 "than 64 characters"),
140 str = &trailer->data[i];
142 /* find the next space */
143 for ( ; i < (num_bytes - 2) ; i++)
144 if (trailer->data[i] == ' ')
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"),
156 trailer->data[i] = '\0';
157 SVN_ERR(svn_cstring_atoi64(&val, str));
158 *root_offset = (apr_off_t)val;
162 str = &trailer->data[i];
164 /* find the next newline */
165 for ( ; i < num_bytes; i++)
166 if (trailer->data[i] == '\n')
173 trailer->data[i] = '\0';
174 SVN_ERR(svn_cstring_atoi64(&val, str));
175 *changes_offset = (apr_off_t)val;
182 svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
183 apr_off_t changes_offset,
184 apr_pool_t *result_pool)
186 return svn_stringbuf_createf(result_pool,
187 "%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
192 /* If ERR is not NULL, wrap it MESSAGE. The latter must have an %ld
193 * format parameter that will be filled with REV. */
195 wrap_footer_error(svn_error_t *err,
200 return svn_error_quick_wrapf(err, message, rev);
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,
212 apr_off_t footer_offset,
213 apr_pool_t *result_pool)
216 char *last_str = footer->data;
218 /* Get the L2P offset. */
219 const char *str = svn_cstring_tokenize(" ", &last_str);
221 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
222 "Invalid r%ld footer", rev);
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",
228 *l2p_offset = (apr_off_t)val;
230 /* Get the L2P checksum. */
231 str = svn_cstring_tokenize(" ", &last_str);
233 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
234 "Invalid r%ld footer", rev);
236 SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
239 /* Get the P2L offset. */
240 str = svn_cstring_tokenize(" ", &last_str);
242 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
243 "Invalid r%ld footer", rev);
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",
249 *p2l_offset = (apr_off_t)val;
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"
256 apr_psprintf(result_pool,
257 "%" APR_UINT64_T_HEX_FMT,
258 (apr_uint64_t)*p2l_offset),
259 apr_psprintf(result_pool,
260 "%" APR_UINT64_T_HEX_FMT,
261 (apr_uint64_t)*l2p_offset),
264 /* Get the P2L checksum. */
265 str = svn_cstring_tokenize(" ", &last_str);
267 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
268 "Invalid r%ld footer", rev);
270 SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
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)
284 return svn_stringbuf_createf(result_pool,
285 "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
287 svn_checksum_to_cstring(l2p_checksum,
290 svn_checksum_to_cstring(p2l_checksum,
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. */
298 read_change(change_t **change_p,
299 svn_stream_t *stream,
300 apr_pool_t *result_pool,
301 apr_pool_t *scratch_pool)
303 svn_stringbuf_t *line;
304 svn_boolean_t eof = TRUE;
306 char *str, *last_str, *kind_str;
307 svn_fs_path_change2_t *info;
309 /* Default return value. */
312 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
314 /* Check for a blank line. */
315 if (eof || (line->len == 0))
318 change = apr_pcalloc(result_pool, sizeof(*change));
319 info = &change->info;
320 last_str = line->data;
322 /* Get the node-id of the change. */
323 str = svn_cstring_tokenize(" ", &last_str);
325 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
326 _("Invalid changes line in rev-file"));
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"));
333 /* Get the change type. */
334 str = svn_cstring_tokenize(" ", &last_str);
336 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
337 _("Invalid changes line in rev-file"));
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, '-');
345 /* Cap off the end of "str" (the action). */
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;
353 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
354 _("Invalid changes line in rev-file"));
357 if (strcmp(str, ACTION_MODIFY) == 0)
359 info->change_kind = svn_fs_path_change_modify;
361 else if (strcmp(str, ACTION_ADD) == 0)
363 info->change_kind = svn_fs_path_change_add;
365 else if (strcmp(str, ACTION_DELETE) == 0)
367 info->change_kind = svn_fs_path_change_delete;
369 else if (strcmp(str, ACTION_REPLACE) == 0)
371 info->change_kind = svn_fs_path_change_replace;
373 else if (strcmp(str, ACTION_RESET) == 0)
375 info->change_kind = svn_fs_path_change_reset;
379 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
380 _("Invalid change kind in rev file"));
383 /* Get the text-mod flag. */
384 str = svn_cstring_tokenize(" ", &last_str);
386 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
387 _("Invalid changes line in rev-file"));
389 if (strcmp(str, FLAG_TRUE) == 0)
391 info->text_mod = TRUE;
393 else if (strcmp(str, FLAG_FALSE) == 0)
395 info->text_mod = FALSE;
399 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
400 _("Invalid text-mod flag in rev-file"));
403 /* Get the prop-mod flag. */
404 str = svn_cstring_tokenize(" ", &last_str);
406 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
407 _("Invalid changes line in rev-file"));
409 if (strcmp(str, FLAG_TRUE) == 0)
411 info->prop_mod = TRUE;
413 else if (strcmp(str, FLAG_FALSE) == 0)
415 info->prop_mod = FALSE;
419 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
420 _("Invalid prop-mod flag in rev-file"));
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 != '/')
429 str = svn_cstring_tokenize(" ", &last_str);
431 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
432 _("Invalid changes line in rev-file"));
434 if (strcmp(str, FLAG_TRUE) == 0)
436 info->mergeinfo_mod = svn_tristate_true;
438 else if (strcmp(str, FLAG_FALSE) == 0)
440 info->mergeinfo_mod = svn_tristate_false;
444 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
445 _("Invalid mergeinfo-mod flag in rev-file"));
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"));
454 change->path.len = strlen(last_str);
455 change->path.data = apr_pstrdup(result_pool, last_str);
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)
462 info->copyfrom_rev = SVN_INVALID_REVNUM;
463 info->copyfrom_path = NULL;
467 last_str = line->data;
468 SVN_ERR(parse_revnum(&info->copyfrom_rev, (const char **)&last_str));
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"));
474 info->copyfrom_path = apr_pstrdup(result_pool, last_str);
483 svn_fs_fs__read_changes(apr_array_header_t **changes,
484 svn_stream_t *stream,
486 apr_pool_t *result_pool,
487 apr_pool_t *scratch_pool)
489 apr_pool_t *iterpool;
491 /* Pre-allocate enough room for most change lists.
492 (will be auto-expanded as necessary).
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).
499 *changes = apr_array_make(result_pool, 63, sizeof(change_t *));
501 iterpool = svn_pool_create(scratch_pool);
502 for (; max_count > 0; --max_count)
505 svn_pool_clear(iterpool);
506 SVN_ERR(read_change(&change, stream, result_pool, iterpool));
510 APR_ARRAY_PUSH(*changes, change_t*) = change;
512 svn_pool_destroy(iterpool);
518 svn_fs_fs__read_changes_incrementally(svn_stream_t *stream,
519 svn_fs_fs__change_receiver_t
521 void *change_receiver_baton,
522 apr_pool_t *scratch_pool)
525 apr_pool_t *iterpool;
527 iterpool = svn_pool_create(scratch_pool);
530 svn_pool_clear(iterpool);
532 SVN_ERR(read_change(&change, stream, iterpool, iterpool));
534 SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
537 svn_pool_destroy(iterpool);
542 /* Write a single change entry, path PATH, change CHANGE, to STREAM.
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. */
548 write_change_entry(svn_stream_t *stream,
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)
556 const char *change_string = NULL;
557 const char *kind_string = "";
558 const char *mergeinfo_string = "";
559 svn_stringbuf_t *buf;
562 switch (change->change_kind)
564 case svn_fs_path_change_modify:
565 change_string = ACTION_MODIFY;
567 case svn_fs_path_change_add:
568 change_string = ACTION_ADD;
570 case svn_fs_path_change_delete:
571 change_string = ACTION_DELETE;
573 case svn_fs_path_change_replace:
574 change_string = ACTION_REPLACE;
576 case svn_fs_path_change_reset:
577 change_string = ACTION_RESET;
580 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
581 _("Invalid change type %d"),
582 change->change_kind);
585 if (change->node_rev_id)
586 idstr = svn_fs_fs__id_unparse(change->node_rev_id, scratch_pool)->data;
588 idstr = ACTION_RESET;
590 if (include_node_kind)
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);
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
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,
613 if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
615 svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
616 change->copyfrom_rev,
617 change->copyfrom_path));
620 svn_stringbuf_appendbyte(buf, '\n');
622 /* Write all change info in one write call. */
624 return svn_error_trace(svn_stream_write(stream, buf->data, &len));
628 svn_fs_fs__write_changes(svn_stream_t *stream,
631 svn_boolean_t terminate_list,
632 apr_pool_t *scratch_pool)
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;
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.
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.
652 sorted_changed_paths = svn_sort__hash(changes,
653 svn_sort_compare_items_lexically,
656 /* Write all items to disk in the new order. */
657 for (i = 0; i < sorted_changed_paths->nelts; ++i)
659 svn_fs_path_change2_t *change;
662 svn_pool_clear(iterpool);
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;
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));
673 svn_stream_puts(stream, "\n");
675 svn_pool_destroy(iterpool);
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
685 read_header_block(apr_hash_t **headers,
686 svn_stream_t *stream,
687 apr_pool_t *result_pool)
689 *headers = svn_hash__make(result_pool);
693 svn_stringbuf_t *header_str;
694 const char *name, *value;
699 SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
702 if (eof || header_str->len == 0)
703 break; /* end of header block */
705 while (header_str->data[i] != ':')
707 if (header_str->data[i] == '\0')
708 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
709 _("Found malformed header '%s' in "
715 /* Create a 'name' string and point to it. */
716 header_str->data[i] = '\0';
717 name = header_str->data;
720 /* Check if we have enough data to parse. */
721 if (i + 2 > header_str->len)
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 "
731 /* Skip over the NULL byte and the space following it. */
734 value = header_str->data + i;
736 /* header_str is safely in our pool, so we can use bits of it as
738 apr_hash_set(*headers, name, name_len, value);
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). */
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)
753 representation_t *rep;
756 char *string = text->data;
757 svn_checksum_t *checksum;
760 rep = apr_pcalloc(result_pool, sizeof(*rep));
763 SVN_ERR(parse_revnum(&rep->revision, (const char **)&string));
765 /* initialize transaction info (never stored) */
766 svn_fs_fs__id_txn_reset(&rep->txn_id);
768 /* while in transactions, it is legal to simply write "-1" */
769 str = svn_cstring_tokenize(" ", &string);
772 if (rep->revision == SVN_INVALID_REVNUM)
775 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
776 _("Malformed text representation offset line in node-rev"));
779 SVN_ERR(svn_cstring_atoi64(&val, str));
780 rep->item_index = (apr_uint64_t)val;
782 str = svn_cstring_tokenize(" ", &string);
784 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
785 _("Malformed text representation offset line in node-rev"));
787 SVN_ERR(svn_cstring_atoi64(&val, str));
788 rep->size = (svn_filesize_t)val;
790 str = svn_cstring_tokenize(" ", &string);
792 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
793 _("Malformed text representation offset line in node-rev"));
795 SVN_ERR(svn_cstring_atoi64(&val, str));
796 rep->expanded_size = (svn_filesize_t)val;
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"));
804 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
807 /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
808 contains the correct value. */
810 memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
812 /* The remaining fields are only used for formats >= 4, so check that. */
813 str = svn_cstring_tokenize(" ", &string);
817 /* Is the SHA1 hash present? */
818 if (str[0] == '-' && str[1] == 0)
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"));
829 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
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;
838 /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
839 contains the correct value. */
841 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
843 str = svn_cstring_tokenize(" ", &string);
845 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
846 _("Malformed text representation offset line in node-rev"));
848 /* Is the uniquifier present? */
849 if (str[0] == '-' && str[1] == 0)
855 char *substring = str;
857 /* Read the uniquifier. */
858 str = svn_cstring_tokenize("/", &substring);
860 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
861 _("Malformed text representation offset line in node-rev"));
863 SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
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"));
871 rep->uniquifier.number = svn__base36toui64(&end, str);
875 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
876 _("Malformed text representation offset line in node-rev"));
881 /* Wrap svn_fs_fs__parse_representation(), extracting its TXN_ID from our
882 NODEREV_ID, and adding an error message. */
884 read_rep_offsets(representation_t **rep_p,
886 const svn_fs_id_t *noderev_id,
887 apr_pool_t *result_pool,
888 apr_pool_t *scratch_pool)
891 = svn_fs_fs__parse_representation(rep_p,
892 svn_stringbuf_create_wrap(string,
898 const svn_string_t *id_unparsed;
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)");
907 return svn_error_quick_wrap(err, where);
910 if ((*rep_p)->revision == SVN_INVALID_REVNUM)
912 (*rep_p)->txn_id = *svn_fs_fs__id_txn_id(noderev_id);
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)
924 node_revision_t *noderev;
926 const char *noderev_id;
928 SVN_ERR(read_header_block(&headers, stream, scratch_pool));
930 noderev = apr_pcalloc(result_pool, sizeof(*noderev));
932 /* Read the node-rev id. */
933 value = svn_hash_gets(headers, HEADER_ID);
935 /* ### More information: filename/offset coordinates */
936 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
937 _("Missing id field in node-rev"));
939 SVN_ERR(svn_stream_close(stream));
941 SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_pool));
942 noderev_id = value; /* for error messages later */
945 value = svn_hash_gets(headers, HEADER_TYPE);
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'"),
955 noderev->kind = (strcmp(value, SVN_FS_FS__KIND_FILE) == 0)
959 /* Read the 'count' field. */
960 value = svn_hash_gets(headers, HEADER_COUNT);
962 SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
964 noderev->predecessor_count = 0;
966 /* Get the properties location. */
967 value = svn_hash_gets(headers, HEADER_PROPS);
970 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
971 noderev->id, result_pool, scratch_pool));
974 /* Get the data location. */
975 value = svn_hash_gets(headers, HEADER_TEXT);
978 SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
979 noderev->id, result_pool, scratch_pool));
982 /* Get the created path. */
983 value = svn_hash_gets(headers, HEADER_CPATH);
986 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
987 _("Missing cpath field in node-rev '%s'"),
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'"),
997 noderev->created_path = apr_pstrdup(result_pool, value);
1000 /* Get the predecessor ID. */
1001 value = svn_hash_gets(headers, HEADER_PRED);
1003 SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value,
1006 /* Get the copyroot. */
1007 value = svn_hash_gets(headers, HEADER_COPYROOT);
1010 noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path);
1011 noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
1015 SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
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'"),
1021 noderev->copyroot_path = apr_pstrdup(result_pool, value);
1024 /* Get the copyfrom. */
1025 value = svn_hash_gets(headers, HEADER_COPYFROM);
1028 noderev->copyfrom_path = NULL;
1029 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1033 SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
1036 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1037 _("Malformed copyfrom line in node-rev '%s'"),
1039 noderev->copyfrom_path = apr_pstrdup(result_pool, value);
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);
1046 /* Get the mergeinfo count. */
1047 value = svn_hash_gets(headers, HEADER_MINFO_CNT);
1049 SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
1051 noderev->mergeinfo_count = 0;
1053 /* Get whether *this* node has mergeinfo. */
1054 value = svn_hash_gets(headers, HEADER_MINFO_HERE);
1055 noderev->has_mergeinfo = (value != NULL);
1057 *noderev_p = noderev;
1059 return SVN_NO_ERROR;
1062 /* Return a textual representation of the DIGEST of given KIND.
1063 * Allocate the result in RESULT_POOL.
1066 format_digest(const unsigned char *digest,
1067 svn_checksum_kind_t kind,
1068 apr_pool_t *result_pool)
1070 svn_checksum_t checksum;
1071 checksum.digest = digest;
1072 checksum.kind = kind;
1074 return svn_checksum_to_cstring_display(&checksum, result_pool);
1077 /* Return a textual representation of the uniquifier represented
1078 * by NODEREV_TXN_ID and NUMBER. Use POOL for the allocations.
1081 format_uniquifier(const svn_fs_fs__id_part_t *noderev_txn_id,
1082 apr_uint64_t number,
1085 char buf[SVN_INT64_BUFFER_SIZE];
1086 const char *txn_id_str;
1088 txn_id_str = svn_fs_fs__id_txn_unparse(noderev_txn_id, pool);
1089 svn__ui64tobase36(buf, number);
1091 return apr_psprintf(pool, "%s/_%s", txn_id_str, buf);
1095 svn_fs_fs__unparse_representation(representation_t *rep,
1097 svn_boolean_t mutable_rep_truncated,
1098 apr_pool_t *result_pool,
1099 apr_pool_t *scratch_pool)
1101 svn_stringbuf_t *str;
1102 const char *sha1_str;
1103 const char *uniquifier_str;
1105 if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated)
1106 return svn_stringbuf_ncreate("-1", 2, result_pool);
1108 /* Format of the string:
1109 <rev> <item_index> <size> <expanded-size> <md5> [<sha1>] [<uniquifier>]
1111 str = svn_stringbuf_createf(
1114 " %" APR_UINT64_T_FMT
1115 " %" SVN_FILESIZE_T_FMT
1116 " %" SVN_FILESIZE_T_FMT
1122 format_digest(rep->md5_digest, svn_checksum_md5, scratch_pool));
1124 /* Compatibility: these formats don't understand <sha1> and <uniquifier>. */
1125 if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1128 if (format < SVN_FS_FS__MIN_REP_STRING_OPTIONAL_VALUES_FORMAT)
1130 /* Compatibility: these formats can only have <sha1> and <uniquifier>
1131 present simultaneously, or don't have them at all. */
1134 sha1_str = format_digest(rep->sha1_digest, svn_checksum_sha1,
1136 uniquifier_str = format_uniquifier(&rep->uniquifier.noderev_txn_id,
1137 rep->uniquifier.number,
1139 svn_stringbuf_appendbyte(str, ' ');
1140 svn_stringbuf_appendcstr(str, sha1_str);
1141 svn_stringbuf_appendbyte(str, ' ');
1142 svn_stringbuf_appendcstr(str, uniquifier_str);
1147 /* The most recent formats support optional <sha1> and <uniquifier> values. */
1150 sha1_str = format_digest(rep->sha1_digest, svn_checksum_sha1,
1156 if (rep->uniquifier.number == 0 &&
1157 rep->uniquifier.noderev_txn_id.number == 0 &&
1158 rep->uniquifier.noderev_txn_id.revision == 0)
1160 uniquifier_str = "-";
1164 uniquifier_str = format_uniquifier(&rep->uniquifier.noderev_txn_id,
1165 rep->uniquifier.number,
1169 svn_stringbuf_appendbyte(str, ' ');
1170 svn_stringbuf_appendcstr(str, sha1_str);
1171 svn_stringbuf_appendbyte(str, ' ');
1172 svn_stringbuf_appendcstr(str, uniquifier_str);
1179 svn_fs_fs__write_noderev(svn_stream_t *outfile,
1180 node_revision_t *noderev,
1182 svn_boolean_t include_mergeinfo,
1183 apr_pool_t *scratch_pool)
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));
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));
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));
1198 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
1199 noderev->predecessor_count));
1201 if (noderev->data_rep)
1202 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
1203 svn_fs_fs__unparse_representation
1206 noderev->kind == svn_node_dir,
1207 scratch_pool, scratch_pool)->data));
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));
1215 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
1216 noderev->created_path));
1218 if (noderev->copyfrom_path)
1219 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
1221 noderev->copyfrom_rev,
1222 noderev->copyfrom_path));
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"
1228 noderev->copyroot_rev,
1229 noderev->copyroot_path));
1231 if (noderev->is_fresh_txn_root)
1232 SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
1234 if (include_mergeinfo)
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));
1241 if (noderev->has_mergeinfo)
1242 SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
1245 return svn_stream_puts(outfile, "\n");
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)
1254 svn_stringbuf_t *buffer;
1255 char *str, *last_str;
1257 svn_boolean_t eol = FALSE;
1259 SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
1261 *header = apr_pcalloc(result_pool, sizeof(**header));
1262 (*header)->header_size = buffer->len + 1;
1263 if (strcmp(buffer->data, REP_PLAIN) == 0)
1265 (*header)->type = svn_fs_fs__rep_plain;
1266 return SVN_NO_ERROR;
1269 if (strcmp(buffer->data, REP_DELTA) == 0)
1271 /* This is a delta against the empty stream. */
1272 (*header)->type = svn_fs_fs__rep_self_delta;
1273 return SVN_NO_ERROR;
1276 (*header)->type = svn_fs_fs__rep_delta;
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))
1284 SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
1286 str = svn_cstring_tokenize(" ", &last_str);
1289 SVN_ERR(svn_cstring_atoi64(&val, str));
1290 (*header)->base_item_index = (apr_off_t)val;
1292 str = svn_cstring_tokenize(" ", &last_str);
1295 SVN_ERR(svn_cstring_atoi64(&val, str));
1296 (*header)->base_length = (svn_filesize_t)val;
1298 return SVN_NO_ERROR;
1301 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1302 _("Malformed representation header"));
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)
1312 switch (header->type)
1314 case svn_fs_fs__rep_plain:
1315 text = REP_PLAIN "\n";
1318 case svn_fs_fs__rep_self_delta:
1319 text = REP_DELTA "\n";
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);
1329 return svn_error_trace(svn_stream_puts(stream, text));