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",
193 svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
194 svn_checksum_t **l2p_checksum,
195 apr_off_t *p2l_offset,
196 svn_checksum_t **p2l_checksum,
197 svn_stringbuf_t *footer,
199 apr_pool_t *result_pool)
202 char *last_str = footer->data;
204 /* Get the L2P offset. */
205 const char *str = svn_cstring_tokenize(" ", &last_str);
207 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
208 _("Invalid revision footer"));
210 SVN_ERR(svn_cstring_atoi64(&val, str));
211 *l2p_offset = (apr_off_t)val;
213 /* Get the L2P checksum. */
214 str = svn_cstring_tokenize(" ", &last_str);
216 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
217 _("Invalid revision footer"));
219 SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
222 /* Get the P2L offset. */
223 str = svn_cstring_tokenize(" ", &last_str);
225 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
226 _("Invalid revision footer"));
228 SVN_ERR(svn_cstring_atoi64(&val, str));
229 *p2l_offset = (apr_off_t)val;
231 /* Get the P2L checksum. */
232 str = svn_cstring_tokenize(" ", &last_str);
234 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
235 _("Invalid revision footer"));
237 SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
244 svn_fs_fs__unparse_footer(apr_off_t l2p_offset,
245 svn_checksum_t *l2p_checksum,
246 apr_off_t p2l_offset,
247 svn_checksum_t *p2l_checksum,
248 apr_pool_t *result_pool,
249 apr_pool_t *scratch_pool)
251 return svn_stringbuf_createf(result_pool,
252 "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
254 svn_checksum_to_cstring(l2p_checksum,
257 svn_checksum_to_cstring(p2l_checksum,
261 /* Read the next entry in the changes record from file FILE and store
262 the resulting change in *CHANGE_P. If there is no next record,
263 store NULL there. Perform all allocations from POOL. */
265 read_change(change_t **change_p,
266 svn_stream_t *stream,
267 apr_pool_t *result_pool,
268 apr_pool_t *scratch_pool)
270 svn_stringbuf_t *line;
271 svn_boolean_t eof = TRUE;
273 char *str, *last_str, *kind_str;
274 svn_fs_path_change2_t *info;
276 /* Default return value. */
279 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
281 /* Check for a blank line. */
282 if (eof || (line->len == 0))
285 change = apr_pcalloc(result_pool, sizeof(*change));
286 info = &change->info;
287 last_str = line->data;
289 /* Get the node-id of the change. */
290 str = svn_cstring_tokenize(" ", &last_str);
292 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
293 _("Invalid changes line in rev-file"));
295 SVN_ERR(svn_fs_fs__id_parse(&info->node_rev_id, str, result_pool));
296 if (info->node_rev_id == NULL)
297 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
298 _("Invalid changes line in rev-file"));
300 /* Get the change type. */
301 str = svn_cstring_tokenize(" ", &last_str);
303 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
304 _("Invalid changes line in rev-file"));
306 /* Don't bother to check the format number before looking for
307 * node-kinds: just read them if you find them. */
308 info->node_kind = svn_node_unknown;
309 kind_str = strchr(str, '-');
312 /* Cap off the end of "str" (the action). */
315 if (strcmp(kind_str, SVN_FS_FS__KIND_FILE) == 0)
316 info->node_kind = svn_node_file;
317 else if (strcmp(kind_str, SVN_FS_FS__KIND_DIR) == 0)
318 info->node_kind = svn_node_dir;
320 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
321 _("Invalid changes line in rev-file"));
324 if (strcmp(str, ACTION_MODIFY) == 0)
326 info->change_kind = svn_fs_path_change_modify;
328 else if (strcmp(str, ACTION_ADD) == 0)
330 info->change_kind = svn_fs_path_change_add;
332 else if (strcmp(str, ACTION_DELETE) == 0)
334 info->change_kind = svn_fs_path_change_delete;
336 else if (strcmp(str, ACTION_REPLACE) == 0)
338 info->change_kind = svn_fs_path_change_replace;
340 else if (strcmp(str, ACTION_RESET) == 0)
342 info->change_kind = svn_fs_path_change_reset;
346 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
347 _("Invalid change kind in rev file"));
350 /* Get the text-mod flag. */
351 str = svn_cstring_tokenize(" ", &last_str);
353 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
354 _("Invalid changes line in rev-file"));
356 if (strcmp(str, FLAG_TRUE) == 0)
358 info->text_mod = TRUE;
360 else if (strcmp(str, FLAG_FALSE) == 0)
362 info->text_mod = FALSE;
366 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
367 _("Invalid text-mod flag in rev-file"));
370 /* Get the prop-mod flag. */
371 str = svn_cstring_tokenize(" ", &last_str);
373 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
374 _("Invalid changes line in rev-file"));
376 if (strcmp(str, FLAG_TRUE) == 0)
378 info->prop_mod = TRUE;
380 else if (strcmp(str, FLAG_FALSE) == 0)
382 info->prop_mod = FALSE;
386 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
387 _("Invalid prop-mod flag in rev-file"));
390 /* Get the mergeinfo-mod flag if given. Otherwise, the next thing
391 is the path starting with a slash. Also, we must initialize the
392 flag explicitly because 0 is not valid for a svn_tristate_t. */
393 info->mergeinfo_mod = svn_tristate_unknown;
394 if (*last_str != '/')
396 str = svn_cstring_tokenize(" ", &last_str);
398 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
399 _("Invalid changes line in rev-file"));
401 if (strcmp(str, FLAG_TRUE) == 0)
403 info->mergeinfo_mod = svn_tristate_true;
405 else if (strcmp(str, FLAG_FALSE) == 0)
407 info->mergeinfo_mod = svn_tristate_false;
411 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
412 _("Invalid mergeinfo-mod flag in rev-file"));
416 /* Get the changed path. */
417 if (!svn_fspath__is_canonical(last_str))
418 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
419 _("Invalid path in changes line"));
421 change->path.len = strlen(last_str);
422 change->path.data = apr_pstrdup(result_pool, last_str);
424 /* Read the next line, the copyfrom line. */
425 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
426 info->copyfrom_known = TRUE;
427 if (eof || line->len == 0)
429 info->copyfrom_rev = SVN_INVALID_REVNUM;
430 info->copyfrom_path = NULL;
434 last_str = line->data;
435 SVN_ERR(parse_revnum(&info->copyfrom_rev, (const char **)&last_str));
437 if (!svn_fspath__is_canonical(last_str))
438 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
439 _("Invalid copy-from path in changes line"));
441 info->copyfrom_path = apr_pstrdup(result_pool, last_str);
450 svn_fs_fs__read_changes(apr_array_header_t **changes,
451 svn_stream_t *stream,
452 apr_pool_t *result_pool,
453 apr_pool_t *scratch_pool)
456 apr_pool_t *iterpool;
458 /* Pre-allocate enough room for most change lists.
459 (will be auto-expanded as necessary).
461 Chose the default to just below 2^N such that the doubling reallocs
462 will request roughly 2^M bytes from the OS without exceeding the
463 respective two-power by just a few bytes (leaves room array and APR
464 node overhead for large enough M).
466 *changes = apr_array_make(result_pool, 63, sizeof(change_t *));
468 SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
469 iterpool = svn_pool_create(scratch_pool);
472 APR_ARRAY_PUSH(*changes, change_t*) = change;
473 SVN_ERR(read_change(&change, stream, result_pool, iterpool));
474 svn_pool_clear(iterpool);
476 svn_pool_destroy(iterpool);
482 svn_fs_fs__read_changes_incrementally(svn_stream_t *stream,
483 svn_fs_fs__change_receiver_t
485 void *change_receiver_baton,
486 apr_pool_t *scratch_pool)
489 apr_pool_t *iterpool;
491 iterpool = svn_pool_create(scratch_pool);
494 svn_pool_clear(iterpool);
496 SVN_ERR(read_change(&change, stream, iterpool, iterpool));
498 SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
501 svn_pool_destroy(iterpool);
506 /* Write a single change entry, path PATH, change CHANGE, to STREAM.
508 Only include the node kind field if INCLUDE_NODE_KIND is true. Only
509 include the mergeinfo-mod field if INCLUDE_MERGEINFO_MODS is true.
510 All temporary allocations are in SCRATCH_POOL. */
512 write_change_entry(svn_stream_t *stream,
514 svn_fs_path_change2_t *change,
515 svn_boolean_t include_node_kind,
516 svn_boolean_t include_mergeinfo_mods,
517 apr_pool_t *scratch_pool)
520 const char *change_string = NULL;
521 const char *kind_string = "";
522 const char *mergeinfo_string = "";
523 svn_stringbuf_t *buf;
526 switch (change->change_kind)
528 case svn_fs_path_change_modify:
529 change_string = ACTION_MODIFY;
531 case svn_fs_path_change_add:
532 change_string = ACTION_ADD;
534 case svn_fs_path_change_delete:
535 change_string = ACTION_DELETE;
537 case svn_fs_path_change_replace:
538 change_string = ACTION_REPLACE;
540 case svn_fs_path_change_reset:
541 change_string = ACTION_RESET;
544 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
545 _("Invalid change type %d"),
546 change->change_kind);
549 if (change->node_rev_id)
550 idstr = svn_fs_fs__id_unparse(change->node_rev_id, scratch_pool)->data;
552 idstr = ACTION_RESET;
554 if (include_node_kind)
556 SVN_ERR_ASSERT(change->node_kind == svn_node_dir
557 || change->node_kind == svn_node_file);
558 kind_string = apr_psprintf(scratch_pool, "-%s",
559 change->node_kind == svn_node_dir
560 ? SVN_FS_FS__KIND_DIR
561 : SVN_FS_FS__KIND_FILE);
564 if (include_mergeinfo_mods && change->mergeinfo_mod != svn_tristate_unknown)
565 mergeinfo_string = apr_psprintf(scratch_pool, " %s",
566 change->mergeinfo_mod == svn_tristate_true
570 buf = svn_stringbuf_createf(scratch_pool, "%s %s%s %s %s%s %s\n",
571 idstr, change_string, kind_string,
572 change->text_mod ? FLAG_TRUE : FLAG_FALSE,
573 change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
577 if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
579 svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
580 change->copyfrom_rev,
581 change->copyfrom_path));
584 svn_stringbuf_appendbyte(buf, '\n');
586 /* Write all change info in one write call. */
588 return svn_error_trace(svn_stream_write(stream, buf->data, &len));
592 svn_fs_fs__write_changes(svn_stream_t *stream,
595 svn_boolean_t terminate_list,
596 apr_pool_t *scratch_pool)
598 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
599 fs_fs_data_t *ffd = fs->fsap_data;
600 svn_boolean_t include_node_kinds =
601 ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
602 svn_boolean_t include_mergeinfo_mods =
603 ffd->format >= SVN_FS_FS__MIN_MERGEINFO_IN_CHANGED_FORMAT;
604 apr_array_header_t *sorted_changed_paths;
607 /* For the sake of the repository administrator sort the changes so
608 that the final file is deterministic and repeatable, however the
609 rest of the FSFS code doesn't require any particular order here.
611 Also, this sorting is only effective in writing all entries with
612 a single call as write_final_changed_path_info() does. For the
613 list being written incrementally during transaction, we actually
614 *must not* change the order of entries from different calls.
616 sorted_changed_paths = svn_sort__hash(changes,
617 svn_sort_compare_items_lexically,
620 /* Write all items to disk in the new order. */
621 for (i = 0; i < sorted_changed_paths->nelts; ++i)
623 svn_fs_path_change2_t *change;
626 svn_pool_clear(iterpool);
628 change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
629 path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
631 /* Write out the new entry into the final rev-file. */
632 SVN_ERR(write_change_entry(stream, path, change, include_node_kinds,
633 include_mergeinfo_mods, iterpool));
637 svn_stream_puts(stream, "\n");
639 svn_pool_destroy(iterpool);
644 /* Given a revision file FILE that has been pre-positioned at the
645 beginning of a Node-Rev header block, read in that header block and
646 store it in the apr_hash_t HEADERS. All allocations will be from
649 read_header_block(apr_hash_t **headers,
650 svn_stream_t *stream,
651 apr_pool_t *result_pool)
653 *headers = svn_hash__make(result_pool);
657 svn_stringbuf_t *header_str;
658 const char *name, *value;
663 SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
666 if (eof || header_str->len == 0)
667 break; /* end of header block */
669 while (header_str->data[i] != ':')
671 if (header_str->data[i] == '\0')
672 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
673 _("Found malformed header '%s' in "
679 /* Create a 'name' string and point to it. */
680 header_str->data[i] = '\0';
681 name = header_str->data;
684 /* Check if we have enough data to parse. */
685 if (i + 2 > header_str->len)
687 /* Restore the original line for the error. */
688 header_str->data[i] = ':';
689 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
690 _("Found malformed header '%s' in "
695 /* Skip over the NULL byte and the space following it. */
698 value = header_str->data + i;
700 /* header_str is safely in our pool, so we can use bits of it as
702 apr_hash_set(*headers, name, name_len, value);
709 svn_fs_fs__parse_representation(representation_t **rep_p,
710 svn_stringbuf_t *text,
711 apr_pool_t *result_pool,
712 apr_pool_t *scratch_pool)
714 representation_t *rep;
717 char *string = text->data;
718 svn_checksum_t *checksum;
721 rep = apr_pcalloc(result_pool, sizeof(*rep));
724 SVN_ERR(parse_revnum(&rep->revision, (const char **)&string));
726 /* initialize transaction info (never stored) */
727 svn_fs_fs__id_txn_reset(&rep->txn_id);
729 /* while in transactions, it is legal to simply write "-1" */
730 str = svn_cstring_tokenize(" ", &string);
733 if (rep->revision == SVN_INVALID_REVNUM)
736 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
737 _("Malformed text representation offset line in node-rev"));
740 SVN_ERR(svn_cstring_atoi64(&val, str));
741 rep->item_index = (apr_uint64_t)val;
743 str = svn_cstring_tokenize(" ", &string);
745 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
746 _("Malformed text representation offset line in node-rev"));
748 SVN_ERR(svn_cstring_atoi64(&val, str));
749 rep->size = (svn_filesize_t)val;
751 str = svn_cstring_tokenize(" ", &string);
753 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
754 _("Malformed text representation offset line in node-rev"));
756 SVN_ERR(svn_cstring_atoi64(&val, str));
757 rep->expanded_size = (svn_filesize_t)val;
759 /* Read in the MD5 hash. */
760 str = svn_cstring_tokenize(" ", &string);
761 if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
762 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
763 _("Malformed text representation offset line in node-rev"));
765 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
767 memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
769 /* The remaining fields are only used for formats >= 4, so check that. */
770 str = svn_cstring_tokenize(" ", &string);
774 /* Read the SHA1 hash. */
775 if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
776 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
777 _("Malformed text representation offset line in node-rev"));
779 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
781 rep->has_sha1 = checksum != NULL;
782 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
784 /* Read the uniquifier. */
785 str = svn_cstring_tokenize("/", &string);
787 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
788 _("Malformed text representation offset line in node-rev"));
790 SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
792 str = svn_cstring_tokenize(" ", &string);
793 if (str == NULL || *str != '_')
794 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
795 _("Malformed text representation offset line in node-rev"));
798 rep->uniquifier.number = svn__base36toui64(&end, str);
801 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
802 _("Malformed text representation offset line in node-rev"));
807 /* Wrap svn_fs_fs__parse_representation(), extracting its TXN_ID from our
808 NODEREV_ID, and adding an error message. */
810 read_rep_offsets(representation_t **rep_p,
812 const svn_fs_id_t *noderev_id,
813 apr_pool_t *result_pool,
814 apr_pool_t *scratch_pool)
817 = svn_fs_fs__parse_representation(rep_p,
818 svn_stringbuf_create_wrap(string,
824 const svn_string_t *id_unparsed;
827 id_unparsed = svn_fs_fs__id_unparse(noderev_id, scratch_pool);
828 where = apr_psprintf(scratch_pool,
829 _("While reading representation offsets "
830 "for node-revision '%s':"),
831 noderev_id ? id_unparsed->data : "(null)");
833 return svn_error_quick_wrap(err, where);
836 if ((*rep_p)->revision == SVN_INVALID_REVNUM)
838 (*rep_p)->txn_id = *svn_fs_fs__id_txn_id(noderev_id);
844 svn_fs_fs__read_noderev(node_revision_t **noderev_p,
845 svn_stream_t *stream,
846 apr_pool_t *result_pool,
847 apr_pool_t *scratch_pool)
850 node_revision_t *noderev;
852 const char *noderev_id;
854 SVN_ERR(read_header_block(&headers, stream, scratch_pool));
856 noderev = apr_pcalloc(result_pool, sizeof(*noderev));
858 /* Read the node-rev id. */
859 value = svn_hash_gets(headers, HEADER_ID);
861 /* ### More information: filename/offset coordinates */
862 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
863 _("Missing id field in node-rev"));
865 SVN_ERR(svn_stream_close(stream));
867 SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_pool));
868 noderev_id = value; /* for error messages later */
871 value = svn_hash_gets(headers, HEADER_TYPE);
873 if ((value == NULL) ||
874 ( strcmp(value, SVN_FS_FS__KIND_FILE)
875 && strcmp(value, SVN_FS_FS__KIND_DIR)))
876 /* ### s/kind/type/ */
877 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
878 _("Missing kind field in node-rev '%s'"),
881 noderev->kind = (strcmp(value, SVN_FS_FS__KIND_FILE) == 0)
885 /* Read the 'count' field. */
886 value = svn_hash_gets(headers, HEADER_COUNT);
888 SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
890 noderev->predecessor_count = 0;
892 /* Get the properties location. */
893 value = svn_hash_gets(headers, HEADER_PROPS);
896 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
897 noderev->id, result_pool, scratch_pool));
900 /* Get the data location. */
901 value = svn_hash_gets(headers, HEADER_TEXT);
904 SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
905 noderev->id, result_pool, scratch_pool));
908 /* Get the created path. */
909 value = svn_hash_gets(headers, HEADER_CPATH);
912 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
913 _("Missing cpath field in node-rev '%s'"),
918 if (!svn_fspath__is_canonical(value))
919 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
920 _("Non-canonical cpath field in node-rev '%s'"),
923 noderev->created_path = apr_pstrdup(result_pool, value);
926 /* Get the predecessor ID. */
927 value = svn_hash_gets(headers, HEADER_PRED);
929 SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value,
932 /* Get the copyroot. */
933 value = svn_hash_gets(headers, HEADER_COPYROOT);
936 noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path);
937 noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
941 SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
943 if (!svn_fspath__is_canonical(value))
944 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
945 _("Malformed copyroot line in node-rev '%s'"),
947 noderev->copyroot_path = apr_pstrdup(result_pool, value);
950 /* Get the copyfrom. */
951 value = svn_hash_gets(headers, HEADER_COPYFROM);
954 noderev->copyfrom_path = NULL;
955 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
959 SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
962 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
963 _("Malformed copyfrom line in node-rev '%s'"),
965 noderev->copyfrom_path = apr_pstrdup(result_pool, value);
968 /* Get whether this is a fresh txn root. */
969 value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
970 noderev->is_fresh_txn_root = (value != NULL);
972 /* Get the mergeinfo count. */
973 value = svn_hash_gets(headers, HEADER_MINFO_CNT);
975 SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
977 noderev->mergeinfo_count = 0;
979 /* Get whether *this* node has mergeinfo. */
980 value = svn_hash_gets(headers, HEADER_MINFO_HERE);
981 noderev->has_mergeinfo = (value != NULL);
983 *noderev_p = noderev;
988 /* Return a textual representation of the DIGEST of given KIND.
989 * If IS_NULL is TRUE, no digest is available.
990 * Allocate the result in RESULT_POOL.
993 format_digest(const unsigned char *digest,
994 svn_checksum_kind_t kind,
995 svn_boolean_t is_null,
996 apr_pool_t *result_pool)
998 svn_checksum_t checksum;
999 checksum.digest = digest;
1000 checksum.kind = kind;
1005 return svn_checksum_to_cstring_display(&checksum, result_pool);
1009 svn_fs_fs__unparse_representation(representation_t *rep,
1011 svn_boolean_t mutable_rep_truncated,
1012 apr_pool_t *result_pool,
1013 apr_pool_t *scratch_pool)
1015 char buffer[SVN_INT64_BUFFER_SIZE];
1016 if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated)
1017 return svn_stringbuf_ncreate("-1", 2, result_pool);
1019 if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || !rep->has_sha1)
1020 return svn_stringbuf_createf
1021 (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
1022 " %" SVN_FILESIZE_T_FMT " %s",
1023 rep->revision, rep->item_index, rep->size,
1025 format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
1028 svn__ui64tobase36(buffer, rep->uniquifier.number);
1029 return svn_stringbuf_createf
1030 (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
1031 " %" SVN_FILESIZE_T_FMT " %s %s %s/_%s",
1032 rep->revision, rep->item_index, rep->size,
1034 format_digest(rep->md5_digest, svn_checksum_md5,
1035 FALSE, scratch_pool),
1036 format_digest(rep->sha1_digest, svn_checksum_sha1,
1037 !rep->has_sha1, scratch_pool),
1038 svn_fs_fs__id_txn_unparse(&rep->uniquifier.noderev_txn_id,
1045 svn_fs_fs__write_noderev(svn_stream_t *outfile,
1046 node_revision_t *noderev,
1048 svn_boolean_t include_mergeinfo,
1049 apr_pool_t *scratch_pool)
1051 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
1052 svn_fs_fs__id_unparse(noderev->id,
1053 scratch_pool)->data));
1055 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
1056 (noderev->kind == svn_node_file) ?
1057 SVN_FS_FS__KIND_FILE : SVN_FS_FS__KIND_DIR));
1059 if (noderev->predecessor_id)
1060 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
1061 svn_fs_fs__id_unparse(noderev->predecessor_id,
1062 scratch_pool)->data));
1064 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
1065 noderev->predecessor_count));
1067 if (noderev->data_rep)
1068 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
1069 svn_fs_fs__unparse_representation
1072 noderev->kind == svn_node_dir,
1073 scratch_pool, scratch_pool)->data));
1075 if (noderev->prop_rep)
1076 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
1077 svn_fs_fs__unparse_representation
1078 (noderev->prop_rep, format,
1079 TRUE, scratch_pool, scratch_pool)->data));
1081 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
1082 noderev->created_path));
1084 if (noderev->copyfrom_path)
1085 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
1087 noderev->copyfrom_rev,
1088 noderev->copyfrom_path));
1090 if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
1091 (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
1092 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
1094 noderev->copyroot_rev,
1095 noderev->copyroot_path));
1097 if (noderev->is_fresh_txn_root)
1098 SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
1100 if (include_mergeinfo)
1102 if (noderev->mergeinfo_count > 0)
1103 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT
1104 ": %" APR_INT64_T_FMT "\n",
1105 noderev->mergeinfo_count));
1107 if (noderev->has_mergeinfo)
1108 SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
1111 return svn_stream_puts(outfile, "\n");
1115 svn_fs_fs__read_rep_header(svn_fs_fs__rep_header_t **header,
1116 svn_stream_t *stream,
1117 apr_pool_t *result_pool,
1118 apr_pool_t *scratch_pool)
1120 svn_stringbuf_t *buffer;
1121 char *str, *last_str;
1123 svn_boolean_t eol = FALSE;
1125 SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
1127 *header = apr_pcalloc(result_pool, sizeof(**header));
1128 (*header)->header_size = buffer->len + 1;
1129 if (strcmp(buffer->data, REP_PLAIN) == 0)
1131 (*header)->type = svn_fs_fs__rep_plain;
1132 return SVN_NO_ERROR;
1135 if (strcmp(buffer->data, REP_DELTA) == 0)
1137 /* This is a delta against the empty stream. */
1138 (*header)->type = svn_fs_fs__rep_self_delta;
1139 return SVN_NO_ERROR;
1142 (*header)->type = svn_fs_fs__rep_delta;
1144 /* We have hopefully a DELTA vs. a non-empty base revision. */
1145 last_str = buffer->data;
1146 str = svn_cstring_tokenize(" ", &last_str);
1147 if (! str || (strcmp(str, REP_DELTA) != 0))
1150 SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
1152 str = svn_cstring_tokenize(" ", &last_str);
1155 SVN_ERR(svn_cstring_atoi64(&val, str));
1156 (*header)->base_item_index = (apr_off_t)val;
1158 str = svn_cstring_tokenize(" ", &last_str);
1161 SVN_ERR(svn_cstring_atoi64(&val, str));
1162 (*header)->base_length = (svn_filesize_t)val;
1164 return SVN_NO_ERROR;
1167 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1168 _("Malformed representation header"));
1172 svn_fs_fs__write_rep_header(svn_fs_fs__rep_header_t *header,
1173 svn_stream_t *stream,
1174 apr_pool_t *scratch_pool)
1178 switch (header->type)
1180 case svn_fs_fs__rep_plain:
1181 text = REP_PLAIN "\n";
1184 case svn_fs_fs__rep_self_delta:
1185 text = REP_DELTA "\n";
1189 text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
1190 " %" SVN_FILESIZE_T_FMT "\n",
1191 header->base_revision, header->base_item_index,
1192 header->base_length);
1195 return svn_error_trace(svn_stream_puts(stream, text));