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