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_general.h> /* for strcasecmp() */
27 #include <apr_pools.h>
28 #include <apr_tables.h>
29 #include <apr_file_io.h>
30 #include <apr_strings.h>
32 #include "svn_cmdline.h"
33 #include "svn_types.h"
34 #include "svn_string.h"
37 #include "svn_error.h"
40 #include "svn_subst.h"
41 #include "svn_pools.h"
43 #include "svn_private_config.h"
45 /* The Repository Default EOL used for files which
46 * use the 'native' eol style.
48 #define SVN_SUBST__DEFAULT_EOL_STR "\n"
51 * The textual elements of a detranslated special file. One of these
52 * strings must appear as the first element of any special file as it
53 * exists in the repository or the text base.
55 #define SVN_SUBST__SPECIAL_LINK_STR "link"
58 svn_subst_eol_style_from_value(svn_subst_eol_style_t
*style
,
64 /* property doesn't exist. */
67 *style
= svn_subst_eol_style_none
;
69 else if (! strcmp("native", value
))
71 *eol
= APR_EOL_STR
; /* whee, a portability library! */
73 *style
= svn_subst_eol_style_native
;
75 else if (! strcmp("LF", value
))
79 *style
= svn_subst_eol_style_fixed
;
81 else if (! strcmp("CR", value
))
85 *style
= svn_subst_eol_style_fixed
;
87 else if (! strcmp("CRLF", value
))
91 *style
= svn_subst_eol_style_fixed
;
97 *style
= svn_subst_eol_style_unknown
;
103 svn_subst_translation_required(svn_subst_eol_style_t style
,
105 apr_hash_t
*keywords
,
106 svn_boolean_t special
,
107 svn_boolean_t force_eol_check
)
109 return (special
|| keywords
110 || (style
!= svn_subst_eol_style_none
&& force_eol_check
)
111 || (style
== svn_subst_eol_style_native
&&
112 strcmp(APR_EOL_STR
, SVN_SUBST__DEFAULT_EOL_STR
) != 0)
113 || (style
== svn_subst_eol_style_fixed
&&
114 strcmp(APR_EOL_STR
, eol
) != 0));
119 svn_subst_translate_to_normal_form(const char *src
,
121 svn_subst_eol_style_t eol_style
,
123 svn_boolean_t always_repair_eols
,
124 apr_hash_t
*keywords
,
125 svn_boolean_t special
,
129 if (eol_style
== svn_subst_eol_style_native
)
130 eol_str
= SVN_SUBST__DEFAULT_EOL_STR
;
131 else if (! (eol_style
== svn_subst_eol_style_fixed
132 || eol_style
== svn_subst_eol_style_none
))
133 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL
, NULL
, NULL
);
135 return svn_subst_copy_and_translate3(src
, dst
, eol_str
,
136 eol_style
== svn_subst_eol_style_fixed
137 || always_repair_eols
,
139 FALSE
/* contract keywords */,
145 svn_subst_stream_translated_to_normal_form(svn_stream_t
**stream
,
146 svn_stream_t
*source
,
147 svn_subst_eol_style_t eol_style
,
149 svn_boolean_t always_repair_eols
,
150 apr_hash_t
*keywords
,
153 if (eol_style
== svn_subst_eol_style_native
)
154 eol_str
= SVN_SUBST__DEFAULT_EOL_STR
;
155 else if (! (eol_style
== svn_subst_eol_style_fixed
156 || eol_style
== svn_subst_eol_style_none
))
157 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL
, NULL
, NULL
);
159 *stream
= svn_subst_stream_translated(source
, eol_str
,
160 eol_style
== svn_subst_eol_style_fixed
161 || always_repair_eols
,
162 keywords
, FALSE
, pool
);
168 /* Helper function for svn_subst_build_keywords */
170 /* Given a printf-like format string, return a string with proper
171 * information filled in.
173 * Important API note: This function is the core of the implementation of
174 * svn_subst_build_keywords (all versions), and as such must implement the
175 * tolerance of NULL and zero inputs that that function's documention
180 * %a author of this revision
181 * %b basename of the URL of this file
182 * %d short format of date of this revision
183 * %D long format of date of this revision
184 * %r number of this revision
185 * %u URL of this file
188 * All memory is allocated out of @a pool.
190 static svn_string_t
*
191 keyword_printf(const char *fmt
,
198 svn_stringbuf_t
*value
= svn_stringbuf_ncreate("", 0, pool
);
206 while (*cur
!= '\0' && *cur
!= '%')
209 if ((n
= cur
- fmt
) > 0) /* Do we have an as-is string? */
210 svn_stringbuf_appendbytes(value
, fmt
, n
);
217 case 'a': /* author of this revision */
219 svn_stringbuf_appendcstr(value
, author
);
221 case 'b': /* basename of this file */
224 const char *base_name
225 = svn_path_uri_decode(svn_path_basename(url
, pool
), pool
);
226 svn_stringbuf_appendcstr(value
, base_name
);
229 case 'd': /* short format of date of this revision */
232 apr_time_exp_t exploded_time
;
235 apr_time_exp_gmt(&exploded_time
, date
);
237 human
= apr_psprintf(pool
, "%04d-%02d-%02d %02d:%02d:%02dZ",
238 exploded_time
.tm_year
+ 1900,
239 exploded_time
.tm_mon
+ 1,
240 exploded_time
.tm_mday
,
241 exploded_time
.tm_hour
,
242 exploded_time
.tm_min
,
243 exploded_time
.tm_sec
);
245 svn_stringbuf_appendcstr(value
, human
);
248 case 'D': /* long format of date of this revision */
250 svn_stringbuf_appendcstr(value
,
251 svn_time_to_human_cstring(date
, pool
));
253 case 'r': /* number of this revision */
255 svn_stringbuf_appendcstr(value
, rev
);
257 case 'u': /* URL of this file */
259 svn_stringbuf_appendcstr(value
, url
);
261 case '%': /* '%%' => a literal % */
262 svn_stringbuf_appendbytes(value
, cur
, 1);
264 case '\0': /* '%' as the last character of the string. */
265 svn_stringbuf_appendbytes(value
, cur
, 1);
266 /* Now go back one character, since this was just a one character
267 * sequence, whereas all others are two characters, and we do not
268 * want to skip the null terminator entirely and carry on
269 * formatting random memory contents. */
272 default: /* Unrecognized code, just print it literally. */
273 svn_stringbuf_appendbytes(value
, cur
, 2);
277 /* Format code is processed - skip it, and get ready for next chunk. */
281 return svn_string_create_from_buf(value
, pool
);
284 /* Convert an old-style svn_subst_keywords_t struct * into a new-style
285 * keywords hash. Keyword values are shallow copies, so the produced
286 * hash must not be assumed to have lifetime longer than the struct it
287 * is based on. A NULL input causes a NULL output. */
289 kwstruct_to_kwhash(const svn_subst_keywords_t
*kwstruct
,
294 if (kwstruct
== NULL
)
297 kwhash
= apr_hash_make(pool
);
299 if (kwstruct
->revision
)
301 apr_hash_set(kwhash
, SVN_KEYWORD_REVISION_LONG
,
302 APR_HASH_KEY_STRING
, kwstruct
->revision
);
303 apr_hash_set(kwhash
, SVN_KEYWORD_REVISION_MEDIUM
,
304 APR_HASH_KEY_STRING
, kwstruct
->revision
);
305 apr_hash_set(kwhash
, SVN_KEYWORD_REVISION_SHORT
,
306 APR_HASH_KEY_STRING
, kwstruct
->revision
);
310 apr_hash_set(kwhash
, SVN_KEYWORD_DATE_LONG
,
311 APR_HASH_KEY_STRING
, kwstruct
->date
);
312 apr_hash_set(kwhash
, SVN_KEYWORD_DATE_SHORT
,
313 APR_HASH_KEY_STRING
, kwstruct
->date
);
315 if (kwstruct
->author
)
317 apr_hash_set(kwhash
, SVN_KEYWORD_AUTHOR_LONG
,
318 APR_HASH_KEY_STRING
, kwstruct
->author
);
319 apr_hash_set(kwhash
, SVN_KEYWORD_AUTHOR_SHORT
,
320 APR_HASH_KEY_STRING
, kwstruct
->author
);
324 apr_hash_set(kwhash
, SVN_KEYWORD_URL_LONG
,
325 APR_HASH_KEY_STRING
, kwstruct
->url
);
326 apr_hash_set(kwhash
, SVN_KEYWORD_URL_SHORT
,
327 APR_HASH_KEY_STRING
, kwstruct
->url
);
331 apr_hash_set(kwhash
, SVN_KEYWORD_ID
,
332 APR_HASH_KEY_STRING
, kwstruct
->id
);
339 svn_subst_build_keywords(svn_subst_keywords_t
*kw
,
340 const char *keywords_val
,
348 const svn_string_t
*val
;
350 SVN_ERR(svn_subst_build_keywords2(&kwhash
, keywords_val
, rev
,
351 url
, date
, author
, pool
));
353 /* The behaviour of pre-1.3 svn_subst_build_keywords, which we are
354 * replicating here, is to write to a slot in the svn_subst_keywords_t
355 * only if the relevant keyword was present in keywords_val, otherwise
356 * leaving that slot untouched. */
358 val
= apr_hash_get(kwhash
, SVN_KEYWORD_REVISION_LONG
, APR_HASH_KEY_STRING
);
362 val
= apr_hash_get(kwhash
, SVN_KEYWORD_DATE_LONG
, APR_HASH_KEY_STRING
);
366 val
= apr_hash_get(kwhash
, SVN_KEYWORD_AUTHOR_LONG
, APR_HASH_KEY_STRING
);
370 val
= apr_hash_get(kwhash
, SVN_KEYWORD_URL_LONG
, APR_HASH_KEY_STRING
);
374 val
= apr_hash_get(kwhash
, SVN_KEYWORD_ID
, APR_HASH_KEY_STRING
);
382 svn_subst_build_keywords2(apr_hash_t
**kw
,
383 const char *keywords_val
,
390 apr_array_header_t
*keyword_tokens
;
392 *kw
= apr_hash_make(pool
);
394 keyword_tokens
= svn_cstring_split(keywords_val
, " \t\v\n\b\r\f",
395 TRUE
/* chop */, pool
);
397 for (i
= 0; i
< keyword_tokens
->nelts
; ++i
)
399 const char *keyword
= APR_ARRAY_IDX(keyword_tokens
, i
, const char *);
401 if ((! strcmp(keyword
, SVN_KEYWORD_REVISION_LONG
))
402 || (! strcmp(keyword
, SVN_KEYWORD_REVISION_MEDIUM
))
403 || (! strcasecmp(keyword
, SVN_KEYWORD_REVISION_SHORT
)))
405 svn_string_t
*revision_val
;
407 revision_val
= keyword_printf("%r", rev
, url
, date
, author
, pool
);
408 apr_hash_set(*kw
, SVN_KEYWORD_REVISION_LONG
,
409 APR_HASH_KEY_STRING
, revision_val
);
410 apr_hash_set(*kw
, SVN_KEYWORD_REVISION_MEDIUM
,
411 APR_HASH_KEY_STRING
, revision_val
);
412 apr_hash_set(*kw
, SVN_KEYWORD_REVISION_SHORT
,
413 APR_HASH_KEY_STRING
, revision_val
);
415 else if ((! strcmp(keyword
, SVN_KEYWORD_DATE_LONG
))
416 || (! strcasecmp(keyword
, SVN_KEYWORD_DATE_SHORT
)))
418 svn_string_t
*date_val
;
420 date_val
= keyword_printf("%D", rev
, url
, date
, author
, pool
);
421 apr_hash_set(*kw
, SVN_KEYWORD_DATE_LONG
,
422 APR_HASH_KEY_STRING
, date_val
);
423 apr_hash_set(*kw
, SVN_KEYWORD_DATE_SHORT
,
424 APR_HASH_KEY_STRING
, date_val
);
426 else if ((! strcmp(keyword
, SVN_KEYWORD_AUTHOR_LONG
))
427 || (! strcasecmp(keyword
, SVN_KEYWORD_AUTHOR_SHORT
)))
429 svn_string_t
*author_val
;
431 author_val
= keyword_printf("%a", rev
, url
, date
, author
, pool
);
432 apr_hash_set(*kw
, SVN_KEYWORD_AUTHOR_LONG
,
433 APR_HASH_KEY_STRING
, author_val
);
434 apr_hash_set(*kw
, SVN_KEYWORD_AUTHOR_SHORT
,
435 APR_HASH_KEY_STRING
, author_val
);
437 else if ((! strcmp(keyword
, SVN_KEYWORD_URL_LONG
))
438 || (! strcasecmp(keyword
, SVN_KEYWORD_URL_SHORT
)))
440 svn_string_t
*url_val
;
442 url_val
= keyword_printf("%u", rev
, url
, date
, author
, pool
);
443 apr_hash_set(*kw
, SVN_KEYWORD_URL_LONG
,
444 APR_HASH_KEY_STRING
, url_val
);
445 apr_hash_set(*kw
, SVN_KEYWORD_URL_SHORT
,
446 APR_HASH_KEY_STRING
, url_val
);
448 else if ((! strcasecmp(keyword
, SVN_KEYWORD_ID
)))
450 svn_string_t
*id_val
;
452 id_val
= keyword_printf("%b %r %d %a", rev
, url
, date
, author
,
454 apr_hash_set(*kw
, SVN_KEYWORD_ID
,
455 APR_HASH_KEY_STRING
, id_val
);
463 /*** Helpers for svn_subst_translate_stream2 ***/
466 /* Write out LEN bytes of BUF into STREAM. */
468 translate_write(svn_stream_t
*stream
,
472 apr_size_t wrote
= len
;
473 svn_error_t
*write_err
= svn_stream_write(stream
, buf
, &wrote
);
474 if ((write_err
) || (len
!= wrote
))
481 /* Perform the substition of VALUE into keyword string BUF (with len
482 *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
483 *LEN to the new size of the substituted result. Return TRUE if all
484 goes well, FALSE otherwise. If VALUE is NULL, keyword will be
485 contracted, else it will be expanded. */
487 translate_keyword_subst(char *buf
,
490 apr_size_t keyword_len
,
491 const svn_string_t
*value
)
495 /* Make sure we gotz good stuffs. */
496 assert(*len
<= SVN_KEYWORD_MAX_LEN
);
497 assert((buf
[0] == '$') && (buf
[*len
- 1] == '$'));
499 /* Need at least a keyword and two $'s. */
500 if (*len
< keyword_len
+ 2)
503 /* The keyword needs to match what we're looking for. */
504 if (strncmp(buf
+ 1, keyword
, keyword_len
))
507 buf_ptr
= buf
+ 1 + keyword_len
;
509 /* Check for fixed-length expansion.
510 * The format of fixed length keyword and its data is
511 * Unexpanded keyword: "$keyword:: $"
512 * Expanded keyword: "$keyword:: value $"
513 * Expanded kw with filling: "$keyword:: value $"
514 * Truncated keyword: "$keyword:: longval#$"
516 if ((buf_ptr
[0] == ':') /* first char after keyword is ':' */
517 && (buf_ptr
[1] == ':') /* second char after keyword is ':' */
518 && (buf_ptr
[2] == ' ') /* third char after keyword is ' ' */
519 && ((buf
[*len
- 2] == ' ') /* has ' ' for next to last character */
520 || (buf
[*len
- 2] == '#')) /* .. or has '#' for next to last
522 && ((6 + keyword_len
) < *len
)) /* holds "$kw:: x $" at least */
524 /* This is fixed length keyword, so *len remains unchanged */
525 apr_size_t max_value_len
= *len
- (6 + keyword_len
);
529 /* no value, so unexpand */
531 while (*buf_ptr
!= '$')
536 if (value
->len
<= max_value_len
)
537 { /* replacement not as long as template, pad with spaces */
538 strncpy(buf_ptr
+ 3, value
->data
, value
->len
);
539 buf_ptr
+= 3 + value
->len
;
540 while (*buf_ptr
!= '$')
545 /* replacement needs truncating */
546 strncpy(buf_ptr
+ 3, value
->data
, max_value_len
);
554 /* Check for unexpanded keyword. */
555 else if (buf_ptr
[0] == '$') /* "$keyword$" */
565 apr_size_t vallen
= value
->len
;
567 /* "$keyword: value $" */
568 if (vallen
> (SVN_KEYWORD_MAX_LEN
- 5 - keyword_len
))
569 vallen
= SVN_KEYWORD_MAX_LEN
- 5 - keyword_len
;
570 strncpy(buf_ptr
+ 2, value
->data
, vallen
);
571 buf_ptr
[2 + vallen
] = ' ';
572 buf_ptr
[2 + vallen
+ 1] = '$';
573 *len
= 5 + keyword_len
+ vallen
;
579 *len
= 4 + keyword_len
;
584 /* ...but do nothing. */
589 /* Check for expanded keyword. */
590 else if (((*len
>= 4 + keyword_len
) /* holds at least "$keyword: $" */
591 && (buf_ptr
[0] == ':') /* first char after keyword is ':' */
592 && (buf_ptr
[1] == ' ') /* second char after keyword is ' ' */
593 && (buf
[*len
- 2] == ' '))
594 || ((*len
>= 3 + keyword_len
) /* holds at least "$keyword:$" */
595 && (buf_ptr
[0] == ':') /* first char after keyword is ':' */
596 && (buf_ptr
[1] == '$'))) /* second char after keyword is '$' */
601 /* ...so unexpand. */
603 *len
= 2 + keyword_len
;
607 /* ...so re-expand. */
612 apr_size_t vallen
= value
->len
;
614 /* "$keyword: value $" */
615 if (vallen
> (SVN_KEYWORD_MAX_LEN
- 5))
616 vallen
= SVN_KEYWORD_MAX_LEN
- 5;
617 strncpy(buf_ptr
+ 2, value
->data
, vallen
);
618 buf_ptr
[2 + vallen
] = ' ';
619 buf_ptr
[2 + vallen
+ 1] = '$';
620 *len
= 5 + keyword_len
+ vallen
;
626 *len
= 4 + keyword_len
;
635 /* Parse BUF (whose length is LEN, and which starts and ends with '$'),
636 trying to match one of the keyword names in KEYWORDS. If such a
637 keyword is found, update *KEYWORD_NAME with the keyword name and
640 match_keyword(char *buf
,
643 apr_hash_t
*keywords
)
647 /* Early return for ignored keywords */
651 /* Extract the name of the keyword */
652 for (i
= 0; i
< len
- 2 && buf
[i
+ 1] != ':'; i
++)
653 keyword_name
[i
] = buf
[i
+ 1];
654 keyword_name
[i
] = '\0';
656 return apr_hash_get(keywords
, keyword_name
, APR_HASH_KEY_STRING
) != NULL
;
659 /* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
660 optionally perform the substitution in place, update *LEN with
661 the new length of the translated keyword string, and return TRUE.
662 If this buffer doesn't contain a known keyword pattern, leave BUF
663 and *LEN untouched and return FALSE.
665 See the docstring for svn_subst_copy_and_translate for how the
666 EXPAND and KEYWORDS parameters work.
668 NOTE: It is assumed that BUF has been allocated to be at least
669 SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
670 than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions
671 which would result in a keyword string which is greater than
672 SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
673 that the resultant keyword string is still valid (begins with
674 "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */
676 translate_keyword(char *buf
,
678 const char *keyword_name
,
679 svn_boolean_t expand
,
680 apr_hash_t
*keywords
)
682 const svn_string_t
*value
;
684 /* Make sure we gotz good stuffs. */
685 assert(*len
<= SVN_KEYWORD_MAX_LEN
);
686 assert((buf
[0] == '$') && (buf
[*len
- 1] == '$'));
688 /* Early return for ignored keywords */
692 value
= apr_hash_get(keywords
, keyword_name
, APR_HASH_KEY_STRING
);
696 return translate_keyword_subst(buf
, len
,
697 keyword_name
, strlen(keyword_name
),
698 expand
? value
: NULL
);
705 /* Translate NEWLINE_BUF (length of NEWLINE_LEN) to the newline format
706 specified in EOL_STR (length of EOL_STR_LEN), and write the
707 translated thing to FILE (whose path is DST_PATH).
709 SRC_FORMAT (length *SRC_FORMAT_LEN) is a cache of the first newline
710 found while processing SRC_PATH. If the current newline is not the
711 same style as that of SRC_FORMAT, look to the REPAIR parameter. If
712 REPAIR is TRUE, ignore the inconsistency, else return an
713 SVN_ERR_IO_INCONSISTENT_EOL error. If we are examining the first
714 newline in the file, copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
715 use for later consistency checks. */
717 translate_newline(const char *eol_str
,
718 apr_size_t eol_str_len
,
720 apr_size_t
*src_format_len
,
722 apr_size_t newline_len
,
724 svn_boolean_t repair
)
726 /* If this is the first newline we've seen, cache it
727 future comparisons, else compare it with our cache to
728 check for consistency. */
731 /* Comparing with cache. If we are inconsistent and
732 we are NOT repairing the file, generate an error! */
734 ((*src_format_len
!= newline_len
) ||
735 (strncmp(src_format
, newline_buf
, newline_len
))))
736 return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL
, NULL
, NULL
);
740 /* This is our first line ending, so cache it before
742 strncpy(src_format
, newline_buf
, newline_len
);
743 *src_format_len
= newline_len
;
745 /* Translate the newline */
746 return translate_write(dst
, eol_str
, eol_str_len
);
751 /*** Public interfaces. ***/
754 svn_subst_keywords_differ(const svn_subst_keywords_t
*a
,
755 const svn_subst_keywords_t
*b
,
756 svn_boolean_t compare_values
)
758 if (((a
== NULL
) && (b
== NULL
)) /* no A or B */
759 /* no A, and B has no contents */
761 && (b
->revision
== NULL
)
763 && (b
->author
== NULL
)
765 /* no B, and A has no contents */
766 || ((b
== NULL
) && (a
->revision
== NULL
)
768 && (a
->author
== NULL
)
770 /* neither A nor B has any contents */
771 || ((a
!= NULL
) && (b
!= NULL
)
772 && (b
->revision
== NULL
)
774 && (b
->author
== NULL
)
776 && (a
->revision
== NULL
)
778 && (a
->author
== NULL
)
779 && (a
->url
== NULL
)))
783 else if ((a
== NULL
) || (b
== NULL
))
786 /* Else both A and B have some keywords. */
788 if ((! a
->revision
) != (! b
->revision
))
790 else if ((compare_values
&& (a
->revision
!= NULL
))
791 && (strcmp(a
->revision
->data
, b
->revision
->data
) != 0))
794 if ((! a
->date
) != (! b
->date
))
796 else if ((compare_values
&& (a
->date
!= NULL
))
797 && (strcmp(a
->date
->data
, b
->date
->data
) != 0))
800 if ((! a
->author
) != (! b
->author
))
802 else if ((compare_values
&& (a
->author
!= NULL
))
803 && (strcmp(a
->author
->data
, b
->author
->data
) != 0))
806 if ((! a
->url
) != (! b
->url
))
808 else if ((compare_values
&& (a
->url
!= NULL
))
809 && (strcmp(a
->url
->data
, b
->url
->data
) != 0))
812 /* Else we never found a difference, so they must be the same. */
818 svn_subst_keywords_differ2(apr_hash_t
*a
,
820 svn_boolean_t compare_values
,
823 apr_hash_index_t
*hi
;
824 unsigned int a_count
, b_count
;
826 /* An empty hash is logically equal to a NULL,
827 * as far as this API is concerned. */
828 a_count
= (a
== NULL
) ? 0 : apr_hash_count(a
);
829 b_count
= (b
== NULL
) ? 0 : apr_hash_count(b
);
831 if (a_count
!= b_count
)
837 /* The hashes are both non-NULL, and have the same number of items.
838 * We must check that every item of A is present in B. */
839 for (hi
= apr_hash_first(pool
, a
); hi
; hi
= apr_hash_next(hi
))
844 svn_string_t
*a_val
, *b_val
;
846 apr_hash_this(hi
, &key
, &klen
, &void_a_val
);
848 b_val
= apr_hash_get(b
, key
, klen
);
850 if (!b_val
|| (compare_values
&& !svn_string_compare(a_val
, b_val
)))
858 svn_subst_translate_stream2(svn_stream_t
*s
, /* src stream */
859 svn_stream_t
*d
, /* dst stream */
861 svn_boolean_t repair
,
862 const svn_subst_keywords_t
*keywords
,
863 svn_boolean_t expand
,
866 apr_hash_t
*kh
= kwstruct_to_kwhash(keywords
, pool
);
868 return svn_subst_translate_stream3(s
, d
, eol_str
, repair
, kh
, expand
, pool
);
871 /* Baton for translate_chunk() to store its state in. */
872 struct translation_baton
875 svn_boolean_t repair
;
876 apr_hash_t
*keywords
;
877 svn_boolean_t expand
;
879 /* Characters (excluding the terminating NUL character) which
880 may trigger a translation action, hence are 'interesting' */
881 const char *interesting
;
883 /* Length of the string EOL_STR points to. */
884 apr_size_t eol_str_len
;
886 /* Buffer to cache any newline state between translation chunks */
889 /* Offset (within newline_buf) of the first *unused* character */
890 apr_size_t newline_off
;
892 /* Buffer to cache keyword-parsing state between translation chunks */
893 char keyword_buf
[SVN_KEYWORD_MAX_LEN
];
895 /* Offset (within keyword-buf) to the first *unused* character */
896 apr_size_t keyword_off
;
898 /* EOL style used in the chunk-source */
901 /* Length of the EOL style string found in the chunk-source,
902 or zero if none encountered yet */
903 apr_size_t src_format_len
;
907 /* Allocate a baton for use with translate_chunk() in POOL and
908 * initialize it for the first iteration.
910 * The caller must assure that EOL_STR and KEYWORDS at least
911 * have the same life time as that of POOL.
915 static struct translation_baton
*
916 create_translation_baton(const char *eol_str
,
917 svn_boolean_t repair
,
918 apr_hash_t
*keywords
,
919 svn_boolean_t expand
,
922 struct translation_baton
*b
= apr_palloc(pool
, sizeof(*b
));
924 /* For efficiency, convert an empty set of keywords to NULL. */
925 if (keywords
&& (apr_hash_count(keywords
) == 0))
928 b
->eol_str
= eol_str
;
929 b
->eol_str_len
= eol_str
? strlen(eol_str
) : 0;
931 b
->keywords
= keywords
;
933 b
->interesting
= (eol_str
&& keywords
) ? "$\r\n" : eol_str
? "\r\n" : "$";
936 b
->src_format_len
= 0;
941 /* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
942 * according to the settings and state stored in baton B.
944 * Write output to stream DST.
946 * To finish a series of chunk translations, flush all buffers by calling
947 * this routine with a NULL value for BUF.
949 * Use POOL for temporary allocations.
952 translate_chunk(svn_stream_t
*dst
,
953 struct translation_baton
*b
,
963 /* precalculate some oft-used values */
964 const char *end
= buf
+ buflen
;
965 const char *interesting
= b
->interesting
;
966 apr_size_t next_sign_off
= 0;
968 /* At the beginning of this loop, assume that we might be in an
969 * interesting state, i.e. with data in the newline or keyword
970 * buffer. First try to get to the boring state so we can copy
971 * a run of boring characters; then try to get back to the
972 * interesting state by processing an interesting character,
974 for (p
= buf
; p
< end
;)
976 /* Try to get to the boring state, if necessary. */
980 b
->newline_buf
[b
->newline_off
++] = *p
++;
982 SVN_ERR(translate_newline(b
->eol_str
, b
->eol_str_len
,
984 &b
->src_format_len
, b
->newline_buf
,
985 b
->newline_off
, dst
, b
->repair
));
989 else if (b
->keyword_off
&& *p
== '$')
991 svn_boolean_t keyword_matches
;
992 char keyword_name
[SVN_KEYWORD_MAX_LEN
+ 1];
994 /* If keyword is matched, but not correctly translated, try to
995 * look for the next ending '$'. */
996 b
->keyword_buf
[b
->keyword_off
++] = *p
++;
997 keyword_matches
= match_keyword(b
->keyword_buf
, b
->keyword_off
,
998 keyword_name
, b
->keywords
);
999 if (keyword_matches
== FALSE
)
1001 /* reuse the ending '$' */
1006 if (keyword_matches
== FALSE
||
1007 translate_keyword(b
->keyword_buf
, &b
->keyword_off
,
1008 keyword_name
, b
->expand
, b
->keywords
) ||
1009 b
->keyword_off
>= SVN_KEYWORD_MAX_LEN
)
1011 /* write out non-matching text or translated keyword */
1012 SVN_ERR(translate_write(dst
, b
->keyword_buf
, b
->keyword_off
));
1019 if (next_sign_off
== 0)
1020 next_sign_off
= b
->keyword_off
- 1;
1025 else if (b
->keyword_off
== SVN_KEYWORD_MAX_LEN
- 1
1026 || (b
->keyword_off
&& (*p
== '\r' || *p
== '\n')))
1028 if (next_sign_off
> 0)
1030 /* rolling back, continue with next '$' in keyword_buf */
1031 p
-= (b
->keyword_off
- next_sign_off
);
1032 b
->keyword_off
= next_sign_off
;
1035 /* No closing '$' found; flush the keyword buffer. */
1036 SVN_ERR(translate_write(dst
, b
->keyword_buf
, b
->keyword_off
));
1040 else if (b
->keyword_off
)
1042 b
->keyword_buf
[b
->keyword_off
++] = *p
++;
1046 /* We're in the boring state; look for interest characters. */
1049 /* We wanted memcspn(), but lacking that, the loop below has
1052 Also, skip NUL characters explicitly, since strchr()
1053 considers them part of the string argument,
1054 but we don't consider them interesting
1056 while ((p
+ len
) < end
1057 && (! p
[len
] || ! strchr(interesting
, p
[len
])))
1061 SVN_ERR(translate_write(dst
, p
, len
));
1065 /* Set up state according to the interesting character, if any. */
1071 b
->keyword_buf
[b
->keyword_off
++] = *p
++;
1074 b
->newline_buf
[b
->newline_off
++] = *p
++;
1077 b
->newline_buf
[b
->newline_off
++] = *p
++;
1079 SVN_ERR(translate_newline(b
->eol_str
, b
->eol_str_len
,
1083 b
->newline_off
, dst
, b
->repair
));
1096 SVN_ERR(translate_newline(b
->eol_str
, b
->eol_str_len
,
1097 b
->src_format
, &b
->src_format_len
,
1098 b
->newline_buf
, b
->newline_off
,
1105 SVN_ERR(translate_write(dst
, b
->keyword_buf
, b
->keyword_off
));
1110 return SVN_NO_ERROR
;
1113 /* Baton for use with translated stream callbacks. */
1114 struct translated_stream_baton
1116 /* Stream to take input from (before translation) on read
1117 /write output to (after translation) on write. */
1118 svn_stream_t
*stream
;
1120 /* Input/Output translation batons to make them separate chunk streams. */
1121 struct translation_baton
*in_baton
, *out_baton
;
1123 /* Remembers whether any write operations have taken place;
1124 if so, we need to flush the output chunk stream. */
1125 svn_boolean_t written
;
1127 /* Buffer to hold translated read data. */
1128 svn_stringbuf_t
*readbuf
;
1130 /* Offset of the first non-read character in readbuf. */
1131 apr_size_t readbuf_off
;
1133 /* Buffer to hold read data
1134 between svn_stream_read() and translate_chunk(). */
1137 /* Pool in which (only!) this baton is allocated. */
1140 /* Pool for callback iterations */
1141 apr_pool_t
*iterpool
;
1145 static svn_error_t
*
1146 translated_stream_read(void *baton
,
1150 struct translated_stream_baton
*b
= baton
;
1151 apr_size_t readlen
= SVN__STREAM_CHUNK_SIZE
;
1152 apr_size_t unsatisfied
= *len
;
1154 apr_pool_t
*iterpool
;
1156 iterpool
= b
->iterpool
;
1157 while (readlen
== SVN__STREAM_CHUNK_SIZE
&& unsatisfied
> 0)
1160 apr_size_t buffer_remainder
;
1162 svn_pool_clear(iterpool
);
1163 /* fill read buffer, if necessary */
1164 if (! (b
->readbuf_off
< b
->readbuf
->len
))
1166 svn_stream_t
*buf_stream
;
1168 svn_stringbuf_setempty(b
->readbuf
);
1170 SVN_ERR(svn_stream_read(b
->stream
, b
->buf
, &readlen
));
1171 buf_stream
= svn_stream_from_stringbuf(b
->readbuf
, iterpool
);
1173 SVN_ERR(translate_chunk(buf_stream
, b
->in_baton
, b
->buf
,
1174 readlen
, iterpool
));
1176 if (readlen
!= SVN__STREAM_CHUNK_SIZE
)
1177 SVN_ERR(translate_chunk(buf_stream
, b
->in_baton
, NULL
, 0,
1180 SVN_ERR(svn_stream_close(buf_stream
));
1183 /* Satisfy from the read buffer */
1184 buffer_remainder
= b
->readbuf
->len
- b
->readbuf_off
;
1185 to_copy
= (buffer_remainder
> unsatisfied
)
1186 ? unsatisfied
: buffer_remainder
;
1187 memcpy(buffer
+ off
, b
->readbuf
->data
+ b
->readbuf_off
, to_copy
);
1189 b
->readbuf_off
+= to_copy
;
1190 unsatisfied
-= to_copy
;
1193 *len
-= unsatisfied
;
1195 return SVN_NO_ERROR
;
1198 static svn_error_t
*
1199 translated_stream_write(void *baton
,
1203 struct translated_stream_baton
*b
= baton
;
1204 svn_pool_clear(b
->iterpool
);
1207 SVN_ERR(translate_chunk(b
->stream
, b
->out_baton
, buffer
, *len
,
1210 return SVN_NO_ERROR
;
1213 static svn_error_t
*
1214 translated_stream_close(void *baton
)
1216 struct translated_stream_baton
*b
= baton
;
1219 SVN_ERR(translate_chunk(b
->stream
, b
->out_baton
, NULL
, 0, b
->iterpool
));
1221 SVN_ERR(svn_stream_close(b
->stream
));
1223 svn_pool_destroy(b
->pool
); /* Also destroys the baton itself */
1224 return SVN_NO_ERROR
;
1227 /* Given a special file at SRC, set TRANSLATED_STREAM_P to a stream
1228 with the textual representation of it. Perform all allocations in POOL. */
1229 static svn_error_t
*
1230 detranslated_stream_special(svn_stream_t
**translated_stream_p
,
1237 svn_stringbuf_t
*strbuf
;
1239 /* First determine what type of special file we are
1241 SVN_ERR(svn_io_stat(&finfo
, src
, APR_FINFO_MIN
| APR_FINFO_LINK
, pool
));
1243 switch (finfo
.filetype
) {
1245 /* Nothing special to do here, just create stream from the original
1247 SVN_ERR(svn_io_file_open(&s
, src
, APR_READ
| APR_BUFFERED
,
1248 APR_OS_DEFAULT
, pool
));
1249 *translated_stream_p
= svn_stream_from_aprfile2(s
, FALSE
, pool
);
1253 /* Determine the destination of the link. */
1254 SVN_ERR(svn_io_read_link(&buf
, src
, pool
));
1255 strbuf
= svn_stringbuf_createf(pool
, "link %s", buf
->data
);
1256 *translated_stream_p
= svn_stream_from_stringbuf(strbuf
, pool
);
1263 return SVN_NO_ERROR
;
1267 svn_subst_stream_detranslated(svn_stream_t
**stream_p
,
1269 svn_subst_eol_style_t eol_style
,
1270 const char *eol_str
,
1271 svn_boolean_t always_repair_eols
,
1272 apr_hash_t
*keywords
,
1273 svn_boolean_t special
,
1277 svn_stream_t
*src_stream
;
1280 return detranslated_stream_special(stream_p
, src
, pool
);
1282 if (eol_style
== svn_subst_eol_style_native
)
1283 eol_str
= SVN_SUBST__DEFAULT_EOL_STR
;
1284 else if (! (eol_style
== svn_subst_eol_style_fixed
1285 || eol_style
== svn_subst_eol_style_none
))
1286 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL
, NULL
, NULL
);
1288 SVN_ERR(svn_io_file_open(&file_h
, src
, APR_READ
,
1289 APR_OS_DEFAULT
, pool
));
1291 src_stream
= svn_stream_from_aprfile2(file_h
, FALSE
, pool
);
1293 *stream_p
= svn_subst_stream_translated(
1294 src_stream
, eol_str
,
1295 eol_style
== svn_subst_eol_style_fixed
|| always_repair_eols
,
1296 keywords
, FALSE
, pool
);
1298 return SVN_NO_ERROR
;
1302 svn_subst_stream_translated(svn_stream_t
*stream
,
1303 const char *eol_str
,
1304 svn_boolean_t repair
,
1305 apr_hash_t
*keywords
,
1306 svn_boolean_t expand
,
1309 apr_pool_t
*baton_pool
= svn_pool_create(pool
);
1310 struct translated_stream_baton
*baton
1311 = apr_palloc(baton_pool
, sizeof(*baton
));
1312 svn_stream_t
*s
= svn_stream_create(baton
, baton_pool
);
1314 /* Make sure EOL_STR and KEYWORDS are allocated in POOL, as
1315 required by create_translation_baton() */
1317 eol_str
= apr_pstrdup(baton_pool
, eol_str
);
1320 if (apr_hash_count(keywords
) == 0)
1324 /* deep copy the hash to make sure it's allocated in POOL */
1325 apr_hash_t
*copy
= apr_hash_make(baton_pool
);
1326 apr_hash_index_t
*hi
;
1328 for (hi
= apr_hash_first(pool
, keywords
);
1329 hi
; hi
= apr_hash_next(hi
))
1333 apr_hash_this(hi
, &key
, NULL
, &val
);
1334 apr_hash_set(copy
, apr_pstrdup(baton_pool
, key
),
1335 APR_HASH_KEY_STRING
,
1336 svn_string_dup(val
, baton_pool
));
1343 /* Setup the baton fields */
1344 baton
->stream
= stream
;
1346 = create_translation_baton(eol_str
, repair
, keywords
, expand
, baton_pool
);
1348 = create_translation_baton(eol_str
, repair
, keywords
, expand
, baton_pool
);
1349 baton
->written
= FALSE
;
1350 baton
->readbuf
= svn_stringbuf_create("", baton_pool
);
1351 baton
->readbuf_off
= 0;
1352 baton
->iterpool
= svn_pool_create(baton_pool
);
1353 baton
->pool
= baton_pool
;
1354 baton
->buf
= apr_palloc(baton
->pool
, SVN__STREAM_CHUNK_SIZE
+ 1);
1356 /* Setup the stream methods */
1357 svn_stream_set_read(s
, translated_stream_read
);
1358 svn_stream_set_write(s
, translated_stream_write
);
1359 svn_stream_set_close(s
, translated_stream_close
);
1366 svn_subst_translate_stream3(svn_stream_t
*s
, /* src stream */
1367 svn_stream_t
*d
, /* dst stream */
1368 const char *eol_str
,
1369 svn_boolean_t repair
,
1370 apr_hash_t
*keywords
,
1371 svn_boolean_t expand
,
1374 apr_pool_t
*subpool
= svn_pool_create(pool
);
1375 apr_pool_t
*iterpool
= svn_pool_create(subpool
);
1376 struct translation_baton
*baton
;
1377 apr_size_t readlen
= SVN__STREAM_CHUNK_SIZE
;
1378 char *buf
= apr_palloc(subpool
, SVN__STREAM_CHUNK_SIZE
);
1380 /* The docstring requires that *some* translation be requested. */
1381 assert(eol_str
|| keywords
);
1383 baton
= create_translation_baton(eol_str
, repair
, keywords
, expand
, pool
);
1384 while (readlen
== SVN__STREAM_CHUNK_SIZE
)
1386 svn_pool_clear(iterpool
);
1387 SVN_ERR(svn_stream_read(s
, buf
, &readlen
));
1388 SVN_ERR(translate_chunk(d
, baton
, buf
, readlen
, iterpool
));
1391 SVN_ERR(translate_chunk(d
, baton
, NULL
, 0, iterpool
));
1393 svn_pool_destroy(subpool
); /* also destroys iterpool */
1394 return SVN_NO_ERROR
;
1399 svn_subst_translate_stream(svn_stream_t
*s
, /* src stream */
1400 svn_stream_t
*d
, /* dst stream */
1401 const char *eol_str
,
1402 svn_boolean_t repair
,
1403 const svn_subst_keywords_t
*keywords
,
1404 svn_boolean_t expand
)
1406 apr_pool_t
*pool
= svn_pool_create(NULL
);
1407 svn_error_t
*err
= svn_subst_translate_stream2(s
, d
, eol_str
, repair
,
1408 keywords
, expand
, pool
);
1409 svn_pool_destroy(pool
);
1415 svn_subst_translate_cstring(const char *src
,
1417 const char *eol_str
,
1418 svn_boolean_t repair
,
1419 const svn_subst_keywords_t
*keywords
,
1420 svn_boolean_t expand
,
1423 apr_hash_t
*kh
= kwstruct_to_kwhash(keywords
, pool
);
1425 return svn_subst_translate_cstring2(src
, dst
, eol_str
, repair
,
1430 svn_subst_translate_cstring2(const char *src
,
1432 const char *eol_str
,
1433 svn_boolean_t repair
,
1434 apr_hash_t
*keywords
,
1435 svn_boolean_t expand
,
1438 svn_stringbuf_t
*src_stringbuf
, *dst_stringbuf
;
1439 svn_stream_t
*src_stream
, *dst_stream
;
1442 src_stringbuf
= svn_stringbuf_create(src
, pool
);
1444 /* The easy way out: no translation needed, just copy. */
1445 if (! (eol_str
|| (keywords
&& (apr_hash_count(keywords
) > 0))))
1447 dst_stringbuf
= svn_stringbuf_dup(src_stringbuf
, pool
);
1451 /* Convert our stringbufs into streams. */
1452 src_stream
= svn_stream_from_stringbuf(src_stringbuf
, pool
);
1453 dst_stringbuf
= svn_stringbuf_create("", pool
);
1454 dst_stream
= svn_stream_from_stringbuf(dst_stringbuf
, pool
);
1456 /* Translate src stream into dst stream. */
1457 err
= svn_subst_translate_stream3(src_stream
, dst_stream
,
1458 eol_str
, repair
, keywords
, expand
, pool
);
1461 svn_error_clear(svn_stream_close(src_stream
));
1462 svn_error_clear(svn_stream_close(dst_stream
));
1466 /* clean up nicely. */
1467 SVN_ERR(svn_stream_close(src_stream
));
1468 SVN_ERR(svn_stream_close(dst_stream
));
1471 *dst
= dst_stringbuf
->data
;
1472 return SVN_NO_ERROR
;
1477 svn_subst_copy_and_translate(const char *src
,
1479 const char *eol_str
,
1480 svn_boolean_t repair
,
1481 const svn_subst_keywords_t
*keywords
,
1482 svn_boolean_t expand
,
1485 return svn_subst_copy_and_translate2(src
, dst
, eol_str
, repair
, keywords
,
1486 expand
, FALSE
, pool
);
1490 /* Set SRC_STREAM to a stream from which the internal representation
1491 * for the special file at SRC can be read.
1493 * The stream returned will be allocated in POOL.
1495 static svn_error_t
*
1496 detranslate_special_file_to_stream(svn_stream_t
**src_stream
,
1504 /* First determine what type of special file we are
1506 SVN_ERR(svn_io_stat(&finfo
, src
, APR_FINFO_MIN
| APR_FINFO_LINK
, pool
));
1508 switch (finfo
.filetype
) {
1510 /* Nothing special to do here, just copy the original file's
1512 SVN_ERR(svn_io_file_open(&s
, src
, APR_READ
| APR_BUFFERED
,
1513 APR_OS_DEFAULT
, pool
));
1514 *src_stream
= svn_stream_from_aprfile(s
, pool
);
1518 /* Determine the destination of the link. */
1520 *src_stream
= svn_stream_from_stringbuf(svn_stringbuf_create ("", pool
),
1522 SVN_ERR(svn_io_read_link(&buf
, src
, pool
));
1524 SVN_ERR(svn_stream_printf(*src_stream
, pool
, "link %s",
1531 return SVN_NO_ERROR
;
1534 /* Given a special file at SRC, generate a textual representation of
1535 it in a normal file at DST. Perform all allocations in POOL. */
1536 static svn_error_t
*
1537 detranslate_special_file(const char *src
, const char *dst
, apr_pool_t
*pool
)
1539 const char *dst_tmp
;
1541 svn_stream_t
*src_stream
, *dst_stream
;
1544 /* Open a temporary destination that we will eventually atomically
1545 rename into place. */
1546 SVN_ERR(svn_io_open_unique_file2(&d
, &dst_tmp
, dst
,
1547 ".tmp", svn_io_file_del_none
, pool
));
1549 dst_stream
= svn_stream_from_aprfile2(d
, FALSE
, pool
);
1551 SVN_ERR(detranslate_special_file_to_stream(&src_stream
, src
, pool
));
1552 SVN_ERR(svn_stream_copy(src_stream
, dst_stream
, pool
));
1554 SVN_ERR(svn_stream_close(dst_stream
));
1556 /* Do the atomic rename from our temporary location. */
1557 SVN_ERR(svn_io_file_rename(dst_tmp
, dst
, pool
));
1559 return SVN_NO_ERROR
;
1562 /* Creates a special file DST from the internal representation given
1565 * All temporary allocations will be done in POOL.
1567 static svn_error_t
*
1568 create_special_file_from_stringbuf(svn_stringbuf_t
*src
, const char *dst
,
1571 char *identifier
, *remainder
;
1572 const char *dst_tmp
;
1573 svn_boolean_t create_using_internal_representation
= FALSE
;
1575 /* Separate off the identifier. The first space character delimits
1576 the identifier, after which any remaining characters are specific
1577 to the actual special file type being created. */
1578 identifier
= src
->data
;
1579 for (remainder
= identifier
; *remainder
; remainder
++)
1581 if (*remainder
== ' ')
1588 if (! strncmp(identifier
, SVN_SUBST__SPECIAL_LINK_STR
" ",
1589 strlen(SVN_SUBST__SPECIAL_LINK_STR
" ")))
1591 /* For symlinks, the type specific data is just a filesystem
1592 path that the symlink should reference. */
1593 svn_error_t
*err
= svn_io_create_unique_link(&dst_tmp
, dst
, remainder
,
1596 /* If we had an error, check to see if it was because symlinks are
1597 not supported on the platform. If so, fall back
1598 to using the internal representation. */
1601 if (err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
)
1603 svn_error_clear(err
);
1604 create_using_internal_representation
= TRUE
;
1612 /* Just create a normal file using the internal special file
1613 representation. We don't want a commit of an unknown special
1614 file type to DoS all the clients. */
1615 create_using_internal_representation
= TRUE
;
1618 /* If nothing else worked, write out the internal representation to
1619 a file that can be edited by the user. */
1620 if (create_using_internal_representation
)
1622 apr_file_t
*dst_tmp_file
;
1626 SVN_ERR(svn_io_open_unique_file2(&dst_tmp_file
, &dst_tmp
,
1627 dst
, ".tmp", svn_io_file_del_none
,
1629 SVN_ERR(svn_io_file_write_full(dst_tmp_file
, src
->data
, src
->len
,
1631 SVN_ERR(svn_io_file_close(dst_tmp_file
, pool
));
1634 /* Do the atomic rename from our temporary location. */
1635 return svn_io_file_rename(dst_tmp
, dst
, pool
);
1638 /* Given a file containing a repository representation of a special
1639 file in SRC, create the appropriate special file at location DST.
1640 Perform all allocations in POOL. */
1641 static svn_error_t
*
1642 create_special_file(const char *src
, const char *dst
, apr_pool_t
*pool
)
1644 svn_stringbuf_t
*contents
;
1645 svn_node_kind_t kind
;
1646 svn_boolean_t is_special
;
1647 svn_stream_t
*source
;
1649 /* Check to see if we are being asked to create a special file from
1650 a special file. If so, do a temporary detranslation and work
1652 SVN_ERR(svn_io_check_special_path(src
, &kind
, &is_special
, pool
));
1658 SVN_ERR(detranslate_special_file_to_stream(&source
, src
, pool
));
1659 /* The special file normal form doesn't have line endings,
1660 * so, read all of the file into the stringbuf */
1661 SVN_ERR(svn_stream_readline(source
, &contents
, "\n", &eof
, pool
));
1664 /* Read in the detranslated file. */
1665 SVN_ERR(svn_stringbuf_from_file(&contents
, src
, pool
));
1667 return create_special_file_from_stringbuf(contents
, dst
, pool
);
1672 svn_subst_copy_and_translate2(const char *src
,
1674 const char *eol_str
,
1675 svn_boolean_t repair
,
1676 const svn_subst_keywords_t
*keywords
,
1677 svn_boolean_t expand
,
1678 svn_boolean_t special
,
1681 apr_hash_t
*kh
= kwstruct_to_kwhash(keywords
, pool
);
1683 return svn_subst_copy_and_translate3(src
, dst
, eol_str
,
1684 repair
, kh
, expand
, special
,
1689 svn_subst_copy_and_translate3(const char *src
,
1691 const char *eol_str
,
1692 svn_boolean_t repair
,
1693 apr_hash_t
*keywords
,
1694 svn_boolean_t expand
,
1695 svn_boolean_t special
,
1698 const char *dst_tmp
= NULL
;
1699 svn_stream_t
*src_stream
, *dst_stream
;
1700 apr_file_t
*s
= NULL
, *d
= NULL
; /* init to null important for APR */
1702 svn_node_kind_t kind
;
1703 svn_boolean_t path_special
;
1705 SVN_ERR(svn_io_check_special_path(src
, &kind
, &path_special
, pool
));
1707 /* If this is a 'special' file, we may need to create it or
1709 if (special
|| path_special
)
1712 SVN_ERR(create_special_file(src
, dst
, pool
));
1714 SVN_ERR(detranslate_special_file(src
, dst
, pool
));
1716 return SVN_NO_ERROR
;
1719 /* The easy way out: no translation needed, just copy. */
1720 if (! (eol_str
|| (keywords
&& (apr_hash_count(keywords
) > 0))))
1721 return svn_io_copy_file(src
, dst
, FALSE
, pool
);
1723 /* Open source file. */
1724 SVN_ERR(svn_io_file_open(&s
, src
, APR_READ
| APR_BUFFERED
,
1725 APR_OS_DEFAULT
, pool
));
1727 /* For atomicity, we translate to a tmp file and
1728 then rename the tmp file over the real destination. */
1729 SVN_ERR(svn_io_open_unique_file2(&d
, &dst_tmp
, dst
,
1730 ".tmp", svn_io_file_del_on_pool_cleanup
,
1733 /* Now convert our two open files into streams. */
1734 src_stream
= svn_stream_from_aprfile(s
, pool
);
1735 dst_stream
= svn_stream_from_aprfile(d
, pool
);
1737 /* Translate src stream into dst stream. */
1738 err
= svn_subst_translate_stream3(src_stream
, dst_stream
, eol_str
,
1739 repair
, keywords
, expand
, pool
);
1742 if (err
->apr_err
== SVN_ERR_IO_INCONSISTENT_EOL
)
1743 return svn_error_createf
1744 (SVN_ERR_IO_INCONSISTENT_EOL
, err
,
1745 _("File '%s' has inconsistent newlines"),
1746 svn_path_local_style(src
, pool
));
1751 /* clean up nicely. */
1752 SVN_ERR(svn_stream_close(src_stream
));
1753 SVN_ERR(svn_stream_close(dst_stream
));
1754 SVN_ERR(svn_io_file_close(s
, pool
));
1755 SVN_ERR(svn_io_file_close(d
, pool
));
1757 /* Now that dst_tmp contains the translated data, do the atomic rename. */
1758 SVN_ERR(svn_io_file_rename(dst_tmp
, dst
, pool
));
1760 return SVN_NO_ERROR
;
1764 /*** 'Special file' stream support */
1766 struct special_stream_baton
1768 svn_stream_t
*read_stream
;
1769 svn_stringbuf_t
*write_content
;
1770 svn_stream_t
*write_stream
;
1776 static svn_error_t
*
1777 read_handler_special(void *baton
, char *buffer
, apr_size_t
*len
)
1779 struct special_stream_baton
*btn
= baton
;
1781 if (btn
->read_stream
)
1782 /* We actually found a file to read from */
1783 return svn_stream_read(btn
->read_stream
, buffer
, len
);
1785 return svn_error_createf(APR_ENOENT
, NULL
,
1786 "Can't read special file: File '%s' not found",
1787 svn_path_local_style (btn
->path
, btn
->pool
));
1790 static svn_error_t
*
1791 write_handler_special(void *baton
, const char *buffer
, apr_size_t
*len
)
1793 struct special_stream_baton
*btn
= baton
;
1795 return svn_stream_write(btn
->write_stream
, buffer
, len
);
1799 static svn_error_t
*
1800 close_handler_special (void *baton
)
1802 struct special_stream_baton
*btn
= baton
;
1804 if (btn
->write_content
->len
)
1806 /* yeay! we received data and need to create a special file! */
1808 SVN_ERR(create_special_file_from_stringbuf(btn
->write_content
,
1813 return SVN_NO_ERROR
;
1818 svn_subst_stream_from_specialfile(svn_stream_t
**stream
,
1822 struct special_stream_baton
*baton
= apr_palloc(pool
, sizeof(*baton
));
1826 baton
->path
= apr_pstrdup(pool
, path
);
1828 err
= detranslate_special_file_to_stream(&baton
->read_stream
, path
, pool
);
1830 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
1832 svn_error_clear(err
);
1833 baton
->read_stream
= NULL
;
1836 baton
->write_content
= svn_stringbuf_create("", pool
);
1837 baton
->write_stream
= svn_stream_from_stringbuf(baton
->write_content
, pool
);
1839 *stream
= svn_stream_create(baton
, pool
);
1840 svn_stream_set_read(*stream
, read_handler_special
);
1841 svn_stream_set_write(*stream
, write_handler_special
);
1842 svn_stream_set_close(*stream
, close_handler_special
);
1844 return SVN_NO_ERROR
;
1849 /*** String translation */
1851 svn_subst_translate_string(svn_string_t
**new_value
,
1852 const svn_string_t
*value
,
1853 const char *encoding
,
1856 const char *val_utf8
;
1857 const char *val_utf8_lf
;
1862 return SVN_NO_ERROR
;
1867 SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8
, value
->data
,
1872 SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8
, value
->data
, pool
));
1875 SVN_ERR(svn_subst_translate_cstring2(val_utf8
,
1877 "\n", /* translate to LF */
1878 FALSE
, /* no repair */
1879 NULL
, /* no keywords */
1880 FALSE
, /* no expansion */
1883 *new_value
= svn_string_create(val_utf8_lf
, pool
);
1885 return SVN_NO_ERROR
;
1890 svn_subst_detranslate_string(svn_string_t
**new_value
,
1891 const svn_string_t
*value
,
1892 svn_boolean_t for_output
,
1896 const char *val_neol
;
1897 const char *val_nlocale_neol
;
1902 return SVN_NO_ERROR
;
1905 SVN_ERR(svn_subst_translate_cstring2(value
->data
,
1907 APR_EOL_STR
, /* 'native' eol */
1908 FALSE
, /* no repair */
1909 NULL
, /* no keywords */
1910 FALSE
, /* no expansion */
1915 err
= svn_cmdline_cstring_from_utf8(&val_nlocale_neol
, val_neol
, pool
);
1916 if (err
&& (APR_STATUS_IS_EINVAL(err
->apr_err
)))
1919 svn_cmdline_cstring_from_utf8_fuzzy(val_neol
, pool
);
1920 svn_error_clear(err
);
1927 err
= svn_utf_cstring_from_utf8(&val_nlocale_neol
, val_neol
, pool
);
1928 if (err
&& (APR_STATUS_IS_EINVAL(err
->apr_err
)))
1930 val_nlocale_neol
= svn_utf_cstring_from_utf8_fuzzy(val_neol
, pool
);
1931 svn_error_clear(err
);
1937 *new_value
= svn_string_create(val_nlocale_neol
, pool
);
1939 return SVN_NO_ERROR
;