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"
55 * The textual elements of a detranslated special file. One of these
56 * strings must appear as the first element of any special file as it
57 * exists in the repository or the text base.
59 #define SVN_SUBST__SPECIAL_LINK_STR "link"
62 svn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
68 /* property doesn't exist. */
71 *style = svn_subst_eol_style_none;
73 else if (! strcmp("native", value))
75 *eol = APR_EOL_STR; /* whee, a portability library! */
77 *style = svn_subst_eol_style_native;
79 else if (! strcmp("LF", value))
83 *style = svn_subst_eol_style_fixed;
85 else if (! strcmp("CR", value))
89 *style = svn_subst_eol_style_fixed;
91 else if (! strcmp("CRLF", value))
95 *style = svn_subst_eol_style_fixed;
101 *style = svn_subst_eol_style_unknown;
107 svn_subst_translation_required(svn_subst_eol_style_t style,
109 apr_hash_t *keywords,
110 svn_boolean_t special,
111 svn_boolean_t force_eol_check)
113 return (special || keywords
114 || (style != svn_subst_eol_style_none && force_eol_check)
115 || (style == svn_subst_eol_style_native &&
116 strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0)
117 || (style == svn_subst_eol_style_fixed &&
118 strcmp(APR_EOL_STR, eol) != 0));
123 /* Helper function for svn_subst_build_keywords */
125 /* Given a printf-like format string, return a string with proper
126 * information filled in.
128 * Important API note: This function is the core of the implementation of
129 * svn_subst_build_keywords (all versions), and as such must implement the
130 * tolerance of NULL and zero inputs that that function's documention
135 * %a author of this revision
136 * %b basename of the URL of this file
137 * %d short format of date of this revision
138 * %D long format of date of this revision
139 * %P path relative to root of repos
140 * %r number of this revision
141 * %R root url of repository
142 * %u URL of this file
146 * The following special format codes are also recognized:
147 * %H is equivalent to %P%_%r%_%d%_%a
148 * %I is equivalent to %b%_%r%_%d%_%a
150 * All memory is allocated out of @a pool.
152 static svn_string_t *
153 keyword_printf(const char *fmt,
156 const char *repos_root_url,
161 svn_stringbuf_t *value = svn_stringbuf_ncreate("", 0, pool);
169 while (*cur != '\0' && *cur != '%')
172 if ((n = cur - fmt) > 0) /* Do we have an as-is string? */
173 svn_stringbuf_appendbytes(value, fmt, n);
180 case 'a': /* author of this revision */
182 svn_stringbuf_appendcstr(value, author);
184 case 'b': /* basename of this file */
187 const char *base_name = svn_uri_basename(url, pool);
188 svn_stringbuf_appendcstr(value, base_name);
191 case 'd': /* short format of date of this revision */
194 apr_time_exp_t exploded_time;
197 apr_time_exp_gmt(&exploded_time, date);
199 human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ",
200 exploded_time.tm_year + 1900,
201 exploded_time.tm_mon + 1,
202 exploded_time.tm_mday,
203 exploded_time.tm_hour,
204 exploded_time.tm_min,
205 exploded_time.tm_sec);
207 svn_stringbuf_appendcstr(value, human);
210 case 'D': /* long format of date of this revision */
212 svn_stringbuf_appendcstr(value,
213 svn_time_to_human_cstring(date, pool));
215 case 'P': /* relative path of this file */
216 if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0')
218 const char *repos_relpath;
220 repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool);
222 svn_stringbuf_appendcstr(value, repos_relpath);
225 case 'R': /* root of repos */
226 if (repos_root_url && *repos_root_url != '\0')
227 svn_stringbuf_appendcstr(value, repos_root_url);
229 case 'r': /* number of this revision */
231 svn_stringbuf_appendcstr(value, rev);
233 case 'u': /* URL of this file */
235 svn_stringbuf_appendcstr(value, url);
237 case '_': /* '%_' => a space */
238 svn_stringbuf_appendbyte(value, ' ');
240 case '%': /* '%%' => a literal % */
241 svn_stringbuf_appendbyte(value, *cur);
243 case '\0': /* '%' as the last character of the string. */
244 svn_stringbuf_appendbyte(value, *cur);
245 /* Now go back one character, since this was just a one character
246 * sequence, whereas all others are two characters, and we do not
247 * want to skip the null terminator entirely and carry on
248 * formatting random memory contents. */
253 svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url,
254 repos_root_url, date, author,
256 svn_stringbuf_appendcstr(value, s->data);
261 svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url,
262 repos_root_url, date, author,
264 svn_stringbuf_appendcstr(value, s->data);
267 default: /* Unrecognized code, just print it literally. */
268 svn_stringbuf_appendbytes(value, cur, 2);
272 /* Format code is processed - skip it, and get ready for next chunk. */
276 return svn_stringbuf__morph_into_string(value);
280 build_keywords(apr_hash_t **kw,
281 svn_boolean_t expand_custom_keywords,
282 const char *keywords_val,
285 const char *repos_root_url,
290 apr_array_header_t *keyword_tokens;
292 *kw = apr_hash_make(pool);
294 keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f",
295 TRUE /* chop */, pool);
297 for (i = 0; i < keyword_tokens->nelts; ++i)
299 const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *);
300 const char *custom_fmt = NULL;
302 if (expand_custom_keywords)
306 /* Check if there is a custom keyword definition, started by '='. */
307 sep = strchr(keyword, '=');
310 *sep = '\0'; /* Split keyword's name from custom format. */
311 custom_fmt = sep + 1;
317 svn_string_t *custom_val;
319 /* Custom keywords must be allowed to match the name of an
320 * existing fixed keyword. This is for compatibility purposes,
321 * in case new fixed keywords are added to Subversion which
322 * happen to match a custom keyword defined somewhere.
323 * There is only one global namespace for keyword names. */
324 custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url,
326 svn_hash_sets(*kw, keyword, custom_val);
328 else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG))
329 || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM))
330 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT)))
332 svn_string_t *revision_val;
334 revision_val = keyword_printf("%r", rev, url, repos_root_url,
336 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val);
337 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val);
338 svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val);
340 else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG))
341 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT)))
343 svn_string_t *date_val;
345 date_val = keyword_printf("%D", rev, url, repos_root_url, date,
347 svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val);
348 svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val);
350 else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG))
351 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT)))
353 svn_string_t *author_val;
355 author_val = keyword_printf("%a", rev, url, repos_root_url, date,
357 svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val);
358 svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val);
360 else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG))
361 || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT)))
363 svn_string_t *url_val;
365 url_val = keyword_printf("%u", rev, url, repos_root_url, date,
367 svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val);
368 svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val);
370 else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID)))
372 svn_string_t *id_val;
374 id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url,
376 svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val);
378 else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER)))
380 svn_string_t *header_val;
382 header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url,
384 svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val);
392 svn_subst_build_keywords2(apr_hash_t **kw,
393 const char *keywords_val,
400 return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url,
401 NULL, date, author, pool));
406 svn_subst_build_keywords3(apr_hash_t **kw,
407 const char *keywords_val,
410 const char *repos_root_url,
415 return svn_error_trace(build_keywords(kw, TRUE, keywords_val,
416 rev, url, repos_root_url,
417 date, author, pool));
421 /*** Helpers for svn_subst_translate_stream2 ***/
424 /* Write out LEN bytes of BUF into STREAM. */
425 /* ### TODO: 'stream_write()' would be a better name for this. */
427 translate_write(svn_stream_t *stream,
431 SVN_ERR(svn_stream_write(stream, buf, &len));
432 /* (No need to check LEN, as a short write always produces an error.) */
437 /* Perform the substitution of VALUE into keyword string BUF (with len
438 *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
439 *LEN to the new size of the substituted result. Return TRUE if all
440 goes well, FALSE otherwise. If VALUE is NULL, keyword will be
441 contracted, else it will be expanded. */
443 translate_keyword_subst(char *buf,
446 apr_size_t keyword_len,
447 const svn_string_t *value)
451 /* Make sure we gotz good stuffs. */
452 assert(*len <= SVN_KEYWORD_MAX_LEN);
453 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
455 /* Need at least a keyword and two $'s. */
456 if (*len < keyword_len + 2)
459 /* Need at least space for two $'s, two spaces and a colon, and that
460 leaves zero space for the value itself. */
461 if (keyword_len > SVN_KEYWORD_MAX_LEN - 5)
464 /* The keyword needs to match what we're looking for. */
465 if (strncmp(buf + 1, keyword, keyword_len))
468 buf_ptr = buf + 1 + keyword_len;
470 /* Check for fixed-length expansion.
471 * The format of fixed length keyword and its data is
472 * Unexpanded keyword: "$keyword:: $"
473 * Expanded keyword: "$keyword:: value $"
474 * Expanded kw with filling: "$keyword:: value $"
475 * Truncated keyword: "$keyword:: longval#$"
477 if ((buf_ptr[0] == ':') /* first char after keyword is ':' */
478 && (buf_ptr[1] == ':') /* second char after keyword is ':' */
479 && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */
480 && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */
481 || (buf[*len - 2] == '#')) /* .. or has '#' for next to last
483 && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */
485 /* This is fixed length keyword, so *len remains unchanged */
486 apr_size_t max_value_len = *len - (6 + keyword_len);
490 /* no value, so unexpand */
492 while (*buf_ptr != '$')
497 if (value->len <= max_value_len)
498 { /* replacement not as long as template, pad with spaces */
499 strncpy(buf_ptr + 3, value->data, value->len);
500 buf_ptr += 3 + value->len;
501 while (*buf_ptr != '$')
506 /* replacement needs truncating */
507 strncpy(buf_ptr + 3, value->data, max_value_len);
515 /* Check for unexpanded keyword. */
516 else if (buf_ptr[0] == '$') /* "$keyword$" */
526 apr_size_t vallen = value->len;
528 /* "$keyword: value $" */
529 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
530 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
531 strncpy(buf_ptr + 2, value->data, vallen);
532 buf_ptr[2 + vallen] = ' ';
533 buf_ptr[2 + vallen + 1] = '$';
534 *len = 5 + keyword_len + vallen;
540 *len = 4 + keyword_len;
545 /* ...but do nothing. */
550 /* Check for expanded keyword. */
551 else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */
552 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
553 && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */
554 && (buf[*len - 2] == ' '))
555 || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */
556 && (buf_ptr[0] == ':') /* first char after keyword is ':' */
557 && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */
562 /* ...so unexpand. */
564 *len = 2 + keyword_len;
568 /* ...so re-expand. */
573 apr_size_t vallen = value->len;
575 /* "$keyword: value $" */
576 if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
577 vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
578 strncpy(buf_ptr + 2, value->data, vallen);
579 buf_ptr[2 + vallen] = ' ';
580 buf_ptr[2 + vallen + 1] = '$';
581 *len = 5 + keyword_len + vallen;
587 *len = 4 + keyword_len;
596 /* Parse BUF (whose length is LEN, and which starts and ends with '$'),
597 trying to match one of the keyword names in KEYWORDS. If such a
598 keyword is found, update *KEYWORD_NAME with the keyword name and
601 match_keyword(char *buf,
604 apr_hash_t *keywords)
608 /* Early return for ignored keywords */
612 /* Extract the name of the keyword */
613 for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++)
614 keyword_name[i] = buf[i + 1];
615 keyword_name[i] = '\0';
617 return svn_hash_gets(keywords, keyword_name) != NULL;
620 /* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
621 optionally perform the substitution in place, update *LEN with
622 the new length of the translated keyword string, and return TRUE.
623 If this buffer doesn't contain a known keyword pattern, leave BUF
624 and *LEN untouched and return FALSE.
626 See the docstring for svn_subst_copy_and_translate for how the
627 EXPAND and KEYWORDS parameters work.
629 NOTE: It is assumed that BUF has been allocated to be at least
630 SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
631 than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions
632 which would result in a keyword string which is greater than
633 SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
634 that the resultant keyword string is still valid (begins with
635 "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */
637 translate_keyword(char *buf,
639 const char *keyword_name,
640 svn_boolean_t expand,
641 apr_hash_t *keywords)
643 const svn_string_t *value;
645 /* Make sure we gotz good stuffs. */
646 assert(*len <= SVN_KEYWORD_MAX_LEN);
647 assert((buf[0] == '$') && (buf[*len - 1] == '$'));
649 /* Early return for ignored keywords */
653 value = svn_hash_gets(keywords, keyword_name);
657 return translate_keyword_subst(buf, len,
658 keyword_name, strlen(keyword_name),
659 expand ? value : NULL);
665 /* A boolean expression that evaluates to true if the first STR_LEN characters
666 of the string STR are one of the end-of-line strings LF, CR, or CRLF;
667 to false otherwise. */
668 #define STRING_IS_EOL(str, str_len) \
669 (((str_len) == 2 && (str)[0] == '\r' && (str)[1] == '\n') || \
670 ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r')))
672 /* A boolean expression that evaluates to true if the end-of-line string EOL1,
673 having length EOL1_LEN, and the end-of-line string EOL2, having length
674 EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the
675 set {"\n", "\r", "\r\n"}; to false otherwise.
677 Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if
678 EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course
679 different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both
680 "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1.
681 We need only check the one character for equality to determine whether
682 EOL1 and EOL2 are different in that case. */
683 #define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \
684 (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2)))
687 /* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to
688 the newline string EOL_STR (of length EOL_STR_LEN), writing the
689 result (which is always EOL_STR) to the stream DST.
691 This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n".
693 Also check for consistency of the source newline strings across
694 multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache
695 of the first newline found. If the current newline is not the same
696 as SRC_FORMAT, look to the REPAIR parameter. If REPAIR is TRUE,
697 ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL
698 error. If *SRC_FORMAT_LEN is 0, assume we are examining the first
699 newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
700 use for later consistency checks.
702 If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the
703 newline string that was written (EOL_STR) is not the same as the newline
704 string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL
707 Note: all parameters are required even if REPAIR is TRUE.
708 ### We could require that REPAIR must not change across a sequence of
709 calls, and could then optimize by not using SRC_FORMAT at all if
713 translate_newline(const char *eol_str,
714 apr_size_t eol_str_len,
716 apr_size_t *src_format_len,
717 const char *newline_buf,
718 apr_size_t newline_len,
720 svn_boolean_t *translated_eol,
721 svn_boolean_t repair)
723 SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len));
725 /* If we've seen a newline before, compare it with our cache to
726 check for consistency, else cache it for future comparisons. */
729 /* Comparing with cache. If we are inconsistent and
730 we are NOT repairing the file, generate an error! */
731 if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len,
732 newline_buf, newline_len))
733 return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL);
737 /* This is our first line ending, so cache it before
739 strncpy(src_format, newline_buf, newline_len);
740 *src_format_len = newline_len;
743 /* Write the desired newline */
744 SVN_ERR(translate_write(dst, eol_str, eol_str_len));
746 /* Report whether we translated it. Note: Not using DIFFERENT_EOL_STRINGS()
747 * because EOL_STR may not be a valid EOL sequence. */
748 if (translated_eol != NULL &&
749 (eol_str_len != newline_len ||
750 memcmp(eol_str, newline_buf, eol_str_len) != 0))
751 *translated_eol = TRUE;
758 /*** Public interfaces. ***/
761 svn_subst_keywords_differ(const svn_subst_keywords_t *a,
762 const svn_subst_keywords_t *b,
763 svn_boolean_t compare_values)
765 if (((a == NULL) && (b == NULL)) /* no A or B */
766 /* no A, and B has no contents */
768 && (b->revision == NULL)
770 && (b->author == NULL)
772 /* no B, and A has no contents */
773 || ((b == NULL) && (a->revision == NULL)
775 && (a->author == NULL)
777 /* neither A nor B has any contents */
778 || ((a != NULL) && (b != NULL)
779 && (b->revision == NULL)
781 && (b->author == NULL)
783 && (a->revision == NULL)
785 && (a->author == NULL)
786 && (a->url == NULL)))
790 else if ((a == NULL) || (b == NULL))
793 /* Else both A and B have some keywords. */
795 if ((! a->revision) != (! b->revision))
797 else if ((compare_values && (a->revision != NULL))
798 && (strcmp(a->revision->data, b->revision->data) != 0))
801 if ((! a->date) != (! b->date))
803 else if ((compare_values && (a->date != NULL))
804 && (strcmp(a->date->data, b->date->data) != 0))
807 if ((! a->author) != (! b->author))
809 else if ((compare_values && (a->author != NULL))
810 && (strcmp(a->author->data, b->author->data) != 0))
813 if ((! a->url) != (! b->url))
815 else if ((compare_values && (a->url != NULL))
816 && (strcmp(a->url->data, b->url->data) != 0))
819 /* Else we never found a difference, so they must be the same. */
825 svn_subst_keywords_differ2(apr_hash_t *a,
827 svn_boolean_t compare_values,
830 apr_hash_index_t *hi;
831 unsigned int a_count, b_count;
833 /* An empty hash is logically equal to a NULL,
834 * as far as this API is concerned. */
835 a_count = (a == NULL) ? 0 : apr_hash_count(a);
836 b_count = (b == NULL) ? 0 : apr_hash_count(b);
838 if (a_count != b_count)
844 /* The hashes are both non-NULL, and have the same number of items.
845 * We must check that every item of A is present in B. */
846 for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
851 svn_string_t *a_val, *b_val;
853 apr_hash_this(hi, &key, &klen, &void_a_val);
855 b_val = apr_hash_get(b, key, klen);
857 if (!b_val || (compare_values && !svn_string_compare(a_val, b_val)))
865 /* Baton for translate_chunk() to store its state in. */
866 struct translation_baton
869 svn_boolean_t *translated_eol;
870 svn_boolean_t repair;
871 apr_hash_t *keywords;
872 svn_boolean_t expand;
874 /* 'short boolean' array that encodes what character values
875 may trigger a translation action, hence are 'interesting' */
876 char interesting[256];
878 /* Length of the string EOL_STR points to. */
879 apr_size_t eol_str_len;
881 /* Buffer to cache any newline state between translation chunks */
884 /* Offset (within newline_buf) of the first *unused* character */
885 apr_size_t newline_off;
887 /* Buffer to cache keyword-parsing state between translation chunks */
888 char keyword_buf[SVN_KEYWORD_MAX_LEN];
890 /* Offset (within keyword-buf) to the first *unused* character */
891 apr_size_t keyword_off;
893 /* EOL style used in the chunk-source */
896 /* Length of the EOL style string found in the chunk-source,
897 or zero if none encountered yet */
898 apr_size_t src_format_len;
900 /* If this is svn_tristate_false, translate_newline() will be called
901 for every newline in the file */
902 svn_tristate_t nl_translation_skippable;
906 /* Allocate a baton for use with translate_chunk() in POOL and
907 * initialize it for the first iteration.
909 * The caller must assure that EOL_STR and KEYWORDS at least
910 * have the same life time as that of POOL.
912 static struct translation_baton *
913 create_translation_baton(const char *eol_str,
914 svn_boolean_t *translated_eol,
915 svn_boolean_t repair,
916 apr_hash_t *keywords,
917 svn_boolean_t expand,
920 struct translation_baton *b = apr_palloc(pool, sizeof(*b));
922 /* For efficiency, convert an empty set of keywords to NULL. */
923 if (keywords && (apr_hash_count(keywords) == 0))
926 b->eol_str = eol_str;
927 b->eol_str_len = eol_str ? strlen(eol_str) : 0;
928 b->translated_eol = translated_eol;
930 b->keywords = keywords;
934 b->src_format_len = 0;
935 b->nl_translation_skippable = svn_tristate_unknown;
937 /* Most characters don't start translation actions.
938 * Mark those that do depending on the parameters we got. */
939 memset(b->interesting, FALSE, sizeof(b->interesting));
941 b->interesting['$'] = TRUE;
944 b->interesting['\r'] = TRUE;
945 b->interesting['\n'] = TRUE;
951 /* Return TRUE if the EOL starting at BUF matches the eol_str member of B.
952 * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like
953 * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is
954 * more efficient to handle that special case implicitly in the calling code
955 * by exiting the quick scan loop.
956 * The caller must ensure that buf[0] and buf[1] refer to valid memory
959 static APR_INLINE svn_boolean_t
960 eol_unchanged(struct translation_baton *b,
963 /* If the first byte doesn't match, the whole EOL won't.
964 * This does also handle the (certainly invalid) case that
965 * eol_str would be an empty string.
967 if (buf[0] != b->eol_str[0])
970 /* two-char EOLs must be a full match */
971 if (b->eol_str_len == 2)
972 return buf[1] == b->eol_str[1];
974 /* The first char matches the required 1-byte EOL.
975 * But maybe, buf[] contains a 2-byte EOL?
976 * In that case, the second byte will be interesting
977 * and not be another EOL of its own.
979 return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1];
983 /* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
984 * according to the settings and state stored in baton B.
986 * Write output to stream DST.
988 * To finish a series of chunk translations, flush all buffers by calling
989 * this routine with a NULL value for BUF.
991 * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if
992 * an end-of-line sequence was changed, otherwise leave it untouched.
994 * Use POOL for temporary allocations.
997 translate_chunk(svn_stream_t *dst,
998 struct translation_baton *b,
1008 /* precalculate some oft-used values */
1009 const char *end = buf + buflen;
1010 const char *interesting = b->interesting;
1011 apr_size_t next_sign_off = 0;
1013 /* At the beginning of this loop, assume that we might be in an
1014 * interesting state, i.e. with data in the newline or keyword
1015 * buffer. First try to get to the boring state so we can copy
1016 * a run of boring characters; then try to get back to the
1017 * interesting state by processing an interesting character,
1019 for (p = buf; p < end;)
1021 /* Try to get to the boring state, if necessary. */
1025 b->newline_buf[b->newline_off++] = *p++;
1027 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1029 &b->src_format_len, b->newline_buf,
1030 b->newline_off, dst, b->translated_eol,
1035 else if (b->keyword_off && *p == '$')
1037 svn_boolean_t keyword_matches;
1038 char keyword_name[SVN_KEYWORD_MAX_LEN + 1];
1040 /* If keyword is matched, but not correctly translated, try to
1041 * look for the next ending '$'. */
1042 b->keyword_buf[b->keyword_off++] = *p++;
1043 keyword_matches = match_keyword(b->keyword_buf, b->keyword_off,
1044 keyword_name, b->keywords);
1045 if (!keyword_matches)
1047 /* reuse the ending '$' */
1052 if (!keyword_matches ||
1053 translate_keyword(b->keyword_buf, &b->keyword_off,
1054 keyword_name, b->expand, b->keywords) ||
1055 b->keyword_off >= SVN_KEYWORD_MAX_LEN)
1057 /* write out non-matching text or translated keyword */
1058 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1065 if (next_sign_off == 0)
1066 next_sign_off = b->keyword_off - 1;
1071 else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1
1072 || (b->keyword_off && (*p == '\r' || *p == '\n')))
1074 if (next_sign_off > 0)
1076 /* rolling back, continue with next '$' in keyword_buf */
1077 p -= (b->keyword_off - next_sign_off);
1078 b->keyword_off = next_sign_off;
1081 /* No closing '$' found; flush the keyword buffer. */
1082 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1086 else if (b->keyword_off)
1088 b->keyword_buf[b->keyword_off++] = *p++;
1092 /* translate_newline will modify the baton for src_format_len==0
1093 or may return an error if b->repair is FALSE. In all other
1094 cases, we can skip the newline translation as long as source
1095 EOL format and actual EOL format match. If there is a
1096 mismatch, translate_newline will be called regardless of
1097 nl_translation_skippable.
1099 if (b->nl_translation_skippable == svn_tristate_unknown &&
1100 b->src_format_len > 0)
1102 /* test whether translate_newline may return an error */
1103 if (b->eol_str_len == b->src_format_len &&
1104 strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0)
1105 b->nl_translation_skippable = svn_tristate_true;
1107 b->nl_translation_skippable = svn_tristate_true;
1109 b->nl_translation_skippable = svn_tristate_false;
1112 /* We're in the boring state; look for interesting characters.
1113 Offset len such that it will become 0 in the first iteration.
1115 len = 0 - b->eol_str_len;
1117 /* Look for the next EOL (or $) that actually needs translation.
1118 Stop there or at EOF, whichever is encountered first.
1122 /* skip current EOL */
1123 len += b->eol_str_len;
1125 /* Check 4 bytes at once to allow for efficient pipelining
1126 and to reduce loop condition overhead. */
1127 while ((p + len + 4) <= end)
1129 if (interesting[(unsigned char)p[len]]
1130 || interesting[(unsigned char)p[len+1]]
1131 || interesting[(unsigned char)p[len+2]]
1132 || interesting[(unsigned char)p[len+3]])
1138 /* Found an interesting char or EOF in the next 4 bytes.
1139 Find its exact position. */
1140 while ((p + len) < end && !interesting[(unsigned char)p[len]])
1143 while (b->nl_translation_skippable ==
1144 svn_tristate_true && /* can potentially skip EOLs */
1145 p + len + 2 < end && /* not too close to EOF */
1146 eol_unchanged (b, p + len)); /* EOL format already ok */
1148 while ((p + len) < end && !interesting[(unsigned char)p[len]])
1153 SVN_ERR(translate_write(dst, p, len));
1157 /* Set up state according to the interesting character, if any. */
1163 b->keyword_buf[b->keyword_off++] = *p++;
1166 b->newline_buf[b->newline_off++] = *p++;
1169 b->newline_buf[b->newline_off++] = *p++;
1171 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1175 b->newline_off, dst,
1176 b->translated_eol, b->repair));
1189 SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
1190 b->src_format, &b->src_format_len,
1191 b->newline_buf, b->newline_off,
1192 dst, b->translated_eol, b->repair));
1198 SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
1203 return SVN_NO_ERROR;
1206 /* Baton for use with translated stream callbacks. */
1207 struct translated_stream_baton
1209 /* Stream to take input from (before translation) on read
1210 /write output to (after translation) on write. */
1211 svn_stream_t *stream;
1213 /* Input/Output translation batons to make them separate chunk streams. */
1214 struct translation_baton *in_baton, *out_baton;
1216 /* Remembers whether any write operations have taken place;
1217 if so, we need to flush the output chunk stream. */
1218 svn_boolean_t written;
1220 /* Buffer to hold translated read data. */
1221 svn_stringbuf_t *readbuf;
1223 /* Offset of the first non-read character in readbuf. */
1224 apr_size_t readbuf_off;
1226 /* Buffer to hold read data
1227 between svn_stream_read() and translate_chunk(). */
1229 #define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1)
1231 /* Pool for callback iterations */
1232 apr_pool_t *iterpool;
1236 /* Implements svn_read_fn_t. */
1237 static svn_error_t *
1238 translated_stream_read(void *baton,
1242 struct translated_stream_baton *b = baton;
1243 apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
1244 apr_size_t unsatisfied = *len;
1247 /* Optimization for a frequent special case. The configuration parser (and
1248 a few others) reads the stream one byte at a time. All the memcpy, pool
1249 clearing etc. imposes a huge overhead in that case. In most cases, we
1250 can just take that single byte directly from the read buffer.
1252 Since *len > 1 requires lots of code to be run anyways, we can afford
1253 the extra overhead of checking for *len == 1.
1255 See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>.
1257 if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len)
1259 /* Just take it from the read buffer */
1260 *buffer = b->readbuf->data[b->readbuf_off++];
1262 return SVN_NO_ERROR;
1265 /* Standard code path. */
1266 while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0)
1269 apr_size_t buffer_remainder;
1271 svn_pool_clear(b->iterpool);
1272 /* fill read buffer, if necessary */
1273 if (! (b->readbuf_off < b->readbuf->len))
1275 svn_stream_t *buf_stream;
1277 svn_stringbuf_setempty(b->readbuf);
1279 SVN_ERR(svn_stream_read(b->stream, b->buf, &readlen));
1280 buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool);
1282 SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf,
1283 readlen, b->iterpool));
1285 if (readlen != SVN__STREAM_CHUNK_SIZE)
1286 SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0,
1289 SVN_ERR(svn_stream_close(buf_stream));
1292 /* Satisfy from the read buffer */
1293 buffer_remainder = b->readbuf->len - b->readbuf_off;
1294 to_copy = (buffer_remainder > unsatisfied)
1295 ? unsatisfied : buffer_remainder;
1296 memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy);
1298 b->readbuf_off += to_copy;
1299 unsatisfied -= to_copy;
1302 *len -= unsatisfied;
1304 return SVN_NO_ERROR;
1307 /* Implements svn_write_fn_t. */
1308 static svn_error_t *
1309 translated_stream_write(void *baton,
1313 struct translated_stream_baton *b = baton;
1314 svn_pool_clear(b->iterpool);
1317 return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool);
1320 /* Implements svn_close_fn_t. */
1321 static svn_error_t *
1322 translated_stream_close(void *baton)
1324 struct translated_stream_baton *b = baton;
1325 svn_error_t *err = NULL;
1328 err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool);
1330 err = svn_error_compose_create(err, svn_stream_close(b->stream));
1332 svn_pool_destroy(b->iterpool);
1334 return svn_error_trace(err);
1338 /* svn_stream_mark_t for translation streams. */
1339 typedef struct mark_translated_t
1341 /* Saved translation state. */
1342 struct translated_stream_baton saved_baton;
1344 /* Mark set on the underlying stream. */
1345 svn_stream_mark_t *mark;
1346 } mark_translated_t;
1348 /* Implements svn_stream_mark_fn_t. */
1349 static svn_error_t *
1350 translated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
1352 mark_translated_t *mt;
1353 struct translated_stream_baton *b = baton;
1355 mt = apr_palloc(pool, sizeof(*mt));
1356 SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool));
1358 /* Save translation state. */
1359 mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton,
1360 sizeof(*mt->saved_baton.in_baton));
1361 mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton,
1362 sizeof(*mt->saved_baton.out_baton));
1363 mt->saved_baton.written = b->written;
1364 mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool);
1365 mt->saved_baton.readbuf_off = b->readbuf_off;
1366 mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE);
1368 *mark = (svn_stream_mark_t *)mt;
1370 return SVN_NO_ERROR;
1373 /* Implements svn_stream_seek_fn_t. */
1374 static svn_error_t *
1375 translated_stream_seek(void *baton, const svn_stream_mark_t *mark)
1377 struct translated_stream_baton *b = baton;
1381 const mark_translated_t *mt = (const mark_translated_t *)mark;
1383 /* Flush output buffer if necessary. */
1385 SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0,
1388 SVN_ERR(svn_stream_seek(b->stream, mt->mark));
1390 /* Restore translation state, avoiding new allocations. */
1391 *b->in_baton = *mt->saved_baton.in_baton;
1392 *b->out_baton = *mt->saved_baton.out_baton;
1393 b->written = mt->saved_baton.written;
1394 svn_stringbuf_setempty(b->readbuf);
1395 svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data,
1396 mt->saved_baton.readbuf->len);
1397 b->readbuf_off = mt->saved_baton.readbuf_off;
1398 memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE);
1402 SVN_ERR(svn_stream_reset(b->stream));
1404 b->in_baton->newline_off = 0;
1405 b->in_baton->keyword_off = 0;
1406 b->in_baton->src_format_len = 0;
1407 b->out_baton->newline_off = 0;
1408 b->out_baton->keyword_off = 0;
1409 b->out_baton->src_format_len = 0;
1412 svn_stringbuf_setempty(b->readbuf);
1416 return SVN_NO_ERROR;
1419 /* Implements svn_stream__is_buffered_fn_t. */
1420 static svn_boolean_t
1421 translated_stream_is_buffered(void *baton)
1423 struct translated_stream_baton *b = baton;
1424 return svn_stream__is_buffered(b->stream);
1428 svn_subst_read_specialfile(svn_stream_t **stream,
1430 apr_pool_t *result_pool,
1431 apr_pool_t *scratch_pool)
1436 /* First determine what type of special file we are
1438 SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK,
1441 switch (finfo.filetype) {
1443 /* Nothing special to do here, just create stream from the original
1445 SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool));
1449 /* Determine the destination of the link. */
1450 SVN_ERR(svn_io_read_link(&buf, path, scratch_pool));
1451 *stream = svn_stream_from_string(svn_string_createf(result_pool,
1458 SVN_ERR_MALFUNCTION();
1461 return SVN_NO_ERROR;
1464 /* Same as svn_subst_stream_translated(), except for the following.
1466 * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream
1467 * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed,
1468 * otherwise leave it untouched.
1470 static svn_stream_t *
1471 stream_translated(svn_stream_t *stream,
1472 const char *eol_str,
1473 svn_boolean_t *translated_eol,
1474 svn_boolean_t repair,
1475 apr_hash_t *keywords,
1476 svn_boolean_t expand,
1477 apr_pool_t *result_pool)
1479 struct translated_stream_baton *baton
1480 = apr_palloc(result_pool, sizeof(*baton));
1481 svn_stream_t *s = svn_stream_create(baton, result_pool);
1483 /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL
1484 so they have the same lifetime as the stream. */
1486 eol_str = apr_pstrdup(result_pool, eol_str);
1489 if (apr_hash_count(keywords) == 0)
1493 /* deep copy the hash to make sure it's allocated in RESULT_POOL */
1494 apr_hash_t *copy = apr_hash_make(result_pool);
1495 apr_hash_index_t *hi;
1496 apr_pool_t *subpool;
1498 subpool = svn_pool_create(result_pool);
1499 for (hi = apr_hash_first(subpool, keywords);
1500 hi; hi = apr_hash_next(hi))
1505 apr_hash_this(hi, &key, NULL, &val);
1506 svn_hash_sets(copy, apr_pstrdup(result_pool, key),
1507 svn_string_dup(val, result_pool));
1509 svn_pool_destroy(subpool);
1515 /* Setup the baton fields */
1516 baton->stream = stream;
1518 = create_translation_baton(eol_str, translated_eol, repair, keywords,
1519 expand, result_pool);
1521 = create_translation_baton(eol_str, translated_eol, repair, keywords,
1522 expand, result_pool);
1523 baton->written = FALSE;
1524 baton->readbuf = svn_stringbuf_create_empty(result_pool);
1525 baton->readbuf_off = 0;
1526 baton->iterpool = svn_pool_create(result_pool);
1527 baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE);
1529 /* Setup the stream methods */
1530 svn_stream_set_read(s, translated_stream_read);
1531 svn_stream_set_write(s, translated_stream_write);
1532 svn_stream_set_close(s, translated_stream_close);
1533 svn_stream_set_mark(s, translated_stream_mark);
1534 svn_stream_set_seek(s, translated_stream_seek);
1535 svn_stream__set_is_buffered(s, translated_stream_is_buffered);
1541 svn_subst_stream_translated(svn_stream_t *stream,
1542 const char *eol_str,
1543 svn_boolean_t repair,
1544 apr_hash_t *keywords,
1545 svn_boolean_t expand,
1546 apr_pool_t *result_pool)
1548 return stream_translated(stream, eol_str, NULL, repair, keywords, expand,
1552 /* Same as svn_subst_translate_cstring2(), except for the following.
1554 * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an
1555 * end-of-line sequence was changed, or to FALSE otherwise.
1557 static svn_error_t *
1558 translate_cstring(const char **dst,
1559 svn_boolean_t *translated_eol,
1561 const char *eol_str,
1562 svn_boolean_t repair,
1563 apr_hash_t *keywords,
1564 svn_boolean_t expand,
1567 svn_stringbuf_t *dst_stringbuf;
1568 svn_stream_t *dst_stream;
1569 apr_size_t len = strlen(src);
1571 /* The easy way out: no translation needed, just copy. */
1572 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1574 *dst = apr_pstrmemdup(pool, src, len);
1575 return SVN_NO_ERROR;
1578 /* Create a stringbuf and wrapper stream to hold the output. */
1579 dst_stringbuf = svn_stringbuf_create_empty(pool);
1580 dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
1583 *translated_eol = FALSE;
1585 /* Another wrapper to translate the content. */
1586 dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair,
1587 keywords, expand, pool);
1589 /* Jam the text into the destination stream (to translate it). */
1590 SVN_ERR(svn_stream_write(dst_stream, src, &len));
1592 /* Close the destination stream to flush unwritten data. */
1593 SVN_ERR(svn_stream_close(dst_stream));
1595 *dst = dst_stringbuf->data;
1596 return SVN_NO_ERROR;
1600 svn_subst_translate_cstring2(const char *src,
1602 const char *eol_str,
1603 svn_boolean_t repair,
1604 apr_hash_t *keywords,
1605 svn_boolean_t expand,
1608 return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand,
1612 /* Given a special file at SRC, generate a textual representation of
1613 it in a normal file at DST. Perform all allocations in POOL. */
1614 /* ### this should be folded into svn_subst_copy_and_translate3 */
1615 static svn_error_t *
1616 detranslate_special_file(const char *src, const char *dst,
1617 svn_cancel_func_t cancel_func, void *cancel_baton,
1618 apr_pool_t *scratch_pool)
1620 const char *dst_tmp;
1621 svn_stream_t *src_stream;
1622 svn_stream_t *dst_stream;
1624 /* Open a temporary destination that we will eventually atomically
1625 rename into place. */
1626 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1627 svn_dirent_dirname(dst, scratch_pool),
1628 svn_io_file_del_none,
1629 scratch_pool, scratch_pool));
1630 SVN_ERR(svn_subst_read_specialfile(&src_stream, src,
1631 scratch_pool, scratch_pool));
1632 SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
1633 cancel_func, cancel_baton, scratch_pool));
1635 /* Do the atomic rename from our temporary location. */
1636 return svn_error_trace(svn_io_file_rename(dst_tmp, dst, scratch_pool));
1639 /* Creates a special file DST from the "normal form" located in SOURCE.
1641 * All temporary allocations will be done in POOL.
1643 static svn_error_t *
1644 create_special_file_from_stream(svn_stream_t *source, const char *dst,
1647 svn_stringbuf_t *contents;
1649 const char *identifier;
1650 const char *remainder;
1651 const char *dst_tmp;
1652 svn_boolean_t create_using_internal_representation = FALSE;
1654 SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
1656 /* Separate off the identifier. The first space character delimits
1657 the identifier, after which any remaining characters are specific
1658 to the actual special file type being created. */
1659 identifier = contents->data;
1660 for (remainder = identifier; *remainder; remainder++)
1662 if (*remainder == ' ')
1669 if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
1670 sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1))
1672 /* For symlinks, the type specific data is just a filesystem
1673 path that the symlink should reference. */
1674 svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
1677 /* If we had an error, check to see if it was because symlinks are
1678 not supported on the platform. If so, fall back
1679 to using the internal representation. */
1682 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1684 svn_error_clear(err);
1685 create_using_internal_representation = TRUE;
1693 /* Just create a normal file using the internal special file
1694 representation. We don't want a commit of an unknown special
1695 file type to DoS all the clients. */
1696 create_using_internal_representation = TRUE;
1699 /* If nothing else worked, write out the internal representation to
1700 a file that can be edited by the user.
1702 ### this only writes the first line!
1704 if (create_using_internal_representation)
1705 SVN_ERR(svn_io_write_unique(&dst_tmp, svn_dirent_dirname(dst, pool),
1706 contents->data, contents->len,
1707 svn_io_file_del_none, pool));
1709 /* Do the atomic rename from our temporary location. */
1710 return svn_io_file_rename(dst_tmp, dst, pool);
1715 svn_subst_copy_and_translate4(const char *src,
1717 const char *eol_str,
1718 svn_boolean_t repair,
1719 apr_hash_t *keywords,
1720 svn_boolean_t expand,
1721 svn_boolean_t special,
1722 svn_cancel_func_t cancel_func,
1726 svn_stream_t *src_stream;
1727 svn_stream_t *dst_stream;
1728 const char *dst_tmp;
1730 svn_node_kind_t kind;
1731 svn_boolean_t path_special;
1733 SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
1735 /* If this is a 'special' file, we may need to create it or
1737 if (special || path_special)
1743 /* We are being asked to create a special file from a special
1744 file. Do a temporary detranslation and work from there. */
1746 /* ### woah. this section just undoes all the work we already did
1747 ### to read the contents of the special file. shoot... the
1748 ### svn_subst_read_specialfile even checks the file type
1751 SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool));
1755 SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1758 return svn_error_trace(create_special_file_from_stream(src_stream,
1763 return svn_error_trace(detranslate_special_file(src, dst,
1769 /* The easy way out: no translation needed, just copy. */
1770 if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
1771 return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool));
1773 /* Open source file. */
1774 SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
1776 /* For atomicity, we translate to a tmp file and then rename the tmp file
1777 over the real destination. */
1778 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
1779 svn_dirent_dirname(dst, pool),
1780 svn_io_file_del_none, pool, pool));
1782 dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
1783 keywords, expand, pool);
1785 /* ###: use cancel func/baton in place of NULL/NULL below. */
1786 err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton,
1790 /* On errors, we have a pathname available. */
1791 if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1792 err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err,
1793 _("File '%s' has inconsistent newlines"),
1794 svn_dirent_local_style(src, pool));
1795 return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp,
1799 /* Now that dst_tmp contains the translated data, do the atomic rename. */
1800 SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
1802 /* Preserve the source file's permission bits. */
1803 SVN_ERR(svn_io_copy_perms(src, dst, pool));
1805 return SVN_NO_ERROR;
1809 /*** 'Special file' stream support */
1811 struct special_stream_baton
1813 svn_stream_t *read_stream;
1814 svn_stringbuf_t *write_content;
1815 svn_stream_t *write_stream;
1821 static svn_error_t *
1822 read_handler_special(void *baton, char *buffer, apr_size_t *len)
1824 struct special_stream_baton *btn = baton;
1826 if (btn->read_stream)
1827 /* We actually found a file to read from */
1828 return svn_stream_read(btn->read_stream, buffer, len);
1830 return svn_error_createf(APR_ENOENT, NULL,
1831 "Can't read special file: File '%s' not found",
1832 svn_dirent_local_style(btn->path, btn->pool));
1835 static svn_error_t *
1836 write_handler_special(void *baton, const char *buffer, apr_size_t *len)
1838 struct special_stream_baton *btn = baton;
1840 return svn_stream_write(btn->write_stream, buffer, len);
1844 static svn_error_t *
1845 close_handler_special(void *baton)
1847 struct special_stream_baton *btn = baton;
1849 if (btn->write_content->len)
1851 /* yeay! we received data and need to create a special file! */
1853 svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content,
1855 SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool));
1858 return SVN_NO_ERROR;
1863 svn_subst_create_specialfile(svn_stream_t **stream,
1865 apr_pool_t *result_pool,
1866 apr_pool_t *scratch_pool)
1868 struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton));
1870 baton->path = apr_pstrdup(result_pool, path);
1872 /* SCRATCH_POOL may not exist after the function returns. */
1873 baton->pool = result_pool;
1875 baton->write_content = svn_stringbuf_create_empty(result_pool);
1876 baton->write_stream = svn_stream_from_stringbuf(baton->write_content,
1879 *stream = svn_stream_create(baton, result_pool);
1880 svn_stream_set_write(*stream, write_handler_special);
1881 svn_stream_set_close(*stream, close_handler_special);
1883 return SVN_NO_ERROR;
1887 /* NOTE: this function is deprecated, but we cannot move it over to
1888 deprecated.c because it uses stuff private to this file, and it is
1889 not easily rebuilt in terms of "new" functions. */
1891 svn_subst_stream_from_specialfile(svn_stream_t **stream,
1895 struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
1899 baton->path = apr_pstrdup(pool, path);
1901 err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool);
1903 /* File might not exist because we intend to create it upon close. */
1904 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1906 svn_error_clear(err);
1908 /* Note: the special file is missing. the caller won't find out
1909 until the first read. Oh well. This function is deprecated anyways,
1910 so they can just deal with the weird behavior. */
1911 baton->read_stream = NULL;
1914 baton->write_content = svn_stringbuf_create_empty(pool);
1915 baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
1917 *stream = svn_stream_create(baton, pool);
1918 svn_stream_set_read(*stream, read_handler_special);
1919 svn_stream_set_write(*stream, write_handler_special);
1920 svn_stream_set_close(*stream, close_handler_special);
1922 return SVN_NO_ERROR;
1927 /*** String translation */
1929 svn_subst_translate_string2(svn_string_t **new_value,
1930 svn_boolean_t *translated_to_utf8,
1931 svn_boolean_t *translated_line_endings,
1932 const svn_string_t *value,
1933 const char *encoding,
1934 svn_boolean_t repair,
1935 apr_pool_t *result_pool,
1936 apr_pool_t *scratch_pool)
1938 const char *val_utf8;
1939 const char *val_utf8_lf;
1944 return SVN_NO_ERROR;
1947 if (encoding && !strcmp(encoding, "UTF-8"))
1949 val_utf8 = value->data;
1953 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
1954 encoding, scratch_pool));
1958 SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool));
1961 if (translated_to_utf8)
1962 *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0);
1964 SVN_ERR(translate_cstring(&val_utf8_lf,
1965 translated_line_endings,
1967 "\n", /* translate to LF */
1969 NULL, /* no keywords */
1970 FALSE, /* no expansion */
1973 *new_value = svn_string_create(val_utf8_lf, result_pool);
1974 return SVN_NO_ERROR;
1979 svn_subst_detranslate_string(svn_string_t **new_value,
1980 const svn_string_t *value,
1981 svn_boolean_t for_output,
1985 const char *val_neol;
1986 const char *val_nlocale_neol;
1991 return SVN_NO_ERROR;
1994 SVN_ERR(svn_subst_translate_cstring2(value->data,
1996 APR_EOL_STR, /* 'native' eol */
1997 FALSE, /* no repair */
1998 NULL, /* no keywords */
1999 FALSE, /* no expansion */
2004 err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2005 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2008 svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
2009 svn_error_clear(err);
2016 err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
2017 if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
2019 val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
2020 svn_error_clear(err);
2026 *new_value = svn_string_create(val_nlocale_neol, pool);
2028 return SVN_NO_ERROR;