cmdline.c revision 262253
1/*
2 * cmdline.c :  Helpers for command-line programs.
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#include <stdlib.h>             /* for atexit() */
26#include <stdio.h>              /* for setvbuf() */
27#include <locale.h>             /* for setlocale() */
28
29#ifndef WIN32
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <fcntl.h>
33#include <unistd.h>
34#else
35#include <crtdbg.h>
36#include <io.h>
37#endif
38
39#include <apr.h>                /* for STDIN_FILENO */
40#include <apr_errno.h>          /* for apr_strerror */
41#include <apr_general.h>        /* for apr_initialize/apr_terminate */
42#include <apr_strings.h>        /* for apr_snprintf */
43#include <apr_pools.h>
44
45#include "svn_cmdline.h"
46#include "svn_ctype.h"
47#include "svn_dso.h"
48#include "svn_dirent_uri.h"
49#include "svn_hash.h"
50#include "svn_path.h"
51#include "svn_pools.h"
52#include "svn_error.h"
53#include "svn_nls.h"
54#include "svn_utf.h"
55#include "svn_auth.h"
56#include "svn_xml.h"
57#include "svn_base64.h"
58#include "svn_config.h"
59#include "svn_sorts.h"
60#include "svn_props.h"
61#include "svn_subst.h"
62
63#include "private/svn_cmdline_private.h"
64#include "private/svn_utf_private.h"
65#include "private/svn_string_private.h"
66
67#include "svn_private_config.h"
68
69#include "win32_crashrpt.h"
70
71/* The stdin encoding. If null, it's the same as the native encoding. */
72static const char *input_encoding = NULL;
73
74/* The stdout encoding. If null, it's the same as the native encoding. */
75static const char *output_encoding = NULL;
76
77
78int
79svn_cmdline_init(const char *progname, FILE *error_stream)
80{
81  apr_status_t status;
82  apr_pool_t *pool;
83  svn_error_t *err;
84  char prefix_buf[64];  /* 64 is probably bigger than most program names */
85
86#ifndef WIN32
87  {
88    struct stat st;
89
90    /* The following makes sure that file descriptors 0 (stdin), 1
91       (stdout) and 2 (stderr) will not be "reused", because if
92       e.g. file descriptor 2 would be reused when opening a file, a
93       write to stderr would write to that file and most likely
94       corrupt it. */
95    if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) ||
96        (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) ||
97        (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1))
98      {
99        if (error_stream)
100          fprintf(error_stream, "%s: error: cannot open '/dev/null'\n",
101                  progname);
102        return EXIT_FAILURE;
103      }
104  }
105#endif
106
107  /* Ignore any errors encountered while attempting to change stream
108     buffering, as the streams should retain their default buffering
109     modes. */
110  if (error_stream)
111    setvbuf(error_stream, NULL, _IONBF, 0);
112#ifndef WIN32
113  setvbuf(stdout, NULL, _IOLBF, 0);
114#endif
115
116#ifdef WIN32
117#if _MSC_VER < 1400
118  /* Initialize the input and output encodings. */
119  {
120    static char input_encoding_buffer[16];
121    static char output_encoding_buffer[16];
122
123    apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer,
124                 "CP%u", (unsigned) GetConsoleCP());
125    input_encoding = input_encoding_buffer;
126
127    apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer,
128                 "CP%u", (unsigned) GetConsoleOutputCP());
129    output_encoding = output_encoding_buffer;
130  }
131#endif /* _MSC_VER < 1400 */
132
133#ifdef SVN_USE_WIN32_CRASHHANDLER
134  /* Attach (but don't load) the crash handler */
135  SetUnhandledExceptionFilter(svn__unhandled_exception_filter);
136
137#if _MSC_VER >= 1400
138  /* ### This should work for VC++ 2002 (=1300) and later */
139  /* Show the abort message on STDERR instead of a dialog to allow
140     scripts (e.g. our testsuite) to continue after an abort without
141     user intervention. Allow overriding for easier debugging. */
142  if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT"))
143    {
144      /* In release mode: Redirect abort() errors to stderr */
145      _set_error_mode(_OUT_TO_STDERR);
146
147      /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr.
148         (Ignored in release builds) */
149      _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR);
150      _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR);
151      _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR);
152      _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
153      _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
154      _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
155    }
156#endif /* _MSC_VER >= 1400 */
157
158#endif /* SVN_USE_WIN32_CRASHHANDLER */
159
160#endif /* WIN32 */
161
162  /* C programs default to the "C" locale. But because svn is supposed
163     to be i18n-aware, it should inherit the default locale of its
164     environment.  */
165  if (!setlocale(LC_ALL, "")
166      && !setlocale(LC_CTYPE, ""))
167    {
168      if (error_stream)
169        {
170          const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL };
171          const char **env_var = &env_vars[0], *env_val = NULL;
172          while (*env_var)
173            {
174              env_val = getenv(*env_var);
175              if (env_val && env_val[0])
176                break;
177              ++env_var;
178            }
179
180          if (!*env_var)
181            {
182              /* Unlikely. Can setlocale fail if no env vars are set? */
183              --env_var;
184              env_val = "not set";
185            }
186
187          fprintf(error_stream,
188                  "%s: warning: cannot set LC_CTYPE locale\n"
189                  "%s: warning: environment variable %s is %s\n"
190                  "%s: warning: please check that your locale name is correct\n",
191                  progname, progname, *env_var, env_val, progname);
192        }
193    }
194
195  /* Initialize the APR subsystem, and register an atexit() function
196     to Uninitialize that subsystem at program exit. */
197  status = apr_initialize();
198  if (status)
199    {
200      if (error_stream)
201        {
202          char buf[1024];
203          apr_strerror(status, buf, sizeof(buf) - 1);
204          fprintf(error_stream,
205                  "%s: error: cannot initialize APR: %s\n",
206                  progname, buf);
207        }
208      return EXIT_FAILURE;
209    }
210
211  strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3);
212  prefix_buf[sizeof(prefix_buf) - 3] = '\0';
213  strcat(prefix_buf, ": ");
214
215  /* DSO pool must be created before any other pools used by the
216     application so that pool cleanup doesn't unload DSOs too
217     early. See docstring of svn_dso_initialize2(). */
218  if ((err = svn_dso_initialize2()))
219    {
220      if (error_stream)
221        svn_handle_error2(err, error_stream, TRUE, prefix_buf);
222
223      svn_error_clear(err);
224      return EXIT_FAILURE;
225    }
226
227  if (0 > atexit(apr_terminate))
228    {
229      if (error_stream)
230        fprintf(error_stream,
231                "%s: error: atexit registration failed\n",
232                progname);
233      return EXIT_FAILURE;
234    }
235
236  /* Create a pool for use by the UTF-8 routines.  It will be cleaned
237     up by APR at exit time. */
238  pool = svn_pool_create(NULL);
239  svn_utf_initialize2(FALSE, pool);
240
241  if ((err = svn_nls_init()))
242    {
243      if (error_stream)
244        svn_handle_error2(err, error_stream, TRUE, prefix_buf);
245
246      svn_error_clear(err);
247      return EXIT_FAILURE;
248    }
249
250  return EXIT_SUCCESS;
251}
252
253
254svn_error_t *
255svn_cmdline_cstring_from_utf8(const char **dest,
256                              const char *src,
257                              apr_pool_t *pool)
258{
259  if (output_encoding == NULL)
260    return svn_utf_cstring_from_utf8(dest, src, pool);
261  else
262    return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool);
263}
264
265
266const char *
267svn_cmdline_cstring_from_utf8_fuzzy(const char *src,
268                                    apr_pool_t *pool)
269{
270  return svn_utf__cstring_from_utf8_fuzzy(src, pool,
271                                          svn_cmdline_cstring_from_utf8);
272}
273
274
275svn_error_t *
276svn_cmdline_cstring_to_utf8(const char **dest,
277                            const char *src,
278                            apr_pool_t *pool)
279{
280  if (input_encoding == NULL)
281    return svn_utf_cstring_to_utf8(dest, src, pool);
282  else
283    return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool);
284}
285
286
287svn_error_t *
288svn_cmdline_path_local_style_from_utf8(const char **dest,
289                                       const char *src,
290                                       apr_pool_t *pool)
291{
292  return svn_cmdline_cstring_from_utf8(dest,
293                                       svn_dirent_local_style(src, pool),
294                                       pool);
295}
296
297svn_error_t *
298svn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...)
299{
300  const char *message;
301  va_list ap;
302
303  /* A note about encoding issues:
304   * APR uses the execution character set, but here we give it UTF-8 strings,
305   * both the fmt argument and any other string arguments.  Since apr_pvsprintf
306   * only cares about and produces ASCII characters, this works under the
307   * assumption that all supported platforms use an execution character set
308   * with ASCII as a subset.
309   */
310
311  va_start(ap, fmt);
312  message = apr_pvsprintf(pool, fmt, ap);
313  va_end(ap);
314
315  return svn_cmdline_fputs(message, stdout, pool);
316}
317
318svn_error_t *
319svn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...)
320{
321  const char *message;
322  va_list ap;
323
324  /* See svn_cmdline_printf () for a note about character encoding issues. */
325
326  va_start(ap, fmt);
327  message = apr_pvsprintf(pool, fmt, ap);
328  va_end(ap);
329
330  return svn_cmdline_fputs(message, stream, pool);
331}
332
333svn_error_t *
334svn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool)
335{
336  svn_error_t *err;
337  const char *out;
338
339  err = svn_cmdline_cstring_from_utf8(&out, string, pool);
340
341  if (err)
342    {
343      svn_error_clear(err);
344      out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
345    }
346
347  /* On POSIX systems, errno will be set on an error in fputs, but this might
348     not be the case on other platforms.  We reset errno and only
349     use it if it was set by the below fputs call.  Else, we just return
350     a generic error. */
351  errno = 0;
352
353  if (fputs(out, stream) == EOF)
354    {
355      if (apr_get_os_error()) /* is errno on POSIX */
356        {
357          /* ### Issue #3014: Return a specific error for broken pipes,
358           * ### with a single element in the error chain. */
359          if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
360            return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
361          else
362            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
363        }
364      else
365        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
366    }
367
368  return SVN_NO_ERROR;
369}
370
371svn_error_t *
372svn_cmdline_fflush(FILE *stream)
373{
374  /* See comment in svn_cmdline_fputs about use of errno and stdio. */
375  errno = 0;
376  if (fflush(stream) == EOF)
377    {
378      if (apr_get_os_error()) /* is errno on POSIX */
379        {
380          /* ### Issue #3014: Return a specific error for broken pipes,
381           * ### with a single element in the error chain. */
382          if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
383            return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
384          else
385            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
386        }
387      else
388        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
389    }
390
391  return SVN_NO_ERROR;
392}
393
394const char *svn_cmdline_output_encoding(apr_pool_t *pool)
395{
396  if (output_encoding)
397    return apr_pstrdup(pool, output_encoding);
398  else
399    return SVN_APR_LOCALE_CHARSET;
400}
401
402int
403svn_cmdline_handle_exit_error(svn_error_t *err,
404                              apr_pool_t *pool,
405                              const char *prefix)
406{
407  /* Issue #3014:
408   * Don't print anything on broken pipes. The pipe was likely
409   * closed by the process at the other end. We expect that
410   * process to perform error reporting as necessary.
411   *
412   * ### This assumes that there is only one error in a chain for
413   * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
414  if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
415    svn_handle_error2(err, stderr, FALSE, prefix);
416  svn_error_clear(err);
417  if (pool)
418    svn_pool_destroy(pool);
419  return EXIT_FAILURE;
420}
421
422/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'.
423
424   Don't actually prompt.  Instead, set *CRED_P to valid credentials
425   iff FAILURES is empty or is exactly SVN_AUTH_SSL_UNKNOWNCA.  If
426   there are any other failure bits, then set *CRED_P to null (that
427   is, reject the cert).
428
429   Ignore MAY_SAVE; we don't save certs we never prompted for.
430
431   Ignore BATON, REALM, and CERT_INFO,
432
433   Ignore any further films by George Lucas. */
434static svn_error_t *
435ssl_trust_unknown_server_cert
436  (svn_auth_cred_ssl_server_trust_t **cred_p,
437   void *baton,
438   const char *realm,
439   apr_uint32_t failures,
440   const svn_auth_ssl_server_cert_info_t *cert_info,
441   svn_boolean_t may_save,
442   apr_pool_t *pool)
443{
444  *cred_p = NULL;
445
446  if (failures == 0 || failures == SVN_AUTH_SSL_UNKNOWNCA)
447    {
448      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
449      (*cred_p)->may_save = FALSE;
450      (*cred_p)->accepted_failures = failures;
451    }
452
453  return SVN_NO_ERROR;
454}
455
456svn_error_t *
457svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
458                              svn_boolean_t non_interactive,
459                              const char *auth_username,
460                              const char *auth_password,
461                              const char *config_dir,
462                              svn_boolean_t no_auth_cache,
463                              svn_boolean_t trust_server_cert,
464                              svn_config_t *cfg,
465                              svn_cancel_func_t cancel_func,
466                              void *cancel_baton,
467                              apr_pool_t *pool)
468{
469  svn_boolean_t store_password_val = TRUE;
470  svn_boolean_t store_auth_creds_val = TRUE;
471  svn_auth_provider_object_t *provider;
472  svn_cmdline_prompt_baton2_t *pb = NULL;
473
474  /* The whole list of registered providers */
475  apr_array_header_t *providers;
476
477  /* Populate the registered providers with the platform-specific providers */
478  SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers,
479                                                          cfg, pool));
480
481  /* If we have a cancellation function, cram it and the stuff it
482     needs into the prompt baton. */
483  if (cancel_func)
484    {
485      pb = apr_palloc(pool, sizeof(*pb));
486      pb->cancel_func = cancel_func;
487      pb->cancel_baton = cancel_baton;
488      pb->config_dir = config_dir;
489    }
490
491  if (!non_interactive)
492    {
493      /* This provider doesn't prompt the user in order to get creds;
494         it prompts the user regarding the caching of creds. */
495      svn_auth_get_simple_provider2(&provider,
496                                    svn_cmdline_auth_plaintext_prompt,
497                                    pb, pool);
498    }
499  else
500    {
501      svn_auth_get_simple_provider2(&provider, NULL, NULL, pool);
502    }
503
504  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
505  svn_auth_get_username_provider(&provider, pool);
506  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
507
508  /* The windows ssl server certificate CRYPTOAPI provider. */
509  SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
510                                                  "windows",
511                                                  "ssl_server_trust",
512                                                  pool));
513
514  if (provider)
515    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
516
517  /* The windows ssl authority certificate CRYPTOAPI provider. */
518  SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
519                                                  "windows",
520                                                  "ssl_server_authority",
521                                                  pool));
522
523  if (provider)
524    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
525
526  svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
527  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
528  svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
529  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
530
531  if (!non_interactive)
532    {
533      /* This provider doesn't prompt the user in order to get creds;
534         it prompts the user regarding the caching of creds. */
535      svn_auth_get_ssl_client_cert_pw_file_provider2
536        (&provider, svn_cmdline_auth_plaintext_passphrase_prompt,
537         pb, pool);
538    }
539  else
540    {
541      svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL,
542                                                     pool);
543    }
544  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
545
546  if (!non_interactive)
547    {
548      svn_boolean_t ssl_client_cert_file_prompt;
549
550      SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt,
551                                  SVN_CONFIG_SECTION_AUTH,
552                                  SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT,
553                                  FALSE));
554
555      /* Two basic prompt providers: username/password, and just username. */
556      svn_auth_get_simple_prompt_provider(&provider,
557                                          svn_cmdline_auth_simple_prompt,
558                                          pb,
559                                          2, /* retry limit */
560                                          pool);
561      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
562
563      svn_auth_get_username_prompt_provider
564        (&provider, svn_cmdline_auth_username_prompt, pb,
565         2, /* retry limit */ pool);
566      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
567
568      /* SSL prompt providers: server-certs and client-cert-passphrases.  */
569      svn_auth_get_ssl_server_trust_prompt_provider
570        (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool);
571      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
572
573      svn_auth_get_ssl_client_cert_pw_prompt_provider
574        (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool);
575      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
576
577      /* If configuration allows, add a provider for client-cert path
578         prompting, too. */
579      if (ssl_client_cert_file_prompt)
580        {
581          svn_auth_get_ssl_client_cert_prompt_provider
582            (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
583          APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
584        }
585    }
586  else if (trust_server_cert)
587    {
588      /* Remember, only register this provider if non_interactive. */
589      svn_auth_get_ssl_server_trust_prompt_provider
590        (&provider, ssl_trust_unknown_server_cert, NULL, pool);
591      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
592    }
593
594  /* Build an authentication baton to give to libsvn_client. */
595  svn_auth_open(ab, providers, pool);
596
597  /* Place any default --username or --password credentials into the
598     auth_baton's run-time parameter hash. */
599  if (auth_username)
600    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
601                           auth_username);
602  if (auth_password)
603    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
604                           auth_password);
605
606  /* Same with the --non-interactive option. */
607  if (non_interactive)
608    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, "");
609
610  if (config_dir)
611    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR,
612                           config_dir);
613
614  /* Determine whether storing passwords in any form is allowed.
615   * This is the deprecated location for this option, the new
616   * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
617   * override the value we set here. */
618  SVN_ERR(svn_config_get_bool(cfg, &store_password_val,
619                              SVN_CONFIG_SECTION_AUTH,
620                              SVN_CONFIG_OPTION_STORE_PASSWORDS,
621                              SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS));
622
623  if (! store_password_val)
624    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, "");
625
626  /* Determine whether we are allowed to write to the auth/ area.
627   * This is the deprecated location for this option, the new
628   * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
629   * override the value we set here. */
630  SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val,
631                              SVN_CONFIG_SECTION_AUTH,
632                              SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
633                              SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS));
634
635  if (no_auth_cache || ! store_auth_creds_val)
636    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
637
638#ifdef SVN_HAVE_GNOME_KEYRING
639  svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC,
640                         &svn_cmdline__auth_gnome_keyring_unlock_prompt);
641#endif /* SVN_HAVE_GNOME_KEYRING */
642
643  return SVN_NO_ERROR;
644}
645
646svn_error_t *
647svn_cmdline__getopt_init(apr_getopt_t **os,
648                         int argc,
649                         const char *argv[],
650                         apr_pool_t *pool)
651{
652  apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv);
653  if (apr_err)
654    return svn_error_wrap_apr(apr_err,
655                              _("Error initializing command line arguments"));
656  return SVN_NO_ERROR;
657}
658
659
660void
661svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
662                            const char* propname,
663                            svn_string_t *propval,
664                            svn_boolean_t inherited_prop,
665                            apr_pool_t *pool)
666{
667  const char *xml_safe;
668  const char *encoding = NULL;
669
670  if (*outstr == NULL)
671    *outstr = svn_stringbuf_create_empty(pool);
672
673  if (svn_xml_is_xml_safe(propval->data, propval->len))
674    {
675      svn_stringbuf_t *xml_esc = NULL;
676      svn_xml_escape_cdata_string(&xml_esc, propval, pool);
677      xml_safe = xml_esc->data;
678    }
679  else
680    {
681      const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE,
682                                                               pool);
683      encoding = "base64";
684      xml_safe = base64ed->data;
685    }
686
687  if (encoding)
688    svn_xml_make_open_tag(
689      outstr, pool, svn_xml_protect_pcdata,
690      inherited_prop ? "inherited_property" : "property",
691      "name", propname,
692      "encoding", encoding, NULL);
693  else
694    svn_xml_make_open_tag(
695      outstr, pool, svn_xml_protect_pcdata,
696      inherited_prop ? "inherited_property" : "property",
697      "name", propname, NULL);
698
699  svn_stringbuf_appendcstr(*outstr, xml_safe);
700
701  svn_xml_make_close_tag(
702    outstr, pool,
703    inherited_prop ? "inherited_property" : "property");
704
705  return;
706}
707
708svn_error_t *
709svn_cmdline__parse_config_option(apr_array_header_t *config_options,
710                                 const char *opt_arg,
711                                 apr_pool_t *pool)
712{
713  svn_cmdline__config_argument_t *config_option;
714  const char *first_colon, *second_colon, *equals_sign;
715  apr_size_t len = strlen(opt_arg);
716  if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg))
717    {
718      if ((second_colon = strchr(first_colon + 1, ':')) &&
719          (second_colon != first_colon + 1))
720        {
721          if ((equals_sign = strchr(second_colon + 1, '=')) &&
722              (equals_sign != second_colon + 1))
723            {
724              config_option = apr_pcalloc(pool, sizeof(*config_option));
725              config_option->file = apr_pstrndup(pool, opt_arg,
726                                                 first_colon - opt_arg);
727              config_option->section = apr_pstrndup(pool, first_colon + 1,
728                                                    second_colon - first_colon - 1);
729              config_option->option = apr_pstrndup(pool, second_colon + 1,
730                                                   equals_sign - second_colon - 1);
731
732              if (! (strchr(config_option->option, ':')))
733                {
734                  config_option->value = apr_pstrndup(pool, equals_sign + 1,
735                                                      opt_arg + len - equals_sign - 1);
736                  APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *)
737                                       = config_option;
738                  return SVN_NO_ERROR;
739                }
740            }
741        }
742    }
743  return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
744                          _("Invalid syntax of argument of --config-option"));
745}
746
747svn_error_t *
748svn_cmdline__apply_config_options(apr_hash_t *config,
749                                  const apr_array_header_t *config_options,
750                                  const char *prefix,
751                                  const char *argument_name)
752{
753  int i;
754
755  for (i = 0; i < config_options->nelts; i++)
756   {
757     svn_config_t *cfg;
758     svn_cmdline__config_argument_t *arg =
759                          APR_ARRAY_IDX(config_options, i,
760                                        svn_cmdline__config_argument_t *);
761
762     cfg = svn_hash_gets(config, arg->file);
763
764     if (cfg)
765       {
766         svn_config_set(cfg, arg->section, arg->option, arg->value);
767       }
768     else
769       {
770         svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
771             _("Unrecognized file in argument of %s"), argument_name);
772
773         svn_handle_warning2(stderr, err, prefix);
774         svn_error_clear(err);
775       }
776    }
777
778  return SVN_NO_ERROR;
779}
780
781/* Return a copy, allocated in POOL, of the next line of text from *STR
782 * up to and including a CR and/or an LF. Change *STR to point to the
783 * remainder of the string after the returned part. If there are no
784 * characters to be returned, return NULL; never return an empty string.
785 */
786static const char *
787next_line(const char **str, apr_pool_t *pool)
788{
789  const char *start = *str;
790  const char *p = *str;
791
792  /* n.b. Throughout this fn, we never read any character after a '\0'. */
793  /* Skip over all non-EOL characters, if any. */
794  while (*p != '\r' && *p != '\n' && *p != '\0')
795    p++;
796  /* Skip over \r\n or \n\r or \r or \n, if any. */
797  if (*p == '\r' || *p == '\n')
798    {
799      char c = *p++;
800
801      if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r'))
802        p++;
803    }
804
805  /* Now p points after at most one '\n' and/or '\r'. */
806  *str = p;
807
808  if (p == start)
809    return NULL;
810
811  return svn_string_ncreate(start, p - start, pool)->data;
812}
813
814const char *
815svn_cmdline__indent_string(const char *str,
816                           const char *indent,
817                           apr_pool_t *pool)
818{
819  svn_stringbuf_t *out = svn_stringbuf_create_empty(pool);
820  const char *line;
821
822  while ((line = next_line(&str, pool)))
823    {
824      svn_stringbuf_appendcstr(out, indent);
825      svn_stringbuf_appendcstr(out, line);
826    }
827  return out->data;
828}
829
830svn_error_t *
831svn_cmdline__print_prop_hash(svn_stream_t *out,
832                             apr_hash_t *prop_hash,
833                             svn_boolean_t names_only,
834                             apr_pool_t *pool)
835{
836  apr_array_header_t *sorted_props;
837  int i;
838
839  sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
840                                pool);
841  for (i = 0; i < sorted_props->nelts; i++)
842    {
843      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
844      const char *pname = item.key;
845      svn_string_t *propval = item.value;
846      const char *pname_stdout;
847
848      if (svn_prop_needs_translation(pname))
849        SVN_ERR(svn_subst_detranslate_string(&propval, propval,
850                                             TRUE, pool));
851
852      SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
853
854      if (out)
855        {
856          pname_stdout = apr_psprintf(pool, "  %s\n", pname_stdout);
857          SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout,
858                                              APR_EOL_STR,  /* 'native' eol */
859                                              FALSE, /* no repair */
860                                              NULL,  /* no keywords */
861                                              FALSE, /* no expansion */
862                                              pool));
863
864          SVN_ERR(svn_stream_puts(out, pname_stdout));
865        }
866      else
867        {
868          /* ### We leave these printfs for now, since if propval wasn't
869             translated above, we don't know anything about its encoding.
870             In fact, it might be binary data... */
871          printf("  %s\n", pname_stdout);
872        }
873
874      if (!names_only)
875        {
876          /* Add an extra newline to the value before indenting, so that
877           * every line of output has the indentation whether the value
878           * already ended in a newline or not. */
879          const char *newval = apr_psprintf(pool, "%s\n", propval->data);
880          const char *indented_newval = svn_cmdline__indent_string(newval,
881                                                                   "    ",
882                                                                   pool);
883          if (out)
884            {
885              SVN_ERR(svn_stream_puts(out, indented_newval));
886            }
887          else
888            {
889              printf("%s", indented_newval);
890            }
891        }
892    }
893
894  return SVN_NO_ERROR;
895}
896
897svn_error_t *
898svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr,
899                                 apr_hash_t *prop_hash,
900                                 svn_boolean_t names_only,
901                                 svn_boolean_t inherited_props,
902                                 apr_pool_t *pool)
903{
904  apr_array_header_t *sorted_props;
905  int i;
906
907  if (*outstr == NULL)
908    *outstr = svn_stringbuf_create_empty(pool);
909
910  sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
911                                pool);
912  for (i = 0; i < sorted_props->nelts; i++)
913    {
914      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
915      const char *pname = item.key;
916      svn_string_t *propval = item.value;
917
918      if (names_only)
919        {
920          svn_xml_make_open_tag(
921            outstr, pool, svn_xml_self_closing,
922            inherited_props ? "inherited_property" : "property",
923            "name", pname, NULL);
924        }
925      else
926        {
927          const char *pname_out;
928
929          if (svn_prop_needs_translation(pname))
930            SVN_ERR(svn_subst_detranslate_string(&propval, propval,
931                                                 TRUE, pool));
932
933          SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
934
935          svn_cmdline__print_xml_prop(outstr, pname_out, propval,
936                                      inherited_props, pool);
937        }
938    }
939
940    return SVN_NO_ERROR;
941}
942
943svn_boolean_t
944svn_cmdline__be_interactive(svn_boolean_t non_interactive,
945                            svn_boolean_t force_interactive)
946{
947  /* If neither --non-interactive nor --force-interactive was passed,
948   * be interactive if stdin is a terminal.
949   * If --force-interactive was passed, always be interactive. */
950  if (!force_interactive && !non_interactive)
951    {
952#ifdef WIN32
953      return (_isatty(STDIN_FILENO) != 0);
954#else
955      return (isatty(STDIN_FILENO) != 0);
956#endif
957    }
958  else if (force_interactive)
959    return TRUE;
960
961  return !non_interactive;
962}
963
964
965/* Helper for the next two functions.  Set *EDITOR to some path to an
966   editor binary.  Sources to search include: the EDITOR_CMD argument
967   (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
968   is not NULL), $VISUAL, $EDITOR.  Return
969   SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
970static svn_error_t *
971find_editor_binary(const char **editor,
972                   const char *editor_cmd,
973                   apr_hash_t *config)
974{
975  const char *e;
976  struct svn_config_t *cfg;
977
978  /* Use the editor specified on the command line via --editor-cmd, if any. */
979  e = editor_cmd;
980
981  /* Otherwise look for the Subversion-specific environment variable. */
982  if (! e)
983    e = getenv("SVN_EDITOR");
984
985  /* If not found then fall back on the config file. */
986  if (! e)
987    {
988      cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
989      svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS,
990                     SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
991    }
992
993  /* If not found yet then try general purpose environment variables. */
994  if (! e)
995    e = getenv("VISUAL");
996  if (! e)
997    e = getenv("EDITOR");
998
999#ifdef SVN_CLIENT_EDITOR
1000  /* If still not found then fall back on the hard-coded default. */
1001  if (! e)
1002    e = SVN_CLIENT_EDITOR;
1003#endif
1004
1005  /* Error if there is no editor specified */
1006  if (e)
1007    {
1008      const char *c;
1009
1010      for (c = e; *c; c++)
1011        if (!svn_ctype_isspace(*c))
1012          break;
1013
1014      if (! *c)
1015        return svn_error_create
1016          (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1017           _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
1018             "'editor-cmd' run-time configuration option is empty or "
1019             "consists solely of whitespace. Expected a shell command."));
1020    }
1021  else
1022    return svn_error_create
1023      (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1024       _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
1025         "set, and no 'editor-cmd' run-time configuration option was found"));
1026
1027  *editor = e;
1028  return SVN_NO_ERROR;
1029}
1030
1031
1032svn_error_t *
1033svn_cmdline__edit_file_externally(const char *path,
1034                                  const char *editor_cmd,
1035                                  apr_hash_t *config,
1036                                  apr_pool_t *pool)
1037{
1038  const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
1039  char *old_cwd;
1040  int sys_err;
1041  apr_status_t apr_err;
1042
1043  svn_dirent_split(&base_dir, &file_name, path, pool);
1044
1045  SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1046
1047  apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1048  if (apr_err)
1049    return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1050
1051  /* APR doesn't like "" directories */
1052  if (base_dir[0] == '\0')
1053    base_dir_apr = ".";
1054  else
1055    SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1056
1057  apr_err = apr_filepath_set(base_dir_apr, pool);
1058  if (apr_err)
1059    return svn_error_wrap_apr
1060      (apr_err, _("Can't change working directory to '%s'"), base_dir);
1061
1062  cmd = apr_psprintf(pool, "%s %s", editor, file_name);
1063  sys_err = system(cmd);
1064
1065  apr_err = apr_filepath_set(old_cwd, pool);
1066  if (apr_err)
1067    svn_handle_error2(svn_error_wrap_apr
1068                      (apr_err, _("Can't restore working directory")),
1069                      stderr, TRUE /* fatal */, "svn: ");
1070
1071  if (sys_err)
1072    /* Extracting any meaning from sys_err is platform specific, so just
1073       use the raw value. */
1074    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1075                             _("system('%s') returned %d"), cmd, sys_err);
1076
1077  return SVN_NO_ERROR;
1078}
1079
1080
1081svn_error_t *
1082svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */,
1083                                    const char **tmpfile_left /* UTF-8! */,
1084                                    const char *editor_cmd,
1085                                    const char *base_dir /* UTF-8! */,
1086                                    const svn_string_t *contents /* UTF-8! */,
1087                                    const char *filename,
1088                                    apr_hash_t *config,
1089                                    svn_boolean_t as_text,
1090                                    const char *encoding,
1091                                    apr_pool_t *pool)
1092{
1093  const char *editor;
1094  const char *cmd;
1095  apr_file_t *tmp_file;
1096  const char *tmpfile_name;
1097  const char *tmpfile_native;
1098  const char *tmpfile_apr, *base_dir_apr;
1099  svn_string_t *translated_contents;
1100  apr_status_t apr_err, apr_err2;
1101  apr_size_t written;
1102  apr_finfo_t finfo_before, finfo_after;
1103  svn_error_t *err = SVN_NO_ERROR, *err2;
1104  char *old_cwd;
1105  int sys_err;
1106  svn_boolean_t remove_file = TRUE;
1107
1108  SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1109
1110  /* Convert file contents from UTF-8/LF if desired. */
1111  if (as_text)
1112    {
1113      const char *translated;
1114      SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
1115                                           APR_EOL_STR, FALSE,
1116                                           NULL, FALSE, pool));
1117      translated_contents = svn_string_create_empty(pool);
1118      if (encoding)
1119        SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
1120                                              translated, encoding, pool));
1121      else
1122        SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
1123                                          translated, pool));
1124      translated_contents->len = strlen(translated_contents->data);
1125    }
1126  else
1127    translated_contents = svn_string_dup(contents, pool);
1128
1129  /* Move to BASE_DIR to avoid getting characters that need quoting
1130     into tmpfile_name */
1131  apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1132  if (apr_err)
1133    return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1134
1135  /* APR doesn't like "" directories */
1136  if (base_dir[0] == '\0')
1137    base_dir_apr = ".";
1138  else
1139    SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1140  apr_err = apr_filepath_set(base_dir_apr, pool);
1141  if (apr_err)
1142    {
1143      return svn_error_wrap_apr
1144        (apr_err, _("Can't change working directory to '%s'"), base_dir);
1145    }
1146
1147  /*** From here on, any problems that occur require us to cd back!! ***/
1148
1149  /* Ask the working copy for a temporary file named FILENAME-something. */
1150  err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1151                                   "" /* dirpath */,
1152                                   filename,
1153                                   ".tmp",
1154                                   svn_io_file_del_none, pool, pool);
1155
1156  if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS))
1157    {
1158      const char *temp_dir_apr;
1159
1160      svn_error_clear(err);
1161
1162      SVN_ERR(svn_io_temp_dir(&base_dir, pool));
1163
1164      SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool));
1165      apr_err = apr_filepath_set(temp_dir_apr, pool);
1166      if (apr_err)
1167        {
1168          return svn_error_wrap_apr
1169            (apr_err, _("Can't change working directory to '%s'"), base_dir);
1170        }
1171
1172      err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1173                                       "" /* dirpath */,
1174                                       filename,
1175                                       ".tmp",
1176                                       svn_io_file_del_none, pool, pool);
1177    }
1178
1179  if (err)
1180    goto cleanup2;
1181
1182  /*** From here on, any problems that occur require us to cleanup
1183       the file we just created!! ***/
1184
1185  /* Dump initial CONTENTS to TMP_FILE. */
1186  apr_err = apr_file_write_full(tmp_file, translated_contents->data,
1187                                translated_contents->len, &written);
1188
1189  apr_err2 = apr_file_close(tmp_file);
1190  if (! apr_err)
1191    apr_err = apr_err2;
1192
1193  /* Make sure the whole CONTENTS were written, else return an error. */
1194  if (apr_err)
1195    {
1196      err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"),
1197                               tmpfile_name);
1198      goto cleanup;
1199    }
1200
1201  err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool);
1202  if (err)
1203    goto cleanup;
1204
1205  /* Get information about the temporary file before the user has
1206     been allowed to edit its contents. */
1207  apr_err = apr_stat(&finfo_before, tmpfile_apr,
1208                     APR_FINFO_MTIME, pool);
1209  if (apr_err)
1210    {
1211      err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1212      goto cleanup;
1213    }
1214
1215  /* Backdate the file a little bit in case the editor is very fast
1216     and doesn't change the size.  (Use two seconds, since some
1217     filesystems have coarse granularity.)  It's OK if this call
1218     fails, so we don't check its return value.*/
1219  apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool);
1220
1221  /* Stat it again to get the mtime we actually set. */
1222  apr_err = apr_stat(&finfo_before, tmpfile_apr,
1223                     APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1224  if (apr_err)
1225    {
1226      err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1227      goto cleanup;
1228    }
1229
1230  /* Prepare the editor command line.  */
1231  err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
1232  if (err)
1233    goto cleanup;
1234  cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
1235
1236  /* If the caller wants us to leave the file around, return the path
1237     of the file we'll use, and make a note not to destroy it.  */
1238  if (tmpfile_left)
1239    {
1240      *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool);
1241      remove_file = FALSE;
1242    }
1243
1244  /* Now, run the editor command line.  */
1245  sys_err = system(cmd);
1246  if (sys_err != 0)
1247    {
1248      /* Extracting any meaning from sys_err is platform specific, so just
1249         use the raw value. */
1250      err =  svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1251                               _("system('%s') returned %d"), cmd, sys_err);
1252      goto cleanup;
1253    }
1254
1255  /* Get information about the temporary file after the assumed editing. */
1256  apr_err = apr_stat(&finfo_after, tmpfile_apr,
1257                     APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1258  if (apr_err)
1259    {
1260      err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1261      goto cleanup;
1262    }
1263
1264  /* If the file looks changed... */
1265  if ((finfo_before.mtime != finfo_after.mtime) ||
1266      (finfo_before.size != finfo_after.size))
1267    {
1268      svn_stringbuf_t *edited_contents_s;
1269      err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool);
1270      if (err)
1271        goto cleanup;
1272
1273      *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s);
1274
1275      /* Translate back to UTF8/LF if desired. */
1276      if (as_text)
1277        {
1278          err = svn_subst_translate_string2(edited_contents, FALSE, FALSE,
1279                                            *edited_contents, encoding, FALSE,
1280                                            pool, pool);
1281          if (err)
1282            {
1283              err = svn_error_quick_wrap
1284                (err,
1285                 _("Error normalizing edited contents to internal format"));
1286              goto cleanup;
1287            }
1288        }
1289    }
1290  else
1291    {
1292      /* No edits seem to have been made */
1293      *edited_contents = NULL;
1294    }
1295
1296 cleanup:
1297  if (remove_file)
1298    {
1299      /* Remove the file from disk.  */
1300      err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool);
1301
1302      /* Only report remove error if there was no previous error. */
1303      if (! err && err2)
1304        err = err2;
1305      else
1306        svn_error_clear(err2);
1307    }
1308
1309 cleanup2:
1310  /* If we against all probability can't cd back, all further relative
1311     file references would be screwed up, so we have to abort. */
1312  apr_err = apr_filepath_set(old_cwd, pool);
1313  if (apr_err)
1314    {
1315      svn_handle_error2(svn_error_wrap_apr
1316                        (apr_err, _("Can't restore working directory")),
1317                        stderr, TRUE /* fatal */, "svn: ");
1318    }
1319
1320  return svn_error_trace(err);
1321}
1322