2 * subst.c : generic eol/keyword substitution routines
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
26 #include <apr_pools.h>
27 #include <apr_tables.h>
28 #include <apr_file_io.h>
29 #include <apr_strings.h>
31 #include "svn_cmdline.h"
32 #include "svn_types.h"
33 #include "svn_string.h"
36 #include "svn_error.h"
39 #include "svn_subst.h"
40 #include "svn_pools.h"
42 #include "svn_private_config.h"
44 /* The Repository Default EOL used for files which
45 * use the 'native' eol style.
47 #define SVN_SUBST__DEFAULT_EOL_STR "\n"
50 * The textual elements of a detranslated special file. One of these
51 * strings must appear as the first element of any special file as it
52 * exists in the repository or the text base.
54 #define SVN_SUBST__SPECIAL_LINK_STR "link"
57 svn_subst_eol_style_from_value(svn_subst_eol_style_t
*style
,
63 /* property doesn't exist. */
66 *style
= svn_subst_eol_style_none
;
68 else if (! strcmp("native", value
))
70 *eol
= APR_EOL_STR
; /* whee, a portability library! */
72 *style
= svn_subst_eol_style_native
;
74 else if (! strcmp("LF", value
))
78 *style
= svn_subst_eol_style_fixed
;
80 else if (! strcmp("CR", value
))
84 *style
= svn_subst_eol_style_fixed
;
86 else if (! strcmp("CRLF", value
))
90 *style
= svn_subst_eol_style_fixed
;
96 *style
= svn_subst_eol_style_unknown
;
102 svn_subst_translation_required(svn_subst_eol_style_t style
,
104 apr_hash_t
*keywords
,
105 svn_boolean_t special
,
106 svn_boolean_t force_eol_check
)
108 return (special
|| keywords
109 || (style
!= svn_subst_eol_style_none
&& force_eol_check
)
110 || (style
== svn_subst_eol_style_native
&&
111 strcmp(APR_EOL_STR
, SVN_SUBST__DEFAULT_EOL_STR
) != 0)
112 || (style
== svn_subst_eol_style_fixed
&&
113 strcmp(APR_EOL_STR
, eol
) != 0));
118 svn_subst_translate_to_normal_form(const char *src
,
120 svn_subst_eol_style_t eol_style
,
122 svn_boolean_t always_repair_eols
,
123 apr_hash_t
*keywords
,
124 svn_boolean_t special
,
128 if (eol_style
== svn_subst_eol_style_native
)
129 eol_str
= SVN_SUBST__DEFAULT_EOL_STR
;
130 else if (! (eol_style
== svn_subst_eol_style_fixed
131 || eol_style
== svn_subst_eol_style_none
))
132 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL
, NULL
, NULL
);
134 return svn_subst_copy_and_translate3(src
, dst
, eol_str
,
135 eol_style
== svn_subst_eol_style_fixed
136 || always_repair_eols
,
138 FALSE
/* contract keywords */,
144 svn_subst_stream_translated_to_normal_form(svn_stream_t
**stream
,
145 svn_stream_t
*source
,
146 svn_subst_eol_style_t eol_style
,
148 svn_boolean_t always_repair_eols
,
149 apr_hash_t
*keywords
,
152 if (eol_style
== svn_subst_eol_style_native
)
153 eol_str
= SVN_SUBST__DEFAULT_EOL_STR
;
154 else if (! (eol_style
== svn_subst_eol_style_fixed
155 || eol_style
== svn_subst_eol_style_none
))
156 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL
, NULL
, NULL
);
158 *stream
= svn_subst_stream_translated(source
, eol_str
,
159 eol_style
== svn_subst_eol_style_fixed
160 || always_repair_eols
,
161 keywords
, FALSE
, pool
);
167 /* Helper function for svn_subst_build_keywords */
169 /* Given a printf-like format string, return a string with proper
170 * information filled in.
172 * Important API note: This function is the core of the implementation of
173 * svn_subst_build_keywords (all versions), and as such must implement the
174 * tolerance of NULL and zero inputs that that function's documention
179 * %a author of this revision
180 * %b basename of the URL of this file
181 * %d short format of date of this revision
182 * %D long format of date of this revision
183 * %r number of this revision
184 * %u URL of this file
187 * All memory is allocated out of @a pool.
189 static svn_string_t
*
190 keyword_printf(const char *fmt
,
197 svn_stringbuf_t
*value
= svn_stringbuf_ncreate("", 0, pool
);
205 while (*cur
!= '\0' && *cur
!= '%')
208 if ((n
= cur
- fmt
) > 0) /* Do we have an as-is string? */
209 svn_stringbuf_appendbytes(value
, fmt
, n
);
216 case 'a': /* author of this revision */
218 svn_stringbuf_appendcstr(value
, author
);
220 case 'b': /* basename of this file */
223 const char *base_name
224 = svn_path_uri_decode(svn_path_basename(url
, pool
), pool
);
225 svn_stringbuf_appendcstr(value
, base_name
);
228 case 'd': /* short format of date of this revision */
231 apr_time_exp_t exploded_time
;
234 apr_time_exp_gmt(&exploded_time
, date
);
236 human
= apr_psprintf(pool
, "%04d-%02d-%02d %02d:%02d:%02dZ",
237 exploded_time
.tm_year
+ 1900,
238 exploded_time
.tm_mon
+ 1,
239 exploded_time
.tm_mday
,
240 exploded_time
.tm_hour
,
241 exploded_time
.tm_min
,
242 exploded_time
.tm_sec
);
244 svn_stringbuf_appendcstr(value
, human
);
247 case 'D': /* long format of date of this revision */
249 svn_stringbuf_appendcstr(value
,
250 svn_time_to_human_cstring(date
, pool
));
252 case 'r': /* number of this revision */
254 svn_stringbuf_appendcstr(value
, rev
);
256 case 'u': /* URL of this file */
258 svn_stringbuf_appendcstr(value
, url
);
260 case '%': /* '%%' => a literal % */
261 svn_stringbuf_appendbytes(value
, cur
, 1);
263 case '\0': /* '%' as the last character of the string. */
264 svn_stringbuf_appendbytes(value
, cur
, 1);
265 /* Now go back one character, since this was just a one character
266 * sequence, whereas all others are two characters, and we do not
267 * want to skip the null terminator entirely and carry on
268 * formatting random memory contents. */
271 default: /* Unrecognized code, just print it literally. */
272 svn_stringbuf_appendbytes(value
, cur
, 2);
276 /* Format code is processed - skip it, and get ready for next chunk. */
280 return svn_string_create_from_buf(value
, pool
);
283 /* Convert an old-style svn_subst_keywords_t struct * into a new-style
284 * keywords hash. Keyword values are shallow copies, so the produced
285 * hash must not be assumed to have lifetime longer than the struct it
286 * is based on. A NULL input causes a NULL output. */
288 kwstruct_to_kwhash(const svn_subst_keywords_t
*kwstruct
,
293 if (kwstruct
== NULL
)
296 kwhash
= apr_hash_make(pool
);
298 if (kwstruct
->revision
)
300 apr_hash_set(kwhash
, SVN_KEYWORD_REVISION_LONG
,
301 APR_HASH_KEY_STRING
, kwstruct
->revision
);
302 apr_hash_set(kwhash
, SVN_KEYWORD_REVISION_MEDIUM
,
303 APR_HASH_KEY_STRING
, kwstruct
->revision
);
304 apr_hash_set(kwhash
, SVN_KEYWORD_REVISION_SHORT
,
305 APR_HASH_KEY_STRING
, kwstruct
->revision
);
309 apr_hash_set(kwhash
, SVN_KEYWORD_DATE_LONG
,
310 APR_HASH_KEY_STRING
, kwstruct
->date
);
311 apr_hash_set(kwhash
, SVN_KEYWORD_DATE_SHORT
,
312 APR_HASH_KEY_STRING
, kwstruct
->date
);
314 if (kwstruct
->author
)
316 apr_hash_set(kwhash
, SVN_KEYWORD_AUTHOR_LONG
,
317 APR_HASH_KEY_STRING
, kwstruct
->author
);
318 apr_hash_set(kwhash
, SVN_KEYWORD_AUTHOR_SHORT
,
319 APR_HASH_KEY_STRING
, kwstruct
->author
);
323 apr_hash_set(kwhash
, SVN_KEYWORD_URL_LONG
,
324 APR_HASH_KEY_STRING
, kwstruct
->url
);
325 apr_hash_set(kwhash
, SVN_KEYWORD_URL_SHORT
,
326 APR_HASH_KEY_STRING
, kwstruct
->url
);
330 apr_hash_set(kwhash
, SVN_KEYWORD_ID
,
331 APR_HASH_KEY_STRING
, kwstruct
->id
);
338 svn_subst_build_keywords(svn_subst_keywords_t
*kw
,
339 const char *keywords_val
,
347 const svn_string_t
*val
;
349 SVN_ERR(svn_subst_build_keywords2(&kwhash
, keywords_val
, rev
,
350 url
, date
, author
, pool
));
352 /* The behaviour of pre-1.3 svn_subst_build_keywords, which we are
353 * replicating here, is to write to a slot in the svn_subst_keywords_t
354 * only if the relevant keyword was present in keywords_val, otherwise
355 * leaving that slot untouched. */
357 val
= apr_hash_get(kwhash
, SVN_KEYWORD_REVISION_LONG
, APR_HASH_KEY_STRING
);
361 val
= apr_hash_get(kwhash
, SVN_KEYWORD_DATE_LONG
, APR_HASH_KEY_STRING
);
365 val
= apr_hash_get(kwhash
, SVN_KEYWORD_AUTHOR_LONG
, APR_HASH_KEY_STRING
);
369 val
= apr_hash_get(kwhash
, SVN_KEYWORD_URL_LONG
, APR_HASH_KEY_STRING
);
373 val
= apr_hash_get(kwhash
, SVN_KEYWORD_ID
, APR_HASH_KEY_STRING
);
381 svn_subst_build_keywords2(apr_hash_t
**kw
,
382 const char *keywords_val
,
389 apr_array_header_t
*keyword_tokens
;
391 *kw
= apr_hash_make(pool
);
393 keyword_tokens
= svn_cstring_split(keywords_val
, " \t\v\n\b\r\f",
394 TRUE
/* chop */, pool
);
396 for (i
= 0; i
< keyword_tokens
->nelts
; ++i
)
398 const char *keyword
= APR_ARRAY_IDX(keyword_tokens
, i
, const char *);
400 if ((! strcmp(keyword
, SVN_KEYWORD_REVISION_LONG
))
401 || (! strcmp(keyword
, SVN_KEYWORD_REVISION_MEDIUM
))
402 || (! svn_cstring_casecmp(keyword
, SVN_KEYWORD_REVISION_SHORT
)))
404 svn_string_t
*revision_val
;
406 revision_val
= keyword_printf("%r", rev
, url
, date
, author
, pool
);
407 apr_hash_set(*kw
, SVN_KEYWORD_REVISION_LONG
,
408 APR_HASH_KEY_STRING
, revision_val
);
409 apr_hash_set(*kw
, SVN_KEYWORD_REVISION_MEDIUM
,
410 APR_HASH_KEY_STRING
, revision_val
);
411 apr_hash_set(*kw
, SVN_KEYWORD_REVISION_SHORT
,
412 APR_HASH_KEY_STRING
, revision_val
);
414 else if ((! strcmp(keyword
, SVN_KEYWORD_DATE_LONG
))
415 || (! svn_cstring_casecmp(keyword
, SVN_KEYWORD_DATE_SHORT
)))
417 svn_string_t
*date_val
;
419 date_val
= keyword_printf("%D", rev
, url
, date
, author
, pool
);
420 apr_hash_set(*kw
, SVN_KEYWORD_DATE_LONG
,
421 APR_HASH_KEY_STRING
, date_val
);
422 apr_hash_set(*kw
, SVN_KEYWORD_DATE_SHORT
,
423 APR_HASH_KEY_STRING
, date_val
);
425 else if ((! strcmp(keyword
, SVN_KEYWORD_AUTHOR_LONG
))
426 || (! svn_cstring_casecmp(keyword
, SVN_KEYWORD_AUTHOR_SHORT
)))
428 svn_string_t
*author_val
;
430 author_val
= keyword_printf("%a", rev
, url
, date
, author
, pool
);
431 apr_hash_set(*kw
, SVN_KEYWORD_AUTHOR_LONG
,
432 APR_HASH_KEY_STRING
, author_val
);
433 apr_hash_set(*kw
, SVN_KEYWORD_AUTHOR_SHORT
,
434 APR_HASH_KEY_STRING
, author_val
);
436 else if ((! strcmp(keyword
, SVN_KEYWORD_URL_LONG
))
437 || (! svn_cstring_casecmp(keyword
, SVN_KEYWORD_URL_SHORT
)))
439 svn_string_t
*url_val
;
441 url_val
= keyword_printf("%u", rev
, url
, date
, author
, pool
);
442 apr_hash_set(*kw
, SVN_KEYWORD_URL_LONG
,
443 APR_HASH_KEY_STRING
, url_val
);
444 apr_hash_set(*kw
, SVN_KEYWORD_URL_SHORT
,
445 APR_HASH_KEY_STRING
, url_val
);
447 else if ((! svn_cstring_casecmp(keyword
, SVN_KEYWORD_ID
)))
449 svn_string_t
*id_val
;
451 id_val
= keyword_printf("%b %r %d %a", rev
, url
, date
, author
,
453 apr_hash_set(*kw
, SVN_KEYWORD_ID
,
454 APR_HASH_KEY_STRING
, id_val
);
462 /*** Helpers for svn_subst_translate_stream2 ***/
465 /* Write out LEN bytes of BUF into STREAM. */
467 translate_write(svn_stream_t
*stream
,
471 apr_size_t wrote
= len
;
472 svn_error_t
*write_err
= svn_stream_write(stream
, buf
, &wrote
);
473 if ((write_err
) || (len
!= wrote
))
480 /* Perform the substition of VALUE into keyword string BUF (with len
481 *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
482 *LEN to the new size of the substituted result. Return TRUE if all
483 goes well, FALSE otherwise. If VALUE is NULL, keyword will be
484 contracted, else it will be expanded. */
486 translate_keyword_subst(char *buf
,
489 apr_size_t keyword_len
,
490 const svn_string_t
*value
)
494 /* Make sure we gotz good stuffs. */
495 assert(*len
<= SVN_KEYWORD_MAX_LEN
);
496 assert((buf
[0] == '$') && (buf
[*len
- 1] == '$'));
498 /* Need at least a keyword and two $'s. */
499 if (*len
< keyword_len
+ 2)
502 /* The keyword needs to match what we're looking for. */
503 if (strncmp(buf
+ 1, keyword
, keyword_len
))
506 buf_ptr
= buf
+ 1 + keyword_len
;
508 /* Check for fixed-length expansion.
509 * The format of fixed length keyword and its data is
510 * Unexpanded keyword: "$keyword:: $"
511 * Expanded keyword: "$keyword:: value $"
512 * Expanded kw with filling: "$keyword:: value $"
513 * Truncated keyword: "$keyword:: longval#$"
515 if ((buf_ptr
[0] == ':') /* first char after keyword is ':' */
516 && (buf_ptr
[1] == ':') /* second char after keyword is ':' */
517 && (buf_ptr
[2] == ' ') /* third char after keyword is ' ' */
518 && ((buf
[*len
- 2] == ' ') /* has ' ' for next to last character */
519 || (buf
[*len
- 2] == '#')) /* .. or has '#' for next to last
521 && ((6 + keyword_len
) < *len
)) /* holds "$kw:: x $" at least */
523 /* This is fixed length keyword, so *len remains unchanged */
524 apr_size_t max_value_len
= *len
- (6 + keyword_len
);
528 /* no value, so unexpand */
530 while (*buf_ptr
!= '$')
535 if (value
->len
<= max_value_len
)
536 { /* replacement not as long as template, pad with spaces */
537 strncpy(buf_ptr
+ 3, value
->data
, value
->len
);
538 buf_ptr
+= 3 + value
->len
;
539 while (*buf_ptr
!= '$')
544 /* replacement needs truncating */
545 strncpy(buf_ptr
+ 3, value
->data
, max_value_len
);
553 /* Check for unexpanded keyword. */
554 else if (buf_ptr
[0] == '$') /* "$keyword$" */
564 apr_size_t vallen
= value
->len
;
566 /* "$keyword: value $" */
567 if (vallen
> (SVN_KEYWORD_MAX_LEN
- 5 - keyword_len
))
568 vallen
= SVN_KEYWORD_MAX_LEN
- 5 - keyword_len
;
569 strncpy(buf_ptr
+ 2, value
->data
, vallen
);
570 buf_ptr
[2 + vallen
] = ' ';
571 buf_ptr
[2 + vallen
+ 1] = '$';
572 *len
= 5 + keyword_len
+ vallen
;
578 *len
= 4 + keyword_len
;
583 /* ...but do nothing. */
588 /* Check for expanded keyword. */
589 else if (((*len
>= 4 + keyword_len
) /* holds at least "$keyword: $" */
590 && (buf_ptr
[0] == ':') /* first char after keyword is ':' */
591 && (buf_ptr
[1] == ' ') /* second char after keyword is ' ' */
592 && (buf
[*len
- 2] == ' '))
593 || ((*len
>= 3 + keyword_len
) /* holds at least "$keyword:$" */
594 && (buf_ptr
[0] == ':') /* first char after keyword is ':' */
595 && (buf_ptr
[1] == '$'))) /* second char after keyword is '$' */
600 /* ...so unexpand. */
602 *len
= 2 + keyword_len
;
606 /* ...so re-expand. */
611 apr_size_t vallen
= value
->len
;
613 /* "$keyword: value $" */
614 if (vallen
> (SVN_KEYWORD_MAX_LEN
- 5))
615 vallen
= SVN_KEYWORD_MAX_LEN
- 5;
616 strncpy(buf_ptr
+ 2, value
->data
, vallen
);
617 buf_ptr
[2 + vallen
] = ' ';
618 buf_ptr
[2 + vallen
+ 1] = '$';
619 *len
= 5 + keyword_len
+ vallen
;
625 *len
= 4 + keyword_len
;
634 /* Parse BUF (whose length is LEN, and which starts and ends with '$'),
635 trying to match one of the keyword names in KEYWORDS. If such a
636 keyword is found, update *KEYWORD_NAME with the keyword name and
639 match_keyword(char *buf
,
642 apr_hash_t
*keywords
)
646 /* Early return for ignored keywords */
650 /* Extract the name of the keyword */
651 for (i
= 0; i
< len
- 2 && buf
[i
+ 1] != ':'; i
++)
652 keyword_name
[i
] = buf
[i
+ 1];
653 keyword_name
[i
] = '\0';
655 return apr_hash_get(keywords
, keyword_name
, APR_HASH_KEY_STRING
) != NULL
;
658 /* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
659 optionally perform the substitution in place, update *LEN with
660 the new length of the translated keyword string, and return TRUE.
661 If this buffer doesn't contain a known keyword pattern, leave BUF
662 and *LEN untouched and return FALSE.
664 See the docstring for svn_subst_copy_and_translate for how the
665 EXPAND and KEYWORDS parameters work.
667 NOTE: It is assumed that BUF has been allocated to be at least
668 SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
669 than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions
670 which would result in a keyword string which is greater than
671 SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
672 that the resultant keyword string is still valid (begins with
673 "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */
675 translate_keyword(char *buf
,
677 const char *keyword_name
,
678 svn_boolean_t expand
,
679 apr_hash_t
*keywords
)
681 const svn_string_t
*value
;
683 /* Make sure we gotz good stuffs. */
684 assert(*len
<= SVN_KEYWORD_MAX_LEN
);
685 assert((buf
[0] == '$') && (buf
[*len
- 1] == '$'));
687 /* Early return for ignored keywords */
691 value
= apr_hash_get(keywords
, keyword_name
, APR_HASH_KEY_STRING
);
695 return translate_keyword_subst(buf
, len
,
696 keyword_name
, strlen(keyword_name
),
697 expand
? value
: NULL
);
704 /* Translate NEWLINE_BUF (length of NEWLINE_LEN) to the newline format
705 specified in EOL_STR (length of EOL_STR_LEN), and write the
706 translated thing to FILE (whose path is DST_PATH).
708 SRC_FORMAT (length *SRC_FORMAT_LEN) is a cache of the first newline
709 found while processing SRC_PATH. If the current newline is not the
710 same style as that of SRC_FORMAT, look to the REPAIR parameter. If
711 REPAIR is TRUE, ignore the inconsistency, else return an
712 SVN_ERR_IO_INCONSISTENT_EOL error. If we are examining the first
713 newline in the file, copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
714 use for later consistency checks. */
716 translate_newline(const char *eol_str
,
717 apr_size_t eol_str_len
,
719 apr_size_t
*src_format_len
,
721 apr_size_t newline_len
,
723 svn_boolean_t repair
)
725 /* If this is the first newline we've seen, cache it
726 future comparisons, else compare it with our cache to
727 check for consistency. */
730 /* Comparing with cache. If we are inconsistent and
731 we are NOT repairing the file, generate an error! */
733 ((*src_format_len
!= newline_len
) ||
734 (strncmp(src_format
, newline_buf
, newline_len
))))
735 return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL
, NULL
, NULL
);
739 /* This is our first line ending, so cache it before
741 strncpy(src_format
, newline_buf
, newline_len
);
742 *src_format_len
= newline_len
;
744 /* Translate the newline */
745 return translate_write(dst
, eol_str
, eol_str_len
);
750 /*** Public interfaces. ***/
753 svn_subst_keywords_differ(const svn_subst_keywords_t
*a
,
754 const svn_subst_keywords_t
*b
,
755 svn_boolean_t compare_values
)
757 if (((a
== NULL
) && (b
== NULL
)) /* no A or B */
758 /* no A, and B has no contents */
760 && (b
->revision
== NULL
)
762 && (b
->author
== NULL
)
764 /* no B, and A has no contents */
765 || ((b
== NULL
) && (a
->revision
== NULL
)
767 && (a
->author
== NULL
)
769 /* neither A nor B has any contents */
770 || ((a
!= NULL
) && (b
!= NULL
)
771 && (b
->revision
== NULL
)
773 && (b
->author
== NULL
)
775 && (a
->revision
== NULL
)
777 && (a
->author
== NULL
)
778 && (a
->url
== NULL
)))
782 else if ((a
== NULL
) || (b
== NULL
))
785 /* Else both A and B have some keywords. */
787 if ((! a
->revision
) != (! b
->revision
))
789 else if ((compare_values
&& (a
->revision
!= NULL
))
790 && (strcmp(a
->revision
->data
, b
->revision
->data
) != 0))
793 if ((! a
->date
) != (! b
->date
))
795 else if ((compare_values
&& (a
->date
!= NULL
))
796 && (strcmp(a
->date
->data
, b
->date
->data
) != 0))
799 if ((! a
->author
) != (! b
->author
))
801 else if ((compare_values
&& (a
->author
!= NULL
))
802 && (strcmp(a
->author
->data
, b
->author
->data
) != 0))
805 if ((! a
->url
) != (! b
->url
))
807 else if ((compare_values
&& (a
->url
!= NULL
))
808 && (strcmp(a
->url
->data
, b
->url
->data
) != 0))
811 /* Else we never found a difference, so they must be the same. */
817 svn_subst_keywords_differ2(apr_hash_t
*a
,
819 svn_boolean_t compare_values
,
822 apr_hash_index_t
*hi
;
823 unsigned int a_count
, b_count
;
825 /* An empty hash is logically equal to a NULL,
826 * as far as this API is concerned. */
827 a_count
= (a
== NULL
) ? 0 : apr_hash_count(a
);
828 b_count
= (b
== NULL
) ? 0 : apr_hash_count(b
);
830 if (a_count
!= b_count
)
836 /* The hashes are both non-NULL, and have the same number of items.
837 * We must check that every item of A is present in B. */
838 for (hi
= apr_hash_first(pool
, a
); hi
; hi
= apr_hash_next(hi
))
843 svn_string_t
*a_val
, *b_val
;
845 apr_hash_this(hi
, &key
, &klen
, &void_a_val
);
847 b_val
= apr_hash_get(b
, key
, klen
);
849 if (!b_val
|| (compare_values
&& !svn_string_compare(a_val
, b_val
)))
857 svn_subst_translate_stream2(svn_stream_t
*s
, /* src stream */
858 svn_stream_t
*d
, /* dst stream */
860 svn_boolean_t repair
,
861 const svn_subst_keywords_t
*keywords
,
862 svn_boolean_t expand
,
865 apr_hash_t
*kh
= kwstruct_to_kwhash(keywords
, pool
);
867 return svn_subst_translate_stream3(s
, d
, eol_str
, repair
, kh
, expand
, pool
);
870 /* Baton for translate_chunk() to store its state in. */
871 struct translation_baton
874 svn_boolean_t repair
;
875 apr_hash_t
*keywords
;
876 svn_boolean_t expand
;
878 /* Characters (excluding the terminating NUL character) which
879 may trigger a translation action, hence are 'interesting' */
880 const char *interesting
;
882 /* Length of the string EOL_STR points to. */
883 apr_size_t eol_str_len
;
885 /* Buffer to cache any newline state between translation chunks */
888 /* Offset (within newline_buf) of the first *unused* character */
889 apr_size_t newline_off
;
891 /* Buffer to cache keyword-parsing state between translation chunks */
892 char keyword_buf
[SVN_KEYWORD_MAX_LEN
];
894 /* Offset (within keyword-buf) to the first *unused* character */
895 apr_size_t keyword_off
;
897 /* EOL style used in the chunk-source */
900 /* Length of the EOL style string found in the chunk-source,
901 or zero if none encountered yet */
902 apr_size_t src_format_len
;
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.
914 static struct translation_baton
*
915 create_translation_baton(const char *eol_str
,
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;
930 b
->keywords
= keywords
;
932 b
->interesting
= (eol_str
&& keywords
) ? "$\r\n" : eol_str
? "\r\n" : "$";
935 b
->src_format_len
= 0;
940 /* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
941 * according to the settings and state stored in baton B.
943 * Write output to stream DST.
945 * To finish a series of chunk translations, flush all buffers by calling
946 * this routine with a NULL value for BUF.
948 * Use POOL for temporary allocations.
951 translate_chunk(svn_stream_t
*dst
,
952 struct translation_baton
*b
,
962 /* precalculate some oft-used values */
963 const char *end
= buf
+ buflen
;
964 const char *interesting
= b
->interesting
;
965 apr_size_t next_sign_off
= 0;
967 /* At the beginning of this loop, assume that we might be in an
968 * interesting state, i.e. with data in the newline or keyword
969 * buffer. First try to get to the boring state so we can copy
970 * a run of boring characters; then try to get back to the
971 * interesting state by processing an interesting character,
973 for (p
= buf
; p
< end
;)
975 /* Try to get to the boring state, if necessary. */
979 b
->newline_buf
[b
->newline_off
++] = *p
++;
981 SVN_ERR(translate_newline(b
->eol_str
, b
->eol_str_len
,
983 &b
->src_format_len
, b
->newline_buf
,
984 b
->newline_off
, dst
, b
->repair
));
988 else if (b
->keyword_off
&& *p
== '$')
990 svn_boolean_t keyword_matches
;
991 char keyword_name
[SVN_KEYWORD_MAX_LEN
+ 1];
993 /* If keyword is matched, but not correctly translated, try to
994 * look for the next ending '$'. */
995 b
->keyword_buf
[b
->keyword_off
++] = *p
++;
996 keyword_matches
= match_keyword(b
->keyword_buf
, b
->keyword_off
,
997 keyword_name
, b
->keywords
);
998 if (keyword_matches
== FALSE
)
1000 /* reuse the ending '$' */
1005 if (keyword_matches
== FALSE
||
1006 translate_keyword(b
->keyword_buf
, &b
->keyword_off
,
1007 keyword_name
, b
->expand
, b
->keywords
) ||
1008 b
->keyword_off
>= SVN_KEYWORD_MAX_LEN
)
1010 /* write out non-matching text or translated keyword */
1011 SVN_ERR(translate_write(dst
, b
->keyword_buf
, b
->keyword_off
));
1018 if (next_sign_off
== 0)
1019 next_sign_off
= b
->keyword_off
- 1;
1024 else if (b
->keyword_off
== SVN_KEYWORD_MAX_LEN
- 1
1025 || (b
->keyword_off
&& (*p
== '\r' || *p
== '\n')))
1027 if (next_sign_off
> 0)
1029 /* rolling back, continue with next '$' in keyword_buf */
1030 p
-= (b
->keyword_off
- next_sign_off
);
1031 b
->keyword_off
= next_sign_off
;
1034 /* No closing '$' found; flush the keyword buffer. */
1035 SVN_ERR(translate_write(dst
, b
->keyword_buf
, b
->keyword_off
));
1039 else if (b
->keyword_off
)
1041 b
->keyword_buf
[b
->keyword_off
++] = *p
++;
1045 /* We're in the boring state; look for interest characters. */
1048 /* We wanted memcspn(), but lacking that, the loop below has
1051 Also, skip NUL characters explicitly, since strchr()
1052 considers them part of the string argument,
1053 but we don't consider them interesting
1055 while ((p
+ len
) < end
1056 && (! p
[len
] || ! strchr(interesting
, p
[len
])))
1060 SVN_ERR(translate_write(dst
, p
, len
));
1064 /* Set up state according to the interesting character, if any. */
1070 b
->keyword_buf
[b
->keyword_off
++] = *p
++;
1073 b
->newline_buf
[b
->newline_off
++] = *p
++;
1076 b
->newline_buf
[b
->newline_off
++] = *p
++;
1078 SVN_ERR(translate_newline(b
->eol_str
, b
->eol_str_len
,
1082 b
->newline_off
, dst
, b
->repair
));
1095 SVN_ERR(translate_newline(b
->eol_str
, b
->eol_str_len
,
1096 b
->src_format
, &b
->src_format_len
,
1097 b
->newline_buf
, b
->newline_off
,
1104 SVN_ERR(translate_write(dst
, b
->keyword_buf
, b
->keyword_off
));
1109 return SVN_NO_ERROR
;
1112 /* Baton for use with translated stream callbacks. */
1113 struct translated_stream_baton
1115 /* Stream to take input from (before translation) on read
1116 /write output to (after translation) on write. */
1117 svn_stream_t
*stream
;
1119 /* Input/Output translation batons to make them separate chunk streams. */
1120 struct translation_baton
*in_baton
, *out_baton
;
1122 /* Remembers whether any write operations have taken place;
1123 if so, we need to flush the output chunk stream. */
1124 svn_boolean_t written
;
1126 /* Buffer to hold translated read data. */
1127 svn_stringbuf_t
*readbuf
;
1129 /* Offset of the first non-read character in readbuf. */
1130 apr_size_t readbuf_off
;
1132 /* Buffer to hold read data
1133 between svn_stream_read() and translate_chunk(). */
1136 /* Pool in which (only!) this baton is allocated. */
1139 /* Pool for callback iterations */
1140 apr_pool_t
*iterpool
;
1144 static svn_error_t
*
1145 translated_stream_read(void *baton
,
1149 struct translated_stream_baton
*b
= baton
;
1150 apr_size_t readlen
= SVN__STREAM_CHUNK_SIZE
;
1151 apr_size_t unsatisfied
= *len
;
1153 apr_pool_t
*iterpool
;
1155 iterpool
= b
->iterpool
;
1156 while (readlen
== SVN__STREAM_CHUNK_SIZE
&& unsatisfied
> 0)
1159 apr_size_t buffer_remainder
;
1161 svn_pool_clear(iterpool
);
1162 /* fill read buffer, if necessary */
1163 if (! (b
->readbuf_off
< b
->readbuf
->len
))
1165 svn_stream_t
*buf_stream
;
1167 svn_stringbuf_setempty(b
->readbuf
);
1169 SVN_ERR(svn_stream_read(b
->stream
, b
->buf
, &readlen
));
1170 buf_stream
= svn_stream_from_stringbuf(b
->readbuf
, iterpool
);
1172 SVN_ERR(translate_chunk(buf_stream
, b
->in_baton
, b
->buf
,
1173 readlen
, iterpool
));
1175 if (readlen
!= SVN__STREAM_CHUNK_SIZE
)
1176 SVN_ERR(translate_chunk(buf_stream
, b
->in_baton
, NULL
, 0,
1179 SVN_ERR(svn_stream_close(buf_stream
));
1182 /* Satisfy from the read buffer */
1183 buffer_remainder
= b
->readbuf
->len
- b
->readbuf_off
;
1184 to_copy
= (buffer_remainder
> unsatisfied
)
1185 ? unsatisfied
: buffer_remainder
;
1186 memcpy(buffer
+ off
, b
->readbuf
->data
+ b
->readbuf_off
, to_copy
);
1188 b
->readbuf_off
+= to_copy
;
1189 unsatisfied
-= to_copy
;
1192 *len
-= unsatisfied
;
1194 return SVN_NO_ERROR
;
1197 static svn_error_t
*
1198 translated_stream_write(void *baton
,
1202 struct translated_stream_baton
*b
= baton
;
1203 svn_pool_clear(b
->iterpool
);
1206 SVN_ERR(translate_chunk(b
->stream
, b
->out_baton
, buffer
, *len
,
1209 return SVN_NO_ERROR
;
1212 static svn_error_t
*
1213 translated_stream_close(void *baton
)
1215 struct translated_stream_baton
*b
= baton
;
1218 SVN_ERR(translate_chunk(b
->stream
, b
->out_baton
, NULL
, 0, b
->iterpool
));
1220 SVN_ERR(svn_stream_close(b
->stream
));
1222 svn_pool_destroy(b
->pool
); /* Also destroys the baton itself */
1223 return SVN_NO_ERROR
;
1226 /* Given a special file at SRC, set TRANSLATED_STREAM_P to a stream
1227 with the textual representation of it. Perform all allocations in POOL. */
1228 static svn_error_t
*
1229 detranslated_stream_special(svn_stream_t
**translated_stream_p
,
1236 svn_stringbuf_t
*strbuf
;
1238 /* First determine what type of special file we are
1240 SVN_ERR(svn_io_stat(&finfo
, src
, APR_FINFO_MIN
| APR_FINFO_LINK
, pool
));
1242 switch (finfo
.filetype
) {
1244 /* Nothing special to do here, just create stream from the original
1246 SVN_ERR(svn_io_file_open(&s
, src
, APR_READ
| APR_BUFFERED
,
1247 APR_OS_DEFAULT
, pool
));
1248 *translated_stream_p
= svn_stream_from_aprfile2(s
, FALSE
, pool
);
1252 /* Determine the destination of the link. */
1253 SVN_ERR(svn_io_read_link(&buf
, src
, pool
));
1254 strbuf
= svn_stringbuf_createf(pool
, "link %s", buf
->data
);
1255 *translated_stream_p
= svn_stream_from_stringbuf(strbuf
, pool
);
1262 return SVN_NO_ERROR
;
1266 svn_subst_stream_detranslated(svn_stream_t
**stream_p
,
1268 svn_subst_eol_style_t eol_style
,
1269 const char *eol_str
,
1270 svn_boolean_t always_repair_eols
,
1271 apr_hash_t
*keywords
,
1272 svn_boolean_t special
,
1276 svn_stream_t
*src_stream
;
1279 return detranslated_stream_special(stream_p
, src
, pool
);
1281 if (eol_style
== svn_subst_eol_style_native
)
1282 eol_str
= SVN_SUBST__DEFAULT_EOL_STR
;
1283 else if (! (eol_style
== svn_subst_eol_style_fixed
1284 || eol_style
== svn_subst_eol_style_none
))
1285 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL
, NULL
, NULL
);
1287 SVN_ERR(svn_io_file_open(&file_h
, src
, APR_READ
,
1288 APR_OS_DEFAULT
, pool
));
1290 src_stream
= svn_stream_from_aprfile2(file_h
, FALSE
, pool
);
1292 *stream_p
= svn_subst_stream_translated(
1293 src_stream
, eol_str
,
1294 eol_style
== svn_subst_eol_style_fixed
|| always_repair_eols
,
1295 keywords
, FALSE
, pool
);
1297 return SVN_NO_ERROR
;
1301 svn_subst_stream_translated(svn_stream_t
*stream
,
1302 const char *eol_str
,
1303 svn_boolean_t repair
,
1304 apr_hash_t
*keywords
,
1305 svn_boolean_t expand
,
1308 apr_pool_t
*baton_pool
= svn_pool_create(pool
);
1309 struct translated_stream_baton
*baton
1310 = apr_palloc(baton_pool
, sizeof(*baton
));
1311 svn_stream_t
*s
= svn_stream_create(baton
, baton_pool
);
1313 /* Make sure EOL_STR and KEYWORDS are allocated in POOL, as
1314 required by create_translation_baton() */
1316 eol_str
= apr_pstrdup(baton_pool
, eol_str
);
1319 if (apr_hash_count(keywords
) == 0)
1323 /* deep copy the hash to make sure it's allocated in POOL */
1324 apr_hash_t
*copy
= apr_hash_make(baton_pool
);
1325 apr_hash_index_t
*hi
;
1327 for (hi
= apr_hash_first(pool
, keywords
);
1328 hi
; hi
= apr_hash_next(hi
))
1332 apr_hash_this(hi
, &key
, NULL
, &val
);
1333 apr_hash_set(copy
, apr_pstrdup(baton_pool
, key
),
1334 APR_HASH_KEY_STRING
,
1335 svn_string_dup(val
, baton_pool
));
1342 /* Setup the baton fields */
1343 baton
->stream
= stream
;
1345 = create_translation_baton(eol_str
, repair
, keywords
, expand
, baton_pool
);
1347 = create_translation_baton(eol_str
, repair
, keywords
, expand
, baton_pool
);
1348 baton
->written
= FALSE
;
1349 baton
->readbuf
= svn_stringbuf_create("", baton_pool
);
1350 baton
->readbuf_off
= 0;
1351 baton
->iterpool
= svn_pool_create(baton_pool
);
1352 baton
->pool
= baton_pool
;
1353 baton
->buf
= apr_palloc(baton
->pool
, SVN__STREAM_CHUNK_SIZE
+ 1);
1355 /* Setup the stream methods */
1356 svn_stream_set_read(s
, translated_stream_read
);
1357 svn_stream_set_write(s
, translated_stream_write
);
1358 svn_stream_set_close(s
, translated_stream_close
);
1365 svn_subst_translate_stream3(svn_stream_t
*s
, /* src stream */
1366 svn_stream_t
*d
, /* dst stream */
1367 const char *eol_str
,
1368 svn_boolean_t repair
,
1369 apr_hash_t
*keywords
,
1370 svn_boolean_t expand
,
1373 apr_pool_t
*subpool
= svn_pool_create(pool
);
1374 apr_pool_t
*iterpool
= svn_pool_create(subpool
);
1375 struct translation_baton
*baton
;
1376 apr_size_t readlen
= SVN__STREAM_CHUNK_SIZE
;
1377 char *buf
= apr_palloc(subpool
, SVN__STREAM_CHUNK_SIZE
);
1379 /* The docstring requires that *some* translation be requested. */
1380 assert(eol_str
|| keywords
);
1382 baton
= create_translation_baton(eol_str
, repair
, keywords
, expand
, pool
);
1383 while (readlen
== SVN__STREAM_CHUNK_SIZE
)
1385 svn_pool_clear(iterpool
);
1386 SVN_ERR(svn_stream_read(s
, buf
, &readlen
));
1387 SVN_ERR(translate_chunk(d
, baton
, buf
, readlen
, iterpool
));
1390 SVN_ERR(translate_chunk(d
, baton
, NULL
, 0, iterpool
));
1392 svn_pool_destroy(subpool
); /* also destroys iterpool */
1393 return SVN_NO_ERROR
;
1398 svn_subst_translate_stream(svn_stream_t
*s
, /* src stream */
1399 svn_stream_t
*d
, /* dst stream */
1400 const char *eol_str
,
1401 svn_boolean_t repair
,
1402 const svn_subst_keywords_t
*keywords
,
1403 svn_boolean_t expand
)
1405 apr_pool_t
*pool
= svn_pool_create(NULL
);
1406 svn_error_t
*err
= svn_subst_translate_stream2(s
, d
, eol_str
, repair
,
1407 keywords
, expand
, pool
);
1408 svn_pool_destroy(pool
);
1414 svn_subst_translate_cstring(const char *src
,
1416 const char *eol_str
,
1417 svn_boolean_t repair
,
1418 const svn_subst_keywords_t
*keywords
,
1419 svn_boolean_t expand
,
1422 apr_hash_t
*kh
= kwstruct_to_kwhash(keywords
, pool
);
1424 return svn_subst_translate_cstring2(src
, dst
, eol_str
, repair
,
1429 svn_subst_translate_cstring2(const char *src
,
1431 const char *eol_str
,
1432 svn_boolean_t repair
,
1433 apr_hash_t
*keywords
,
1434 svn_boolean_t expand
,
1437 svn_stringbuf_t
*src_stringbuf
, *dst_stringbuf
;
1438 svn_stream_t
*src_stream
, *dst_stream
;
1441 src_stringbuf
= svn_stringbuf_create(src
, pool
);
1443 /* The easy way out: no translation needed, just copy. */
1444 if (! (eol_str
|| (keywords
&& (apr_hash_count(keywords
) > 0))))
1446 dst_stringbuf
= svn_stringbuf_dup(src_stringbuf
, pool
);
1450 /* Convert our stringbufs into streams. */
1451 src_stream
= svn_stream_from_stringbuf(src_stringbuf
, pool
);
1452 dst_stringbuf
= svn_stringbuf_create("", pool
);
1453 dst_stream
= svn_stream_from_stringbuf(dst_stringbuf
, pool
);
1455 /* Translate src stream into dst stream. */
1456 err
= svn_subst_translate_stream3(src_stream
, dst_stream
,
1457 eol_str
, repair
, keywords
, expand
, pool
);
1460 svn_error_clear(svn_stream_close(src_stream
));
1461 svn_error_clear(svn_stream_close(dst_stream
));
1465 /* clean up nicely. */
1466 SVN_ERR(svn_stream_close(src_stream
));
1467 SVN_ERR(svn_stream_close(dst_stream
));
1470 *dst
= dst_stringbuf
->data
;
1471 return SVN_NO_ERROR
;
1476 svn_subst_copy_and_translate(const char *src
,
1478 const char *eol_str
,
1479 svn_boolean_t repair
,
1480 const svn_subst_keywords_t
*keywords
,
1481 svn_boolean_t expand
,
1484 return svn_subst_copy_and_translate2(src
, dst
, eol_str
, repair
, keywords
,
1485 expand
, FALSE
, pool
);
1489 /* Set SRC_STREAM to a stream from which the internal representation
1490 * for the special file at SRC can be read.
1492 * The stream returned will be allocated in POOL.
1494 static svn_error_t
*
1495 detranslate_special_file_to_stream(svn_stream_t
**src_stream
,
1503 /* First determine what type of special file we are
1505 SVN_ERR(svn_io_stat(&finfo
, src
, APR_FINFO_MIN
| APR_FINFO_LINK
, pool
));
1507 switch (finfo
.filetype
) {
1509 /* Nothing special to do here, just copy the original file's
1511 SVN_ERR(svn_io_file_open(&s
, src
, APR_READ
| APR_BUFFERED
,
1512 APR_OS_DEFAULT
, pool
));
1513 *src_stream
= svn_stream_from_aprfile(s
, pool
);
1517 /* Determine the destination of the link. */
1519 *src_stream
= svn_stream_from_stringbuf(svn_stringbuf_create ("", pool
),
1521 SVN_ERR(svn_io_read_link(&buf
, src
, pool
));
1523 SVN_ERR(svn_stream_printf(*src_stream
, pool
, "link %s",
1530 return SVN_NO_ERROR
;
1533 /* Given a special file at SRC, generate a textual representation of
1534 it in a normal file at DST. Perform all allocations in POOL. */
1535 static svn_error_t
*
1536 detranslate_special_file(const char *src
, const char *dst
, apr_pool_t
*pool
)
1538 const char *dst_tmp
;
1540 svn_stream_t
*src_stream
, *dst_stream
;
1543 /* Open a temporary destination that we will eventually atomically
1544 rename into place. */
1545 SVN_ERR(svn_io_open_unique_file2(&d
, &dst_tmp
, dst
,
1546 ".tmp", svn_io_file_del_none
, pool
));
1548 dst_stream
= svn_stream_from_aprfile2(d
, FALSE
, pool
);
1550 SVN_ERR(detranslate_special_file_to_stream(&src_stream
, src
, pool
));
1551 SVN_ERR(svn_stream_copy(src_stream
, dst_stream
, pool
));
1553 SVN_ERR(svn_stream_close(dst_stream
));
1555 /* Do the atomic rename from our temporary location. */
1556 SVN_ERR(svn_io_file_rename(dst_tmp
, dst
, pool
));
1558 return SVN_NO_ERROR
;
1561 /* Creates a special file DST from the internal representation given
1564 * All temporary allocations will be done in POOL.
1566 static svn_error_t
*
1567 create_special_file_from_stringbuf(svn_stringbuf_t
*src
, const char *dst
,
1570 char *identifier
, *remainder
;
1571 const char *dst_tmp
;
1572 svn_boolean_t create_using_internal_representation
= FALSE
;
1574 /* Separate off the identifier. The first space character delimits
1575 the identifier, after which any remaining characters are specific
1576 to the actual special file type being created. */
1577 identifier
= src
->data
;
1578 for (remainder
= identifier
; *remainder
; remainder
++)
1580 if (*remainder
== ' ')
1587 if (! strncmp(identifier
, SVN_SUBST__SPECIAL_LINK_STR
" ",
1588 strlen(SVN_SUBST__SPECIAL_LINK_STR
" ")))
1590 /* For symlinks, the type specific data is just a filesystem
1591 path that the symlink should reference. */
1592 svn_error_t
*err
= svn_io_create_unique_link(&dst_tmp
, dst
, remainder
,
1595 /* If we had an error, check to see if it was because symlinks are
1596 not supported on the platform. If so, fall back
1597 to using the internal representation. */
1600 if (err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
)
1602 svn_error_clear(err
);
1603 create_using_internal_representation
= TRUE
;
1611 /* Just create a normal file using the internal special file
1612 representation. We don't want a commit of an unknown special
1613 file type to DoS all the clients. */
1614 create_using_internal_representation
= TRUE
;
1617 /* If nothing else worked, write out the internal representation to
1618 a file that can be edited by the user. */
1619 if (create_using_internal_representation
)
1621 apr_file_t
*dst_tmp_file
;
1625 SVN_ERR(svn_io_open_unique_file2(&dst_tmp_file
, &dst_tmp
,
1626 dst
, ".tmp", svn_io_file_del_none
,
1628 SVN_ERR(svn_io_file_write_full(dst_tmp_file
, src
->data
, src
->len
,
1630 SVN_ERR(svn_io_file_close(dst_tmp_file
, pool
));
1633 /* Do the atomic rename from our temporary location. */
1634 return svn_io_file_rename(dst_tmp
, dst
, pool
);
1637 /* Given a file containing a repository representation of a special
1638 file in SRC, create the appropriate special file at location DST.
1639 Perform all allocations in POOL. */
1640 static svn_error_t
*
1641 create_special_file(const char *src
, const char *dst
, apr_pool_t
*pool
)
1643 svn_stringbuf_t
*contents
;
1644 svn_node_kind_t kind
;
1645 svn_boolean_t is_special
;
1646 svn_stream_t
*source
;
1648 /* Check to see if we are being asked to create a special file from
1649 a special file. If so, do a temporary detranslation and work
1651 SVN_ERR(svn_io_check_special_path(src
, &kind
, &is_special
, pool
));
1657 SVN_ERR(detranslate_special_file_to_stream(&source
, src
, pool
));
1658 /* The special file normal form doesn't have line endings,
1659 * so, read all of the file into the stringbuf */
1660 SVN_ERR(svn_stream_readline(source
, &contents
, "\n", &eof
, pool
));
1663 /* Read in the detranslated file. */
1664 SVN_ERR(svn_stringbuf_from_file(&contents
, src
, pool
));
1666 return create_special_file_from_stringbuf(contents
, dst
, pool
);
1671 svn_subst_copy_and_translate2(const char *src
,
1673 const char *eol_str
,
1674 svn_boolean_t repair
,
1675 const svn_subst_keywords_t
*keywords
,
1676 svn_boolean_t expand
,
1677 svn_boolean_t special
,
1680 apr_hash_t
*kh
= kwstruct_to_kwhash(keywords
, pool
);
1682 return svn_subst_copy_and_translate3(src
, dst
, eol_str
,
1683 repair
, kh
, expand
, special
,
1688 svn_subst_copy_and_translate3(const char *src
,
1690 const char *eol_str
,
1691 svn_boolean_t repair
,
1692 apr_hash_t
*keywords
,
1693 svn_boolean_t expand
,
1694 svn_boolean_t special
,
1697 const char *dst_tmp
= NULL
;
1698 svn_stream_t
*src_stream
, *dst_stream
;
1699 apr_file_t
*s
= NULL
, *d
= NULL
; /* init to null important for APR */
1701 svn_node_kind_t kind
;
1702 svn_boolean_t path_special
;
1704 SVN_ERR(svn_io_check_special_path(src
, &kind
, &path_special
, pool
));
1706 /* If this is a 'special' file, we may need to create it or
1708 if (special
|| path_special
)
1711 SVN_ERR(create_special_file(src
, dst
, pool
));
1713 SVN_ERR(detranslate_special_file(src
, dst
, pool
));
1715 return SVN_NO_ERROR
;
1718 /* The easy way out: no translation needed, just copy. */
1719 if (! (eol_str
|| (keywords
&& (apr_hash_count(keywords
) > 0))))
1720 return svn_io_copy_file(src
, dst
, FALSE
, pool
);
1722 /* Open source file. */
1723 SVN_ERR(svn_io_file_open(&s
, src
, APR_READ
| APR_BUFFERED
,
1724 APR_OS_DEFAULT
, pool
));
1726 /* For atomicity, we translate to a tmp file and
1727 then rename the tmp file over the real destination. */
1728 SVN_ERR(svn_io_open_unique_file2(&d
, &dst_tmp
, dst
,
1729 ".tmp", svn_io_file_del_on_pool_cleanup
,
1732 /* Now convert our two open files into streams. */
1733 src_stream
= svn_stream_from_aprfile(s
, pool
);
1734 dst_stream
= svn_stream_from_aprfile(d
, pool
);
1736 /* Translate src stream into dst stream. */
1737 err
= svn_subst_translate_stream3(src_stream
, dst_stream
, eol_str
,
1738 repair
, keywords
, expand
, pool
);
1741 if (err
->apr_err
== SVN_ERR_IO_INCONSISTENT_EOL
)
1742 return svn_error_createf
1743 (SVN_ERR_IO_INCONSISTENT_EOL
, err
,
1744 _("File '%s' has inconsistent newlines"),
1745 svn_path_local_style(src
, pool
));
1750 /* clean up nicely. */
1751 SVN_ERR(svn_stream_close(src_stream
));
1752 SVN_ERR(svn_stream_close(dst_stream
));
1753 SVN_ERR(svn_io_file_close(s
, pool
));
1754 SVN_ERR(svn_io_file_close(d
, pool
));
1756 /* Now that dst_tmp contains the translated data, do the atomic rename. */
1757 SVN_ERR(svn_io_file_rename(dst_tmp
, dst
, pool
));
1759 return SVN_NO_ERROR
;
1763 /*** 'Special file' stream support */
1765 struct special_stream_baton
1767 svn_stream_t
*read_stream
;
1768 svn_stringbuf_t
*write_content
;
1769 svn_stream_t
*write_stream
;
1775 static svn_error_t
*
1776 read_handler_special(void *baton
, char *buffer
, apr_size_t
*len
)
1778 struct special_stream_baton
*btn
= baton
;
1780 if (btn
->read_stream
)
1781 /* We actually found a file to read from */
1782 return svn_stream_read(btn
->read_stream
, buffer
, len
);
1784 return svn_error_createf(APR_ENOENT
, NULL
,
1785 "Can't read special file: File '%s' not found",
1786 svn_path_local_style (btn
->path
, btn
->pool
));
1789 static svn_error_t
*
1790 write_handler_special(void *baton
, const char *buffer
, apr_size_t
*len
)
1792 struct special_stream_baton
*btn
= baton
;
1794 return svn_stream_write(btn
->write_stream
, buffer
, len
);
1798 static svn_error_t
*
1799 close_handler_special (void *baton
)
1801 struct special_stream_baton
*btn
= baton
;
1803 if (btn
->write_content
->len
)
1805 /* yeay! we received data and need to create a special file! */
1807 SVN_ERR(create_special_file_from_stringbuf(btn
->write_content
,
1812 return SVN_NO_ERROR
;
1817 svn_subst_stream_from_specialfile(svn_stream_t
**stream
,
1821 struct special_stream_baton
*baton
= apr_palloc(pool
, sizeof(*baton
));
1825 baton
->path
= apr_pstrdup(pool
, path
);
1827 err
= detranslate_special_file_to_stream(&baton
->read_stream
, path
, pool
);
1829 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
1831 svn_error_clear(err
);
1832 baton
->read_stream
= NULL
;
1835 baton
->write_content
= svn_stringbuf_create("", pool
);
1836 baton
->write_stream
= svn_stream_from_stringbuf(baton
->write_content
, pool
);
1838 *stream
= svn_stream_create(baton
, pool
);
1839 svn_stream_set_read(*stream
, read_handler_special
);
1840 svn_stream_set_write(*stream
, write_handler_special
);
1841 svn_stream_set_close(*stream
, close_handler_special
);
1843 return SVN_NO_ERROR
;
1848 /*** String translation */
1850 svn_subst_translate_string(svn_string_t
**new_value
,
1851 const svn_string_t
*value
,
1852 const char *encoding
,
1855 const char *val_utf8
;
1856 const char *val_utf8_lf
;
1861 return SVN_NO_ERROR
;
1866 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8
, value
->data
,
1871 SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8
, value
->data
, pool
));
1874 SVN_ERR(svn_subst_translate_cstring2(val_utf8
,
1876 "\n", /* translate to LF */
1877 FALSE
, /* no repair */
1878 NULL
, /* no keywords */
1879 FALSE
, /* no expansion */
1882 *new_value
= svn_string_create(val_utf8_lf
, pool
);
1884 return SVN_NO_ERROR
;
1889 svn_subst_detranslate_string(svn_string_t
**new_value
,
1890 const svn_string_t
*value
,
1891 svn_boolean_t for_output
,
1895 const char *val_neol
;
1896 const char *val_nlocale_neol
;
1901 return SVN_NO_ERROR
;
1904 SVN_ERR(svn_subst_translate_cstring2(value
->data
,
1906 APR_EOL_STR
, /* 'native' eol */
1907 FALSE
, /* no repair */
1908 NULL
, /* no keywords */
1909 FALSE
, /* no expansion */
1914 err
= svn_cmdline_cstring_from_utf8(&val_nlocale_neol
, val_neol
, pool
);
1915 if (err
&& (APR_STATUS_IS_EINVAL(err
->apr_err
)))
1918 svn_cmdline_cstring_from_utf8_fuzzy(val_neol
, pool
);
1919 svn_error_clear(err
);
1926 err
= svn_utf_cstring_from_utf8(&val_nlocale_neol
, val_neol
, pool
);
1927 if (err
&& (APR_STATUS_IS_EINVAL(err
->apr_err
)))
1929 val_nlocale_neol
= svn_utf_cstring_from_utf8_fuzzy(val_neol
, pool
);
1930 svn_error_clear(err
);
1936 *new_value
= svn_string_create(val_nlocale_neol
, pool
);
1938 return SVN_NO_ERROR
;