1/*
2 * translate.c :  wc-specific eol/keyword substitution
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#include <stdlib.h>
27#include <string.h>
28
29#include <apr_pools.h>
30#include <apr_file_io.h>
31#include <apr_strings.h>
32
33#include "svn_types.h"
34#include "svn_string.h"
35#include "svn_dirent_uri.h"
36#include "svn_hash.h"
37#include "svn_path.h"
38#include "svn_error.h"
39#include "svn_subst.h"
40#include "svn_io.h"
41#include "svn_props.h"
42
43#include "wc.h"
44#include "adm_files.h"
45#include "translate.h"
46#include "props.h"
47
48#include "svn_private_config.h"
49#include "private/svn_wc_private.h"
50
51
52
53/* */
54static svn_error_t *
55read_handler_unsupported(void *baton, char *buffer, apr_size_t *len)
56{
57  SVN_ERR_MALFUNCTION();
58}
59
60/* */
61static svn_error_t *
62write_handler_unsupported(void *baton, const char *buffer, apr_size_t *len)
63{
64  SVN_ERR_MALFUNCTION();
65}
66
67svn_error_t *
68svn_wc__internal_translated_stream(svn_stream_t **stream,
69                                   svn_wc__db_t *db,
70                                   const char *local_abspath,
71                                   const char *versioned_abspath,
72                                   apr_uint32_t flags,
73                                   apr_pool_t *result_pool,
74                                   apr_pool_t *scratch_pool)
75{
76  svn_boolean_t special;
77  svn_boolean_t to_nf = flags & SVN_WC_TRANSLATE_TO_NF;
78  svn_subst_eol_style_t style;
79  const char *eol;
80  apr_hash_t *keywords;
81  svn_boolean_t repair_forced = flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR;
82
83  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
84  SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath));
85
86  SVN_ERR(svn_wc__get_translate_info(&style, &eol,
87                                     &keywords,
88                                     &special,
89                                     db, versioned_abspath, NULL, FALSE,
90                                     scratch_pool, scratch_pool));
91
92  if (special)
93    {
94      if (to_nf)
95        return svn_subst_read_specialfile(stream, local_abspath, result_pool,
96                                          scratch_pool);
97
98      return svn_subst_create_specialfile(stream, local_abspath, result_pool,
99                                          scratch_pool);
100    }
101
102  if (to_nf)
103    SVN_ERR(svn_stream_open_readonly(stream, local_abspath, result_pool,
104                                     scratch_pool));
105  else
106    {
107      apr_file_t *file;
108
109      /* We don't want the "open-exclusively" feature of the normal
110         svn_stream_open_writable interface. Do this manually. */
111      SVN_ERR(svn_io_file_open(&file, local_abspath,
112                               APR_CREATE | APR_WRITE | APR_BUFFERED,
113                               APR_OS_DEFAULT, result_pool));
114      *stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
115    }
116
117  if (svn_subst_translation_required(style, eol, keywords, special, TRUE))
118    {
119      if (to_nf)
120        {
121          if (style == svn_subst_eol_style_native)
122            eol = SVN_SUBST_NATIVE_EOL_STR;
123          else if (style == svn_subst_eol_style_fixed)
124            repair_forced = TRUE;
125          else if (style != svn_subst_eol_style_none)
126            return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
127
128          /* Wrap the stream to translate to normal form */
129          *stream = svn_subst_stream_translated(*stream,
130                                                eol,
131                                                repair_forced,
132                                                keywords,
133                                                FALSE /* expand */,
134                                                result_pool);
135
136          /* Enforce our contract. TO_NF streams are readonly */
137          svn_stream_set_write(*stream, write_handler_unsupported);
138        }
139      else
140        {
141          *stream = svn_subst_stream_translated(*stream, eol, TRUE,
142                                                keywords, TRUE, result_pool);
143
144          /* Enforce our contract. FROM_NF streams are write-only */
145          svn_stream_set_read(*stream, read_handler_unsupported);
146        }
147    }
148
149  return SVN_NO_ERROR;
150}
151
152
153svn_error_t *
154svn_wc__internal_translated_file(const char **xlated_abspath,
155                                 const char *src_abspath,
156                                 svn_wc__db_t *db,
157                                 const char *versioned_abspath,
158                                 apr_uint32_t flags,
159                                 svn_cancel_func_t cancel_func,
160                                 void *cancel_baton,
161                                 apr_pool_t *result_pool,
162                                 apr_pool_t *scratch_pool)
163{
164  svn_subst_eol_style_t style;
165  const char *eol;
166  apr_hash_t *keywords;
167  svn_boolean_t special;
168
169  SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
170  SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath));
171  SVN_ERR(svn_wc__get_translate_info(&style, &eol,
172                                     &keywords,
173                                     &special,
174                                     db, versioned_abspath, NULL, FALSE,
175                                     scratch_pool, scratch_pool));
176
177  if (! svn_subst_translation_required(style, eol, keywords, special, TRUE)
178      && (! (flags & SVN_WC_TRANSLATE_FORCE_COPY)))
179    {
180      /* Translation would be a no-op, so return the original file. */
181      *xlated_abspath = src_abspath;
182    }
183  else  /* some translation (or copying) is necessary */
184    {
185      const char *tmp_dir;
186      const char *tmp_vfile;
187      svn_boolean_t repair_forced
188          = (flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR) != 0;
189      svn_boolean_t expand = (flags & SVN_WC_TRANSLATE_TO_NF) == 0;
190
191      if (flags & SVN_WC_TRANSLATE_USE_GLOBAL_TMP)
192        tmp_dir = NULL;
193      else
194        SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmp_dir, db, versioned_abspath,
195                                               scratch_pool, scratch_pool));
196
197      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_vfile, tmp_dir,
198                (flags & SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP)
199                  ? svn_io_file_del_none
200                  : svn_io_file_del_on_pool_cleanup,
201                result_pool, scratch_pool));
202
203      /* ### ugh. the repair behavior does NOT match the docstring. bleah.
204         ### all of these translation functions are crap and should go
205         ### away anyways. we'll just deprecate most of the functions and
206         ### properly document the survivors */
207
208      if (expand)
209        {
210          /* from normal form */
211
212          repair_forced = TRUE;
213        }
214      else
215        {
216          /* to normal form */
217
218          if (style == svn_subst_eol_style_native)
219            eol = SVN_SUBST_NATIVE_EOL_STR;
220          else if (style == svn_subst_eol_style_fixed)
221            repair_forced = TRUE;
222          else if (style != svn_subst_eol_style_none)
223            return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
224        }
225
226      SVN_ERR(svn_subst_copy_and_translate4(src_abspath, tmp_vfile,
227                                            eol, repair_forced,
228                                            keywords,
229                                            expand,
230                                            special,
231                                            cancel_func, cancel_baton,
232                                            result_pool));
233
234      *xlated_abspath = tmp_vfile;
235    }
236
237  return SVN_NO_ERROR;
238}
239
240void
241svn_wc__eol_value_from_string(const char **value, const char *eol)
242{
243  if (eol == NULL)
244    *value = NULL;
245  else if (! strcmp("\n", eol))
246    *value = "LF";
247  else if (! strcmp("\r", eol))
248    *value = "CR";
249  else if (! strcmp("\r\n", eol))
250    *value = "CRLF";
251  else
252    *value = NULL;
253}
254
255svn_error_t *
256svn_wc__get_translate_info(svn_subst_eol_style_t *style,
257                           const char **eol,
258                           apr_hash_t **keywords,
259                           svn_boolean_t *special,
260                           svn_wc__db_t *db,
261                           const char *local_abspath,
262                           apr_hash_t *props,
263                           svn_boolean_t for_normalization,
264                           apr_pool_t *result_pool,
265                           apr_pool_t *scratch_pool)
266{
267  const char *propval;
268  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
269
270  if (props == NULL)
271    SVN_ERR(svn_wc__get_actual_props(&props, db, local_abspath,
272                                     scratch_pool, scratch_pool));
273
274  if (eol)
275    {
276      propval = svn_prop_get_value(props, SVN_PROP_EOL_STYLE);
277
278      svn_subst_eol_style_from_value(style, eol, propval);
279    }
280
281  if (keywords)
282    {
283      propval = svn_prop_get_value(props, SVN_PROP_KEYWORDS);
284
285      if (!propval || *propval == '\0')
286        *keywords = NULL;
287      else
288        SVN_ERR(svn_wc__expand_keywords(keywords,
289                                        db, local_abspath, NULL,
290                                        propval, for_normalization,
291                                        result_pool, scratch_pool));
292    }
293  if (special)
294    {
295      propval = svn_prop_get_value(props, SVN_PROP_SPECIAL);
296
297      *special = (propval != NULL);
298    }
299
300  return SVN_NO_ERROR;
301}
302
303svn_error_t *
304svn_wc__expand_keywords(apr_hash_t **keywords,
305                        svn_wc__db_t *db,
306                        const char *local_abspath,
307                        const char *wri_abspath,
308                        const char *keyword_list,
309                        svn_boolean_t for_normalization,
310                        apr_pool_t *result_pool,
311                        apr_pool_t *scratch_pool)
312{
313  svn_revnum_t changed_rev;
314  apr_time_t changed_date;
315  const char *changed_author;
316  const char *url;
317  const char *repos_root_url;
318
319  if (! for_normalization)
320    {
321      const char *repos_relpath;
322
323      SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, &repos_relpath,
324                                   &repos_root_url, NULL, &changed_rev,
325                                   &changed_date, &changed_author, NULL,
326                                   NULL, NULL, NULL, NULL, NULL, NULL,
327                                   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
328                                   NULL, NULL, NULL, NULL,
329                                   db, local_abspath,
330                                   scratch_pool, scratch_pool));
331
332      if (repos_relpath)
333        url = svn_path_url_add_component2(repos_root_url, repos_relpath,
334                                          scratch_pool);
335      else
336         SVN_ERR(svn_wc__db_read_url(&url, db, local_abspath, scratch_pool,
337                                     scratch_pool));
338    }
339  else
340    {
341      url = "";
342      changed_rev = SVN_INVALID_REVNUM;
343      changed_date = 0;
344      changed_author = "";
345      repos_root_url = "";
346    }
347
348  SVN_ERR(svn_subst_build_keywords3(keywords, keyword_list,
349                                    apr_psprintf(scratch_pool, "%ld",
350                                                 changed_rev),
351                                    url, repos_root_url,
352                                    changed_date, changed_author,
353                                    result_pool));
354
355  if (apr_hash_count(*keywords) == 0)
356    *keywords = NULL;
357
358  return SVN_NO_ERROR;
359}
360
361svn_error_t *
362svn_wc__sync_flags_with_props(svn_boolean_t *did_set,
363                              svn_wc__db_t *db,
364                              const char *local_abspath,
365                              apr_pool_t *scratch_pool)
366{
367  svn_wc__db_status_t status;
368  svn_node_kind_t kind;
369  svn_wc__db_lock_t *lock;
370  apr_hash_t *props = NULL;
371  svn_boolean_t had_props;
372  svn_boolean_t props_mod;
373
374  if (did_set)
375    *did_set = FALSE;
376
377  /* ### We'll consolidate these info gathering statements in a future
378         commit. */
379
380  SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
381                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
382                               NULL, &lock, NULL, NULL, NULL, NULL, NULL,
383                               &had_props, &props_mod, NULL, NULL, NULL,
384                               db, local_abspath,
385                               scratch_pool, scratch_pool));
386
387  /* We actually only care about the following flags on files, so just
388     early-out for all other types.
389
390     Also bail if there is no in-wc representation of the file. */
391  if (kind != svn_node_file
392      || (status != svn_wc__db_status_normal
393          && status != svn_wc__db_status_added))
394    return SVN_NO_ERROR;
395
396  if (props_mod || had_props)
397    SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool,
398                                  scratch_pool));
399  else
400    props = NULL;
401
402  /* If we get this far, we're going to change *something*, so just set
403     the flag appropriately. */
404  if (did_set)
405    *did_set = TRUE;
406
407  /* Handle the read-write bit. */
408  if (status != svn_wc__db_status_normal
409      || props == NULL
410      || ! svn_hash_gets(props, SVN_PROP_NEEDS_LOCK)
411      || lock)
412    {
413      SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool));
414    }
415  else
416    {
417      /* Special case: If we have an uncommitted svn:needs-lock, we don't
418         set the file read_only just yet.  That happens upon commit. */
419      apr_hash_t *pristine_props;
420
421      if (! props_mod)
422        pristine_props = props;
423      else if (had_props)
424        SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
425                                                scratch_pool, scratch_pool));
426      else
427        pristine_props = NULL;
428
429      if (pristine_props
430            && svn_hash_gets(pristine_props, SVN_PROP_NEEDS_LOCK) )
431            /*&& props
432            && apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) )*/
433        SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool));
434    }
435
436/* Windows doesn't care about the execute bit. */
437#ifndef WIN32
438
439  if (props == NULL
440      || ! svn_hash_gets(props, SVN_PROP_EXECUTABLE))
441    {
442      /* Turn off the execute bit */
443      SVN_ERR(svn_io_set_file_executable(local_abspath, FALSE, FALSE,
444                                         scratch_pool));
445    }
446  else
447    SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE,
448                                       scratch_pool));
449#endif
450
451  return SVN_NO_ERROR;
452}
453