2 * subst.c : generic eol/keyword substitution routines
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #define APR_WANT_STRFUNC
31 #include <apr_pools.h>
32 #include <apr_tables.h>
33 #include <apr_file_io.h>
34 #include <apr_strings.h>
37 #include "svn_cmdline.h"
38 #include "svn_types.h"
39 #include "svn_string.h"
41 #include "svn_dirent_uri.h"
43 #include "svn_error.h"
46 #include "svn_subst.h"
47 #include "svn_pools.h"
48 #include "private/svn_io_private.h"
50 #include "svn_private_config.h"
52 #include "private/svn_string_private.h"
53 #include "private/svn_eol_private.h"
56 * The textual elements of a detranslated special file. One of these
57 * strings must appear as the first element of any special file as it
58 * exists in the repository or the text base.
60 #define SVN_SUBST__SPECIAL_LINK_STR "link"
63 svn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
69 /* property doesn't exist. */
72 *style = svn_subst_eol_style_none;
74 else if (! strcmp("native", value))
76 *eol = APR_EOL_STR; /* whee, a portability library! */
78 *style = svn_subst_eol_style_native;
80 else if (! strcmp("LF", value))
84 *style = svn_subst_eol_style_fixed;
86 else if (! strcmp("CR", value))
90 *style = svn_subst_eol_style_fixed;
92 else if (! strcmp("CRLF", value))
96 *style = svn_subst_eol_style_fixed;
102 *style = svn_subst_eol_style_unknown;
108 svn_subst_translation_required(svn_subst_eol_style_t style,
110 apr_hash_t *keywords,
111 svn_boolean_t special,
112 svn_boolean_t force_eol_check)
114 return (special || keywords
115 || (style != svn_subst_eol_style_none && force_eol_check)
116 || (style == svn_subst_eol_style_native &&
117 strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0)
118 || (style == svn_subst_eol_style_fixed &&
119 strcmp(APR_EOL_STR, eol) != 0));
124 /* Helper function for svn_subst_build_keywords */
126 /* Given a printf-like format string, return a string with proper
127 * information filled in.
129 * Important API note: This function is the core of the implementation of
130 * svn_subst_build_keywords (all versions), and as such must implement the
131 * tolerance of NULL and zero inputs that that function's documentation
136 * %a author of this revision
137 * %b basename of the URL of this file
138 * %d short format of date of this revision
139 * %D long format of date of this revision
140 * %P path relative to root of repos
141 * %r number of this revision
142 * %R root url of repository
143 * %u URL of this file
147 * The following special format codes are also recognized:
148 * %H is equivalent to %P%_%r%_%d%_%a
149 * %I is equivalent to %b%_%r%_%d%_%a
151 * All memory is allocated out of @a pool.
153 static svn_string_t *
154 keyword_printf(const char *fmt,
157 const char *repos_root_url,
162 svn_stringbuf_t *value = svn_stringbuf_create_empty(pool);
170 while (*cur != '\0' && *cur != '%')
173 if ((n = cur - fmt) > 0) /* Do we have an as-is string? */
174 svn_stringbuf_appendbytes(value, fmt, n);
181 case 'a': /* author of this revision */
183 svn_stringbuf_appendcstr(value, author);
185 case 'b': /* basename of this file */
188 const char *base_name = svn_uri_basename(url, pool);
189 svn_stringbuf_appendcstr(value, base_name);
192 case 'd': /* short format of date of this revision */
195 apr_time_exp_t exploded_time;
198 apr_time_exp_gmt(&exploded_time, date);
200 human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ",
201 exploded_time.tm_year + 1900,
202 exploded_time.tm_mon + 1,
203 exploded_time.tm_mday,
204 exploded_time.tm_hour,
205 exploded_time.tm_min,
206 exploded_time.tm_sec);
208 svn_stringbuf_appendcstr(value, human);
211 case 'D': /* long format of date of this revision */
213 svn_stringbuf_appendcstr(value,
214 svn_time_to_human_cstring(date, pool));
216 case 'P': /* relative path of this file */
217 if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0')
219 const char *repos_relpath;
221 repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool);
223 svn_stringbuf_appendcstr(value, repos_relpath);
226 case 'R': /* root of repos */
227 if (repos_root_url && *repos_root_url != '\0')
228 svn_stringbuf_appendcstr(value, repos_root_url);
230 case 'r': /* number of this revision */
232 svn_stringbuf_appendcstr(value, rev);
234 case 'u': /* URL of this file */
236 svn_stringbuf_appendcstr(value, url);
238 case '_': /* '%_' => a space */
239 svn_stringbuf_appendbyte(value, ' ');
241 case '%': /* '%%' => a literal % */
242 svn_stringbuf_appendbyte(value, *cur);
244 case '\0': /* '%' as the last character of the string. */
245 svn_stringbuf_appendbyte(value, *cur);
246 /* Now go back one character, since this was just a one character
247 * sequence, whereas all others are two characters, and we do not
248 * want to skip the null terminator entirely and carry on
249 * formatting random memory contents. */
254 svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url,
255 repos_root_url, date, author,
257 svn_stringbuf_appendcstr(value, s->data);
262 svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url,
263 repos_root_url, date, author,
265 svn_stringbuf_appendcstr(value, s->data);
268 default: /* Unrecognized code, just print it literally. */
269 svn_stringbuf_appendbytes(value, cur, 2);
273 /* Format code is processed - skip it, and get ready for next chunk. */
277 return svn_stringbuf__morph_into_string(value);
281 build_keywords(apr_hash_t **kw,
282 svn_boolean_t expand_custom_keywords,
283 const char *keywords_val,
286 const char *repos_root_url,
291 apr_array_header_t *keyword_tokens;
293 *kw = apr_hash_make(pool);
295 keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f",
296 TRUE /* chop */, pool);
298 for (i = 0; i < keyword_tokens->nelts; ++i)
300 const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *);
301 const char *custom_fmt = NULL;
303 if (expand_custom_keywords)
307 /* Check if there is a custom keyword definition, started by '='. */
308 sep = strchr(keyword, '=');
311 *sep = '\0'; /* Split keyword's name from custom format. */
312 custom_fmt = sep + 1;
318 svn_string_t *custom_val;
320 /* Custom keywords must be allowed to match the name of an
321 * existing fixed keyword. This is for compatibility purposes,
322 * in case new fixed keywords are added to Subversion which
323 * happen to match a custom keyword defined somewhere.
324 * There is only one global namespace for keyword names. */
325 custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url,
327 svn_hash_sets(*kw, keyword, custom_val);
329 else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG))
330 || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM))
331 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT)))
333 svn_string_t *revision_val;
335 revision_val = keyword_printf("%r", rev, url, repos_root_url,
337 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val);
338 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val);
339 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val);
341 else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG))
342 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT)))
344 svn_string_t *date_val;
346 date_val = keyword_printf("%D", rev, url, repos_root_url, date,
348 svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val);
349 svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val);
351 else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG))
352 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT)))
354 svn_string_t *author_val;
356 author_val = keyword_printf("%a", rev, url, repos_root_url, date,
358 svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val);
359 svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val);
361 else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG))
362 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT)))
364 svn_string_t *url_val;
366 url_val = keyword_printf("%u", rev, url, repos_root_url, date,
368 svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val);
369 svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val);
371 else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID)))
373 svn_string_t *id_val;
375 id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url,
377 svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val);
379 else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER)))
381 svn_string_t *header_val;
383 header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url,
385 svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val);
393 svn_subst_build_keywords2(apr_hash_t **kw,
394 const char *keywords_val,
401 return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url,
402 NULL, date, author, pool));
407 svn_subst_build_keywords3(apr_hash_t **kw,
408 const char *keywords_val,
411 const char *repos_root_url,
416 return svn_error_trace(build_keywords(kw, TRUE, keywords_val,
417 rev, url, repos_root_url,
418 date, author, pool));
422 /*** Helpers for svn_subst_translate_stream2 ***/
425 /* Write out LEN bytes of BUF into STREAM. */
426 /* ### TODO: 'stream_write()' would be a better name for this. */
428 translate_write(svn_stream_t *stream,
432 SVN_ERR(svn_stream_write(stream, buf, &len));
433 /* (No need to check LEN, as a short write always produces an error.) */
438 /* Perform the substitution of VALUE into keyword string BUF (with len
439 *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
440 *LEN to the new size of the substituted result. Return TRUE if all
441 goes well, FALSE otherwise. If VALUE is NULL, keyword will be
442 contracted, else it will be expanded. */
444 translate_keyword_subst(char *buf,
447 apr_size_t keyword_len,
448 const svn_string_t *value)
452 /* Make sure we gotz good stuffs. */
453 assert(*len <= SVN_KEYWORD_MAX_LEN);
454 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
456 /* Need at least a keyword and two $'s. */
457 if (*len < keyword_len + 2)
460 /* Need at least space for two $'s, two spaces and a colon, and that
461 leaves zero space for the value itself. */
462 if (keyword_len > SVN_KEYWORD_MAX_LEN - 5)
465 /* The keyword needs to match what we're looking for. */
466 if (strncmp(buf + 1, keyword, keyword_len))
469 buf_ptr = buf + 1 + keyword_len;
471 /* Check for fixed-length expansion.
472 * The format of fixed length keyword and its data is
473 * Unexpanded keyword: "$keyword:: $"
474 * Expanded keyword: "$keyword:: value $"
475 * Expanded kw with filling: "$keyword:: value $"
476 * Truncated keyword: "$keyword:: longval#$"
478 if ((buf_ptr[0] == ':') /* first char after keyword is ':' */
479 && (buf_ptr[1] == ':') /* second char after keyword is ':' */
480 && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */
481 && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */
482 || (buf[*len - 2] == '#')) /* .. or has '#' for next to last
484 && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */
486 /* This is fixed length keyword, so *len remains unchanged */
487 apr_size_t max_value_len = *len - (6 + keyword_len);
491 /* no value, so unexpand */
493 while (*buf_ptr != '$')
498 if (value->len <= max_value_len)
499 { /* replacement not as long as template, pad with spaces */
500 strncpy(buf_ptr + 3, value->data, value->len);
501 buf_ptr += 3 + value->len;
502 while (*buf_ptr != '$')
507 /* replacement needs truncating */
508 strncpy(buf_ptr + 3, value->data, max_value_len);
516 /* Check for unexpanded keyword. */
517 else if (buf_ptr[0] == '$') /* "$keyword$" */
527 apr_size_t vallen = value->len;
529 /* "$keyword: value $" */
530 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
531 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
532 strncpy(buf_ptr + 2, value->data, vallen);
533 buf_ptr[2 + vallen] = ' ';
534 buf_ptr[2 + vallen + 1] = '$';
535 *len = 5 + keyword_len + vallen;
541 *len = 4 + keyword_len;
546 /* ...but do nothing. */
551 /* Check for expanded keyword. */
552 else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */
553 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
554 && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */
555 && (buf[*len - 2] == ' '))
556 || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */
557 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
558 && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */
563 /* ...so unexpand. */
565 *len = 2 + keyword_len;
569 /* ...so re-expand. */
574 apr_size_t vallen = value->len;
576 /* "$keyword: value $" */
577 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
578 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
579 strncpy(buf_ptr + 2, value->data, vallen);
580 buf_ptr[2 + vallen] = ' ';
581 buf_ptr[2 + vallen + 1] = '$';
582 *len = 5 + keyword_len + vallen;
588 *len = 4 + keyword_len;
597 /* Parse BUF (whose length is LEN, and which starts and ends with '$'),
598 trying to match one of the keyword names in KEYWORDS. If such a
599 keyword is found, update *KEYWORD_NAME with the keyword name and
602 match_keyword(char *buf,
605 apr_hash_t *keywords)
609 /* Early return for ignored keywords */
613 /* Extract the name of the keyword */
614 for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++)
615 keyword_name[i] = buf[i + 1];
616 keyword_name[i] = '\0';
618 return svn_hash_gets(keywords, keyword_name) != NULL;
621 /* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
622 optionally perform the substitution in place, update *LEN with
623 the new length of the translated keyword string, and return TRUE.
624 If this buffer doesn't contain a known keyword pattern, leave BUF
625 and *LEN untouched and return FALSE.
627 See the docstring for svn_subst_copy_and_translate for how the
628 EXPAND and KEYWORDS parameters work.
630 NOTE: It is assumed that BUF has been allocated to be at least
631 SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
632 than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions
633 which would result in a keyword string which is greater than
634 SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
635 that the resultant keyword string is still valid (begins with
636 "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */
638 translate_keyword(char *buf,
640 const char *keyword_name,
641 svn_boolean_t expand,
642 apr_hash_t *keywords)
644 const svn_string_t *value;
646 /* Make sure we gotz good stuffs. */
647 assert(*len <= SVN_KEYWORD_MAX_LEN);
648 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
650 /* Early return for ignored keywords */
654 value = svn_hash_gets(keywords, keyword_name);
658 return translate_keyword_subst(buf, len,
659 keyword_name, strlen(keyword_name),
660 expand ? value : NULL);
666 /* A boolean expression that evaluates to true if the first STR_LEN characters
667 of the string STR are one of the end-of-line strings LF, CR, or CRLF;
668 to false otherwise. */
669 #define STRING_IS_EOL(str, str_len) \
670 (((str_len) == 2 && (str)[0] == '\r' && (str)[1] == '\n') || \
671 ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r')))
673 /* A boolean expression that evaluates to true if the end-of-line string EOL1,
674 having length EOL1_LEN, and the end-of-line string EOL2, having length
675 EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the
676 set {"\n", "\r", "\r\n"}; to false otherwise.
678 Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if
679 EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course
680 different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both
681 "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1.
682 We need only check the one character for equality to determine whether
683 EOL1 and EOL2 are different in that case. */
684 #define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \
685 (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2)))
688 /* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to
689 the newline string EOL_STR (of length EOL_STR_LEN), writing the
690 result (which is always EOL_STR) to the stream DST.
692 This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n".
694 Also check for consistency of the source newline strings across
695 multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache
696 of the first newline found. If the current newline is not the same
697 as SRC_FORMAT, look to the REPAIR parameter. If REPAIR is TRUE,
698 ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL
699 error. If *SRC_FORMAT_LEN is 0, assume we are examining the first
700 newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
701 use for later consistency checks.
703 If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the
704 newline string that was written (EOL_STR) is not the same as the newline
705 string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL
708 Note: all parameters are required even if REPAIR is TRUE.
709 ### We could require that REPAIR must not change across a sequence of
710 calls, and could then optimize by not using SRC_FORMAT at all if
714 translate_newline(const char *eol_str,
715 apr_size_t eol_str_len,
717 apr_size_t *src_format_len,
718 const char *newline_buf,
719 apr_size_t newline_len,
721 svn_boolean_t *translated_eol,
722 svn_boolean_t repair)
724 SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len));
726 /* If we've seen a newline before, compare it with our cache to
727 check for consistency, else cache it for future comparisons. */
730 /* Comparing with cache. If we are inconsistent and
731 we are NOT repairing the file, generate an error! */
732 if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len,
733 newline_buf, newline_len))
734 return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL);
738 /* This is our first line ending, so cache it before
740 strncpy(src_format, newline_buf, newline_len);
741 *src_format_len = newline_len;
744 /* Write the desired newline */
745 SVN_ERR(translate_write(dst, eol_str, eol_str_len));
747 /* Report whether we translated it. Note: Not using DIFFERENT_EOL_STRINGS()
748 * because EOL_STR may not be a valid EOL sequence. */
749 if (translated_eol != NULL &&
750 (eol_str_len != newline_len ||
751 memcmp(eol_str, newline_buf, eol_str_len) != 0))
752 *translated_eol = TRUE;
759 /*** Public interfaces. ***/
762 svn_subst_keywords_differ(const svn_subst_keywords_t *a,
763 const svn_subst_keywords_t *b,
764 svn_boolean_t compare_values)
766 if (((a == NULL) && (b == NULL)) /* no A or B */
767 /* no A, and B has no contents */
769 && (b->revision == NULL)
771 && (b->author == NULL)
773 /* no B, and A has no contents */
774 || ((b == NULL) && (a->revision == NULL)
776 && (a->author == NULL)
778 /* neither A nor B has any contents */
779 || ((a != NULL) && (b != NULL)
780 && (b->revision == NULL)
782 && (b->author == NULL)
784 && (a->revision == NULL)
786 && (a->author == NULL)
787 && (a->url == NULL)))
791 else if ((a == NULL) || (b == NULL))
794 /* Else both A and B have some keywords. */
796 if ((! a->revision) != (! b->revision))
798 else if ((compare_values && (a->revision != NULL))
799 && (strcmp(a->revision->data, b->revision->data) != 0))
802 if ((! a->date) != (! b->date))
804 else if ((compare_values && (a->date != NULL))
805 && (strcmp(a->date->data, b->date->data) != 0))
808 if ((! a->author) != (! b->author))
810 else if ((compare_values && (a->author != NULL))
811 && (strcmp(a->author->data, b->author->data) != 0))
814 if ((! a->url) != (! b->url))
816 else if ((compare_values && (a->url != NULL))
817 && (strcmp(a->url->data, b->url->data) != 0))
820 /* Else we never found a difference, so they must be the same. */
826 svn_subst_keywords_differ2(apr_hash_t *a,
828 svn_boolean_t compare_values,
831 apr_hash_index_t *hi;
832 unsigned int a_count, b_count;
834 /* An empty hash is logically equal to a NULL,
835 * as far as this API is concerned. */
836 a_count = (a == NULL) ? 0 : apr_hash_count(a);
837 b_count = (b == NULL) ? 0 : apr_hash_count(b);
839 if (a_count != b_count)
845 /* The hashes are both non-NULL, and have the same number of items.
846 * We must check that every item of A is present in B. */
847 for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
852 svn_string_t *a_val, *b_val;
854 apr_hash_this(hi, &key, &klen, &void_a_val);
856 b_val = apr_hash_get(b, key, klen);
858 if (!b_val || (compare_values && !svn_string_compare(a_val, b_val)))
866 /* Baton for translate_chunk() to store its state in. */
867 struct translation_baton
870 svn_boolean_t *translated_eol;
871 svn_boolean_t repair;
872 apr_hash_t *keywords;
873 svn_boolean_t expand;
875 /* 'short boolean' array that encodes what character values
876 may trigger a translation action, hence are 'interesting' */
877 char interesting[256];
879 /* Length of the string EOL_STR points to. */
880 apr_size_t eol_str_len;
882 /* Buffer to cache any newline state between translation chunks */
885 /* Offset (within newline_buf) of the first *unused* character */
886 apr_size_t newline_off;
888 /* Buffer to cache keyword-parsing state between translation chunks */
889 char keyword_buf[SVN_KEYWORD_MAX_LEN];
891 /* Offset (within keyword-buf) to the first *unused* character */
892 apr_size_t keyword_off;
894 /* EOL style used in the chunk-source */
897 /* Length of the EOL style string found in the chunk-source,
898 or zero if none encountered yet */
899 apr_size_t src_format_len;
901 /* If this is svn_tristate_false, translate_newline() will be called
902 for every newline in the file */
903 svn_tristate_t nl_translation_skippable;
907 /* Allocate a baton for use with translate_chunk() in POOL and
908 * initialize it for the first iteration.
910 * The caller must assure that EOL_STR and KEYWORDS at least
911 * have the same life time as that of POOL.
913 static struct translation_baton *
914 create_translation_baton(const char *eol_str,
915 svn_boolean_t *translated_eol,
916 svn_boolean_t repair,
917 apr_hash_t *keywords,
918 svn_boolean_t expand,
921 struct translation_baton *b = apr_palloc(pool, sizeof(*b));
923 /* For efficiency, convert an empty set of keywords to NULL. */
924 if (keywords && (apr_hash_count(keywords) == 0))
927 b->eol_str = eol_str;
928 b->eol_str_len = eol_str ? strlen(eol_str) : 0;
929 b->translated_eol = translated_eol;
931 b->keywords = keywords;
935 b->src_format_len = 0;
936 b->nl_translation_skippable = svn_tristate_unknown;
938 /* Most characters don't start translation actions.
939 * Mark those that do depending on the parameters we got. */
940 memset(b->interesting, FALSE, sizeof(b->interesting));
942 b->interesting['$'] = TRUE;
945 b->interesting['\r'] = TRUE;
946 b->interesting['\n'] = TRUE;
952 /* Return TRUE if the EOL starting at BUF matches the eol_str member of B.
953 * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like
954 * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is
955 * more efficient to handle that special case implicitly in the calling code
956 * by exiting the quick scan loop.
957 * The caller must ensure that buf[0] and buf[1] refer to valid memory
960 static APR_INLINE svn_boolean_t
961 eol_unchanged(struct translation_baton *b,
964 /* If the first byte doesn't match, the whole EOL won't.
965 * This does also handle the (certainly invalid) case that
966 * eol_str would be an empty string.
968 if (buf[0] != b->eol_str[0])
971 /* two-char EOLs must be a full match */
972 if (b->eol_str_len == 2)
973 return buf[1] == b->eol_str[1];
975 /* The first char matches the required 1-byte EOL.
976 * But maybe, buf[] contains a 2-byte EOL?
977 * In that case, the second byte will be interesting
978 * and not be another EOL of its own.
980 return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1];
984 /* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
985 * according to the settings and state stored in baton B.
987 * Write output to stream DST.
989 * To finish a series of chunk translations, flush all buffers by calling
990 * this routine with a NULL value for BUF.
992 * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if
993 * an end-of-line sequence was changed, otherwise leave it untouched.
995 * Use POOL for temporary allocations.
998 translate_chunk(svn_stream_t *dst,
999 struct translation_baton *b,
1009 /* precalculate some oft-used values */
1010 const char *end = buf + buflen;
1011 const char *interesting = b->interesting;
1012 apr_size_t next_sign_off = 0;
1014 /* At the beginning of this loop, assume that we might be in an
1015 * interesting state, i.e. with data in the newline or keyword
1016 * buffer. First try to get to the boring state so we can copy
1017 * a run of boring characters; then try to get back to the
1018 * interesting state by processing an interesting character,
1020 for (p = buf; p < end;)
1022 /* Try to get to the boring state, if necessary. */
1026 b->newline_buf[b->newline_off++] = *p++;
1028 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1030 &b->src_format_len, b->newline_buf,
1031 b->newline_off, dst, b->translated_eol,
1036 else if (b->keyword_off && *p == '$')
1038 svn_boolean_t keyword_matches;
1039 char keyword_name[SVN_KEYWORD_MAX_LEN + 1];
1041 /* If keyword is matched, but not correctly translated, try to
1042 * look for the next ending '$'. */
1043 b->keyword_buf[b->keyword_off++] = *p++;
1044 keyword_matches = match_keyword(b->keyword_buf, b->keyword_off,
1045 keyword_name, b->keywords);
1046 if (!keyword_matches)
1048 /* reuse the ending '$' */
1053 if (!keyword_matches ||
1054 translate_keyword(b->keyword_buf, &b->keyword_off,
1055 keyword_name, b->expand, b->keywords) ||
1056 b->keyword_off >= SVN_KEYWORD_MAX_LEN)
1058 /* write out non-matching text or translated keyword */
1059 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1066 if (next_sign_off == 0)
1067 next_sign_off = b->keyword_off - 1;
1072 else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1
1073 || (b->keyword_off && (*p == '\r' || *p == '\n')))
1075 if (next_sign_off > 0)
1077 /* rolling back, continue with next '$' in keyword_buf */
1078 p -= (b->keyword_off - next_sign_off);
1079 b->keyword_off = next_sign_off;
1082 /* No closing '$' found; flush the keyword buffer. */
1083 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1087 else if (b->keyword_off)
1089 b->keyword_buf[b->keyword_off++] = *p++;
1093 /* translate_newline will modify the baton for src_format_len==0
1094 or may return an error if b->repair is FALSE. In all other
1095 cases, we can skip the newline translation as long as source
1096 EOL format and actual EOL format match. If there is a
1097 mismatch, translate_newline will be called regardless of
1098 nl_translation_skippable.
1100 if (b->nl_translation_skippable == svn_tristate_unknown &&
1101 b->src_format_len > 0)
1103 /* test whether translate_newline may return an error */
1104 if (b->eol_str_len == b->src_format_len &&
1105 strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0)
1106 b->nl_translation_skippable = svn_tristate_true;
1108 b->nl_translation_skippable = svn_tristate_true;
1110 b->nl_translation_skippable = svn_tristate_false;
1113 /* We're in the boring state; look for interesting characters.
1114 Offset len such that it will become 0 in the first iteration.
1116 len = 0 - b->eol_str_len;
1118 /* Look for the next EOL (or $) that actually needs translation.
1119 Stop there or at EOF, whichever is encountered first.
1123 /* skip current EOL */
1124 len += b->eol_str_len;
1128 /* Check 4 bytes at once to allow for efficient pipelining
1129 and to reduce loop condition overhead. */
1130 while ((end - p) >= (len + 4))
1132 if (interesting[(unsigned char)p[len]]
1133 || interesting[(unsigned char)p[len+1]]
1134 || interesting[(unsigned char)p[len+2]]
1135 || interesting[(unsigned char)p[len+3]])
1141 /* Found an interesting char or EOF in the next 4 bytes.
1142 Find its exact position. */
1143 while ((p + len) < end
1144 && !interesting[(unsigned char)p[len]])
1149 /* use our optimized sub-routine to find the next EOL */
1150 const char *start = p + len;
1152 = svn_eol__find_eol_start((char *)start, end - start);
1154 /* EOL will be NULL if we did not find a line ending */
1155 len += (eol ? eol : end) - start;
1158 while (b->nl_translation_skippable ==
1159 svn_tristate_true && /* can potentially skip EOLs */
1160 (end - p) > (len + 2) && /* not too close to EOF */
1161 eol_unchanged(b, p + len)); /* EOL format already ok */
1163 while ((p + len) < end && !interesting[(unsigned char)p[len]])
1168 SVN_ERR(translate_write(dst, p, len));
1172 /* Set up state according to the interesting character, if any. */
1178 b->keyword_buf[b->keyword_off++] = *p++;
1181 b->newline_buf[b->newline_off++] = *p++;
1184 b->newline_buf[b->newline_off++] = *p++;
1186 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1190 b->newline_off, dst,
1191 b->translated_eol, b->repair));
1204 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1205 b->src_format, &b->src_format_len,
1206 b->newline_buf, b->newline_off,
1207 dst, b->translated_eol, b->repair));
1213 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1218 return SVN_NO_ERROR;
1221 /* Baton for use with translated stream callbacks. */
1222 struct translated_stream_baton
1224 /* Stream to take input from (before translation) on read
1225 /write output to (after translation) on write. */
1226 svn_stream_t *stream;
1228 /* Input/Output translation batons to make them separate chunk streams. */
1229 struct translation_baton *in_baton, *out_baton;
1231 /* Remembers whether any write operations have taken place;
1232 if so, we need to flush the output chunk stream. */
1233 svn_boolean_t written;
1235 /* Buffer to hold translated read data. */
1236 svn_stringbuf_t *readbuf;
1238 /* Offset of the first non-read character in readbuf. */
1239 apr_size_t readbuf_off;
1241 /* Buffer to hold read data
1242 between svn_stream_read() and translate_chunk(). */
1244 #define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1)
1246 /* Pool for callback iterations */
1247 apr_pool_t *iterpool;
1251 /* Implements svn_read_fn_t. */
1252 static svn_error_t *
1253 translated_stream_read(void *baton,
1257 struct translated_stream_baton *b = baton;
1258 apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1259 apr_size_t unsatisfied = *len;
1262 /* Optimization for a frequent special case. The configuration parser (and
1263 a few others) reads the stream one byte at a time. All the memcpy, pool
1264 clearing etc. imposes a huge overhead in that case. In most cases, we
1265 can just take that single byte directly from the read buffer.
1267 Since *len > 1 requires lots of code to be run anyways, we can afford
1268 the extra overhead of checking for *len == 1.
1270 See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>.
1272 if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len)
1274 /* Just take it from the read buffer */
1275 *buffer = b->readbuf->data[b->readbuf_off++];
1277 return SVN_NO_ERROR;
1280 /* Standard code path. */
1281 while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0)
1284 apr_size_t buffer_remainder;
1286 svn_pool_clear(b->iterpool);
1287 /* fill read buffer, if necessary */
1288 if (! (b->readbuf_off < b->readbuf->len))
1290 svn_stream_t *buf_stream;
1292 svn_stringbuf_setempty(b->readbuf);
1294 SVN_ERR(svn_stream_read_full(b->stream, b->buf, &readlen));
1295 buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool);
1297 SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf,
1298 readlen, b->iterpool));
1300 if (readlen != SVN__STREAM_CHUNK_SIZE)
1301 SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0,
1304 SVN_ERR(svn_stream_close(buf_stream));
1307 /* Satisfy from the read buffer */
1308 buffer_remainder = b->readbuf->len - b->readbuf_off;
1309 to_copy = (buffer_remainder > unsatisfied)
1310 ? unsatisfied : buffer_remainder;
1311 memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy);
1313 b->readbuf_off += to_copy;
1314 unsatisfied -= to_copy;
1317 *len -= unsatisfied;
1319 return SVN_NO_ERROR;
1322 /* Implements svn_write_fn_t. */
1323 static svn_error_t *
1324 translated_stream_write(void *baton,
1328 struct translated_stream_baton *b = baton;
1329 svn_pool_clear(b->iterpool);
1332 return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool);
1335 /* Implements svn_close_fn_t. */
1336 static svn_error_t *
1337 translated_stream_close(void *baton)
1339 struct translated_stream_baton *b = baton;
1340 svn_error_t *err = NULL;
1343 err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool);
1345 err = svn_error_compose_create(err, svn_stream_close(b->stream));
1347 svn_pool_destroy(b->iterpool);
1349 return svn_error_trace(err);
1353 /* svn_stream_mark_t for translation streams. */
1354 typedef struct mark_translated_t
1356 /* Saved translation state. */
1357 struct translated_stream_baton saved_baton;
1359 /* Mark set on the underlying stream. */
1360 svn_stream_mark_t *mark;
1361 } mark_translated_t;
1363 /* Implements svn_stream_mark_fn_t. */
1364 static svn_error_t *
1365 translated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
1367 mark_translated_t *mt;
1368 struct translated_stream_baton *b = baton;
1370 mt = apr_palloc(pool, sizeof(*mt));
1371 SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool));
1373 /* Save translation state. */
1374 mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton,
1375 sizeof(*mt->saved_baton.in_baton));
1376 mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton,
1377 sizeof(*mt->saved_baton.out_baton));
1378 mt->saved_baton.written = b->written;
1379 mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool);
1380 mt->saved_baton.readbuf_off = b->readbuf_off;
1381 mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE);
1383 *mark = (svn_stream_mark_t *)mt;
1385 return SVN_NO_ERROR;
1388 /* Implements svn_stream_seek_fn_t. */
1389 static svn_error_t *
1390 translated_stream_seek(void *baton, const svn_stream_mark_t *mark)
1392 struct translated_stream_baton *b = baton;
1396 const mark_translated_t *mt = (const mark_translated_t *)mark;
1398 /* Flush output buffer if necessary. */
1400 SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0,
1403 SVN_ERR(svn_stream_seek(b->stream, mt->mark));
1405 /* Restore translation state, avoiding new allocations. */
1406 *b->in_baton = *mt->saved_baton.in_baton;
1407 *b->out_baton = *mt->saved_baton.out_baton;
1408 b->written = mt->saved_baton.written;
1409 svn_stringbuf_setempty(b->readbuf);
1410 svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data,
1411 mt->saved_baton.readbuf->len);
1412 b->readbuf_off = mt->saved_baton.readbuf_off;
1413 memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE);
1417 SVN_ERR(svn_stream_reset(b->stream));
1419 b->in_baton->newline_off = 0;
1420 b->in_baton->keyword_off = 0;
1421 b->in_baton->src_format_len = 0;
1422 b->out_baton->newline_off = 0;
1423 b->out_baton->keyword_off = 0;
1424 b->out_baton->src_format_len = 0;
1427 svn_stringbuf_setempty(b->readbuf);
1431 return SVN_NO_ERROR;
1435 svn_subst_read_specialfile(svn_stream_t **stream,
1437 apr_pool_t *result_pool,
1438 apr_pool_t *scratch_pool)
1443 /* First determine what type of special file we are
1445 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK,
1448 switch (finfo.filetype) {
1450 /* Nothing special to do here, just create stream from the original
1452 SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool));
1456 /* Determine the destination of the link. */
1457 SVN_ERR(svn_io_read_link(&buf, path, scratch_pool));
1458 *stream = svn_stream_from_string(svn_string_createf(result_pool,
1465 SVN_ERR_MALFUNCTION();
1468 return SVN_NO_ERROR;
1471 /* Same as svn_subst_stream_translated(), except for the following.
1473 * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream
1474 * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed,
1475 * otherwise leave it untouched.
1477 static svn_stream_t *
1478 stream_translated(svn_stream_t *stream,
1479 const char *eol_str,
1480 svn_boolean_t *translated_eol,
1481 svn_boolean_t repair,
1482 apr_hash_t *keywords,
1483 svn_boolean_t expand,
1484 apr_pool_t *result_pool)
1486 struct translated_stream_baton *baton
1487 = apr_palloc(result_pool, sizeof(*baton));
1488 svn_stream_t *s = svn_stream_create(baton, result_pool);
1490 /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL
1491 so they have the same lifetime as the stream. */
1493 eol_str = apr_pstrdup(result_pool, eol_str);
1496 if (apr_hash_count(keywords) == 0)
1500 /* deep copy the hash to make sure it's allocated in RESULT_POOL */
1501 apr_hash_t *copy = apr_hash_make(result_pool);
1502 apr_hash_index_t *hi;
1503 apr_pool_t *subpool;
1505 subpool = svn_pool_create(result_pool);
1506 for (hi = apr_hash_first(subpool, keywords);
1507 hi; hi = apr_hash_next(hi))
1512 apr_hash_this(hi, &key, NULL, &val);
1513 svn_hash_sets(copy, apr_pstrdup(result_pool, key),
1514 svn_string_dup(val, result_pool));
1516 svn_pool_destroy(subpool);
1522 /* Setup the baton fields */
1523 baton->stream = stream;
1525 = create_translation_baton(eol_str, translated_eol, repair, keywords,
1526 expand, result_pool);
1528 = create_translation_baton(eol_str, translated_eol, repair, keywords,
1529 expand, result_pool);
1530 baton->written = FALSE;
1531 baton->readbuf = svn_stringbuf_create_empty(result_pool);
1532 baton->readbuf_off = 0;
1533 baton->iterpool = svn_pool_create(result_pool);
1534 baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE);
1536 /* Setup the stream methods */
1537 svn_stream_set_read2(s, NULL /* only full read support */,
1538 translated_stream_read);
1539 svn_stream_set_write(s, translated_stream_write);
1540 svn_stream_set_close(s, translated_stream_close);
1541 if (svn_stream_supports_mark(stream))
1543 svn_stream_set_mark(s, translated_stream_mark);
1544 svn_stream_set_seek(s, translated_stream_seek);
1551 svn_subst_stream_translated(svn_stream_t *stream,
1552 const char *eol_str,
1553 svn_boolean_t repair,
1554 apr_hash_t *keywords,
1555 svn_boolean_t expand,
1556 apr_pool_t *result_pool)
1558 return stream_translated(stream, eol_str, NULL, repair, keywords, expand,
1562 /* Same as svn_subst_translate_cstring2(), except for the following.
1564 * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an
1565 * end-of-line sequence was changed, or to FALSE otherwise.
1567 static svn_error_t *
1568 translate_cstring(const char **dst,
1569 svn_boolean_t *translated_eol,
1571 const char *eol_str,
1572 svn_boolean_t repair,
1573 apr_hash_t *keywords,
1574 svn_boolean_t expand,
1577 svn_stringbuf_t *dst_stringbuf;
1578 svn_stream_t *dst_stream;
1579 apr_size_t len = strlen(src);
1581 /* The easy way out: no translation needed, just copy. */
1582 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1584 *dst = apr_pstrmemdup(pool, src, len);
1585 return SVN_NO_ERROR;
1588 /* Create a stringbuf and wrapper stream to hold the output. */
1589 dst_stringbuf = svn_stringbuf_create_empty(pool);
1590 dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
1593 *translated_eol = FALSE;
1595 /* Another wrapper to translate the content. */
1596 dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair,
1597 keywords, expand, pool);
1599 /* Jam the text into the destination stream (to translate it). */
1600 SVN_ERR(svn_stream_write(dst_stream, src, &len));
1602 /* Close the destination stream to flush unwritten data. */
1603 SVN_ERR(svn_stream_close(dst_stream));
1605 *dst = dst_stringbuf->data;
1606 return SVN_NO_ERROR;
1610 svn_subst_translate_cstring2(const char *src,
1612 const char *eol_str,
1613 svn_boolean_t repair,
1614 apr_hash_t *keywords,
1615 svn_boolean_t expand,
1618 return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand,
1622 /* Given a special file at SRC, generate a textual representation of
1623 it in a normal file at DST. Perform all allocations in POOL. */
1624 /* ### this should be folded into svn_subst_copy_and_translate3 */
1625 static svn_error_t *
1626 detranslate_special_file(const char *src, const char *dst,
1627 svn_cancel_func_t cancel_func, void *cancel_baton,
1628 apr_pool_t *scratch_pool)
1630 const char *dst_tmp;
1631 svn_stream_t *src_stream;
1632 svn_stream_t *dst_stream;
1634 /* Open a temporary destination that we will eventually atomically
1635 rename into place. */
1636 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1637 svn_dirent_dirname(dst, scratch_pool),
1638 svn_io_file_del_none,
1639 scratch_pool, scratch_pool));
1640 SVN_ERR(svn_subst_read_specialfile(&src_stream, src,
1641 scratch_pool, scratch_pool));
1642 SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
1643 cancel_func, cancel_baton, scratch_pool));
1645 /* Do the atomic rename from our temporary location. */
1646 return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, scratch_pool));
1649 /* Creates a special file DST from the "normal form" located in SOURCE.
1651 * All temporary allocations will be done in POOL.
1653 static svn_error_t *
1654 create_special_file_from_stream(svn_stream_t *source, const char *dst,
1657 svn_stringbuf_t *contents;
1659 const char *identifier;
1660 const char *remainder;
1661 const char *dst_tmp;
1662 svn_boolean_t create_using_internal_representation = FALSE;
1664 SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
1666 /* Separate off the identifier. The first space character delimits
1667 the identifier, after which any remaining characters are specific
1668 to the actual special file type being created. */
1669 identifier = contents->data;
1670 for (remainder = identifier; *remainder; remainder++)
1672 if (*remainder == ' ')
1679 if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
1680 sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1))
1682 /* For symlinks, the type specific data is just a filesystem
1683 path that the symlink should reference. */
1684 svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
1687 /* If we had an error, check to see if it was because symlinks are
1688 not supported on the platform. If so, fall back to using the
1689 internal representation. */
1690 if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1692 svn_error_clear(err);
1693 create_using_internal_representation = TRUE;
1697 return svn_error_trace(err);
1702 /* Just create a normal file using the internal special file
1703 representation. We don't want a commit of an unknown special
1704 file type to DoS all the clients. */
1705 create_using_internal_representation = TRUE;
1708 /* If nothing else worked, write out the internal representation to
1709 a file that can be edited by the user. */
1710 if (create_using_internal_representation)
1712 svn_stream_t *new_stream;
1715 SVN_ERR(svn_stream_open_unique(&new_stream, &dst_tmp,
1716 svn_dirent_dirname(dst, pool),
1717 svn_io_file_del_none,
1721 svn_stringbuf_appendcstr(contents, "\n");
1722 len = contents->len;
1723 SVN_ERR(svn_stream_write(new_stream, contents->data, &len));
1724 SVN_ERR(svn_stream_copy3(svn_stream_disown(source, pool), new_stream,
1728 /* Do the atomic rename from our temporary location. */
1729 return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, pool));
1734 svn_subst_copy_and_translate4(const char *src,
1736 const char *eol_str,
1737 svn_boolean_t repair,
1738 apr_hash_t *keywords,
1739 svn_boolean_t expand,
1740 svn_boolean_t special,
1741 svn_cancel_func_t cancel_func,
1745 svn_stream_t *src_stream;
1746 svn_stream_t *dst_stream;
1747 const char *dst_tmp;
1749 svn_node_kind_t kind;
1750 svn_boolean_t path_special;
1752 SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1754 /* If this is a 'special' file, we may need to create it or
1756 if (special || path_special)
1762 /* We are being asked to create a special file from a special
1763 file. Do a temporary detranslation and work from there. */
1765 /* ### woah. this section just undoes all the work we already did
1766 ### to read the contents of the special file. shoot... the
1767 ### svn_subst_read_specialfile even checks the file type
1770 SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool));
1774 SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1777 SVN_ERR(create_special_file_from_stream(src_stream, dst, pool));
1779 return svn_error_trace(svn_stream_close(src_stream));
1783 return svn_error_trace(detranslate_special_file(src, dst,
1789 /* The easy way out: no translation needed, just copy. */
1790 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1791 return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool));
1793 /* Open source file. */
1794 SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1796 /* For atomicity, we translate to a tmp file and then rename the tmp file
1797 over the real destination. */
1798 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1799 svn_dirent_dirname(dst, pool),
1800 svn_io_file_del_none, pool, pool));
1802 dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
1803 keywords, expand, pool);
1805 /* ###: use cancel func/baton in place of NULL/NULL below. */
1806 err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton,
1810 /* On errors, we have a pathname available. */
1811 if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1812 err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err,
1813 _("File '%s' has inconsistent newlines"),
1814 svn_dirent_local_style(src, pool));
1815 return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp,
1819 /* Now that dst_tmp contains the translated data, do the atomic rename. */
1820 SVN_ERR(svn_io_file_rename2(dst_tmp, dst, FALSE, pool));
1822 /* Preserve the source file's permission bits. */
1823 SVN_ERR(svn_io_copy_perms(src, dst, pool));
1825 return SVN_NO_ERROR;
1829 /*** 'Special file' stream support */
1831 struct special_stream_baton
1833 svn_stream_t *read_stream;
1834 svn_stringbuf_t *write_content;
1835 svn_stream_t *write_stream;
1841 static svn_error_t *
1842 read_handler_special(void *baton, char *buffer, apr_size_t *len)
1844 struct special_stream_baton *btn = baton;
1846 if (btn->read_stream)
1847 /* We actually found a file to read from */
1848 return svn_stream_read_full(btn->read_stream, buffer, len);
1850 return svn_error_createf(APR_ENOENT, NULL,
1851 _("Can't read special file: File '%s' not found"),
1852 svn_dirent_local_style(btn->path, btn->pool));
1855 static svn_error_t *
1856 write_handler_special(void *baton, const char *buffer, apr_size_t *len)
1858 struct special_stream_baton *btn = baton;
1860 return svn_stream_write(btn->write_stream, buffer, len);
1864 static svn_error_t *
1865 close_handler_special(void *baton)
1867 struct special_stream_baton *btn = baton;
1869 if (btn->write_content->len)
1871 /* yeay! we received data and need to create a special file! */
1873 svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content,
1875 SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool));
1878 return SVN_NO_ERROR;
1883 svn_subst_create_specialfile(svn_stream_t **stream,
1885 apr_pool_t *result_pool,
1886 apr_pool_t *scratch_pool)
1888 struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton));
1890 baton->path = apr_pstrdup(result_pool, path);
1892 /* SCRATCH_POOL may not exist after the function returns. */
1893 baton->pool = result_pool;
1895 baton->write_content = svn_stringbuf_create_empty(result_pool);
1896 baton->write_stream = svn_stream_from_stringbuf(baton->write_content,
1899 *stream = svn_stream_create(baton, result_pool);
1900 svn_stream_set_write(*stream, write_handler_special);
1901 svn_stream_set_close(*stream, close_handler_special);
1903 return SVN_NO_ERROR;
1907 /* NOTE: this function is deprecated, but we cannot move it over to
1908 deprecated.c because it uses stuff private to this file, and it is
1909 not easily rebuilt in terms of "new" functions. */
1911 svn_subst_stream_from_specialfile(svn_stream_t **stream,
1915 struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1919 baton->path = apr_pstrdup(pool, path);
1921 err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool);
1923 /* File might not exist because we intend to create it upon close. */
1924 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1926 svn_error_clear(err);
1928 /* Note: the special file is missing. the caller won't find out
1929 until the first read. Oh well. This function is deprecated anyways,
1930 so they can just deal with the weird behavior. */
1931 baton->read_stream = NULL;
1934 baton->write_content = svn_stringbuf_create_empty(pool);
1935 baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1937 *stream = svn_stream_create(baton, pool);
1938 svn_stream_set_read2(*stream, NULL /* only full read support */,
1939 read_handler_special);
1940 svn_stream_set_write(*stream, write_handler_special);
1941 svn_stream_set_close(*stream, close_handler_special);
1943 return SVN_NO_ERROR;
1948 /*** String translation */
1950 svn_subst_translate_string2(svn_string_t **new_value,
1951 svn_boolean_t *translated_to_utf8,
1952 svn_boolean_t *translated_line_endings,
1953 const svn_string_t *value,
1954 const char *encoding,
1955 svn_boolean_t repair,
1956 apr_pool_t *result_pool,
1957 apr_pool_t *scratch_pool)
1959 const char *val_utf8;
1960 const char *val_utf8_lf;
1965 return SVN_NO_ERROR;
1968 if (encoding && !strcmp(encoding, "UTF-8"))
1970 val_utf8 = value->data;
1974 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1975 encoding, scratch_pool));
1979 SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool));
1982 if (translated_to_utf8)
1983 *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0);
1985 SVN_ERR(translate_cstring(&val_utf8_lf,
1986 translated_line_endings,
1988 "\n", /* translate to LF */
1990 NULL, /* no keywords */
1991 FALSE, /* no expansion */
1994 *new_value = svn_string_create(val_utf8_lf, result_pool);
1995 return SVN_NO_ERROR;
2000 svn_subst_detranslate_string(svn_string_t **new_value,
2001 const svn_string_t *value,
2002 svn_boolean_t for_output,
2006 const char *val_neol;
2007 const char *val_nlocale_neol;
2012 return SVN_NO_ERROR;
2015 SVN_ERR(svn_subst_translate_cstring2(value->data,
2017 APR_EOL_STR, /* 'native' eol */
2018 FALSE, /* no repair */
2019 NULL, /* no keywords */
2020 FALSE, /* no expansion */
2025 err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2026 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2029 svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
2030 svn_error_clear(err);
2037 err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2038 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2040 val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
2041 svn_error_clear(err);
2047 *new_value = svn_string_create(val_nlocale_neol, pool);
2049 return SVN_NO_ERROR;