]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_subr/cmdline.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_subr / cmdline.c
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 #include <conio.h>
38 #endif
39
40 #include <apr.h>                /* for STDIN_FILENO */
41 #include <apr_errno.h>          /* for apr_strerror */
42 #include <apr_general.h>        /* for apr_initialize/apr_terminate */
43 #include <apr_strings.h>        /* for apr_snprintf */
44 #include <apr_pools.h>
45 #include <apr_signal.h>
46
47 #include "svn_cmdline.h"
48 #include "svn_ctype.h"
49 #include "svn_dso.h"
50 #include "svn_dirent_uri.h"
51 #include "svn_hash.h"
52 #include "svn_path.h"
53 #include "svn_pools.h"
54 #include "svn_error.h"
55 #include "svn_nls.h"
56 #include "svn_utf.h"
57 #include "svn_auth.h"
58 #include "svn_xml.h"
59 #include "svn_base64.h"
60 #include "svn_config.h"
61 #include "svn_sorts.h"
62 #include "svn_props.h"
63 #include "svn_subst.h"
64
65 #include "private/svn_cmdline_private.h"
66 #include "private/svn_utf_private.h"
67 #include "private/svn_sorts_private.h"
68 #include "private/svn_string_private.h"
69
70 #include "svn_private_config.h"
71
72 #include "win32_crashrpt.h"
73
74 #if defined(WIN32) && defined(_MSC_VER) && (_MSC_VER < 1400)
75 /* Before Visual Studio 2005, the C runtime didn't handle encodings for the
76    for the stdio output handling. */
77 #define CMDLINE_USE_CUSTOM_ENCODING
78
79 /* The stdin encoding. If null, it's the same as the native encoding. */
80 static const char *input_encoding = NULL;
81
82 /* The stdout encoding. If null, it's the same as the native encoding. */
83 static const char *output_encoding = NULL;
84 #elif defined(WIN32) && defined(_MSC_VER)
85 /* For now limit this code to Visual C++, as the result is highly dependent
86    on the CRT implementation */
87 #define USE_WIN32_CONSOLE_SHORTCUT
88
89 /* When TRUE, stdout/stderr is directly connected to a console */
90 static svn_boolean_t shortcut_stdout_to_console = FALSE;
91 static svn_boolean_t shortcut_stderr_to_console = FALSE;
92 #endif
93
94
95 int
96 svn_cmdline_init(const char *progname, FILE *error_stream)
97 {
98   apr_status_t status;
99   apr_pool_t *pool;
100   svn_error_t *err;
101   char prefix_buf[64];  /* 64 is probably bigger than most program names */
102
103 #ifndef WIN32
104   {
105     struct stat st;
106
107     /* The following makes sure that file descriptors 0 (stdin), 1
108        (stdout) and 2 (stderr) will not be "reused", because if
109        e.g. file descriptor 2 would be reused when opening a file, a
110        write to stderr would write to that file and most likely
111        corrupt it. */
112     if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) ||
113         (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) ||
114         (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1))
115       {
116         if (error_stream)
117           fprintf(error_stream, "%s: error: cannot open '/dev/null'\n",
118                   progname);
119         return EXIT_FAILURE;
120       }
121   }
122 #endif
123
124   /* Ignore any errors encountered while attempting to change stream
125      buffering, as the streams should retain their default buffering
126      modes. */
127   if (error_stream)
128     setvbuf(error_stream, NULL, _IONBF, 0);
129 #ifndef WIN32
130   setvbuf(stdout, NULL, _IOLBF, 0);
131 #endif
132
133 #ifdef WIN32
134 #ifdef CMDLINE_USE_CUSTOM_ENCODING
135   /* Initialize the input and output encodings. */
136   {
137     static char input_encoding_buffer[16];
138     static char output_encoding_buffer[16];
139
140     apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer,
141                  "CP%u", (unsigned) GetConsoleCP());
142     input_encoding = input_encoding_buffer;
143
144     apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer,
145                  "CP%u", (unsigned) GetConsoleOutputCP());
146     output_encoding = output_encoding_buffer;
147   }
148 #endif /* CMDLINE_USE_CUSTOM_ENCODING */
149
150 #ifdef SVN_USE_WIN32_CRASHHANDLER
151   if (!getenv("SVN_CMDLINE_DISABLE_CRASH_HANDLER"))
152     {
153       /* Attach (but don't load) the crash handler */
154       SetUnhandledExceptionFilter(svn__unhandled_exception_filter);
155
156 #if _MSC_VER >= 1400
157       /* ### This should work for VC++ 2002 (=1300) and later */
158       /* Show the abort message on STDERR instead of a dialog to allow
159          scripts (e.g. our testsuite) to continue after an abort without
160          user intervention. Allow overriding for easier debugging. */
161       if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT"))
162         {
163           /* In release mode: Redirect abort() errors to stderr */
164           _set_error_mode(_OUT_TO_STDERR);
165
166           /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr.
167              (Ignored in release builds) */
168           _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR);
169           _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR);
170           _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR);
171           _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
172           _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
173           _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
174         }
175 #endif /* _MSC_VER >= 1400 */
176     }
177 #endif /* SVN_USE_WIN32_CRASHHANDLER */
178
179 #endif /* WIN32 */
180
181   /* C programs default to the "C" locale. But because svn is supposed
182      to be i18n-aware, it should inherit the default locale of its
183      environment.  */
184   if (!setlocale(LC_ALL, "")
185       && !setlocale(LC_CTYPE, ""))
186     {
187       if (error_stream)
188         {
189           const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL };
190           const char **env_var = &env_vars[0], *env_val = NULL;
191           while (*env_var)
192             {
193               env_val = getenv(*env_var);
194               if (env_val && env_val[0])
195                 break;
196               ++env_var;
197             }
198
199           if (!*env_var)
200             {
201               /* Unlikely. Can setlocale fail if no env vars are set? */
202               --env_var;
203               env_val = "not set";
204             }
205
206           fprintf(error_stream,
207                   "%s: warning: cannot set LC_CTYPE locale\n"
208                   "%s: warning: environment variable %s is %s\n"
209                   "%s: warning: please check that your locale name is correct\n",
210                   progname, progname, *env_var, env_val, progname);
211         }
212     }
213
214   /* Initialize the APR subsystem, and register an atexit() function
215      to Uninitialize that subsystem at program exit. */
216   status = apr_initialize();
217   if (status)
218     {
219       if (error_stream)
220         {
221           char buf[1024];
222           apr_strerror(status, buf, sizeof(buf) - 1);
223           fprintf(error_stream,
224                   "%s: error: cannot initialize APR: %s\n",
225                   progname, buf);
226         }
227       return EXIT_FAILURE;
228     }
229
230   strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3);
231   prefix_buf[sizeof(prefix_buf) - 3] = '\0';
232   strcat(prefix_buf, ": ");
233
234   /* DSO pool must be created before any other pools used by the
235      application so that pool cleanup doesn't unload DSOs too
236      early. See docstring of svn_dso_initialize2(). */
237   if ((err = svn_dso_initialize2()))
238     {
239       if (error_stream)
240         svn_handle_error2(err, error_stream, TRUE, prefix_buf);
241
242       svn_error_clear(err);
243       return EXIT_FAILURE;
244     }
245
246   if (0 > atexit(apr_terminate))
247     {
248       if (error_stream)
249         fprintf(error_stream,
250                 "%s: error: atexit registration failed\n",
251                 progname);
252       return EXIT_FAILURE;
253     }
254
255   /* Create a pool for use by the UTF-8 routines.  It will be cleaned
256      up by APR at exit time. */
257   pool = svn_pool_create(NULL);
258   svn_utf_initialize2(FALSE, pool);
259
260   if ((err = svn_nls_init()))
261     {
262       if (error_stream)
263         svn_handle_error2(err, error_stream, TRUE, prefix_buf);
264
265       svn_error_clear(err);
266       return EXIT_FAILURE;
267     }
268
269 #ifdef USE_WIN32_CONSOLE_SHORTCUT
270   if (_isatty(STDOUT_FILENO))
271     {
272       DWORD ignored;
273       HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
274
275        /* stdout is a char device handle, but is it the console? */
276        if (GetConsoleMode(stdout_handle, &ignored))
277         shortcut_stdout_to_console = TRUE;
278
279        /* Don't close stdout_handle */
280     }
281   if (_isatty(STDERR_FILENO))
282     {
283       DWORD ignored;
284       HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
285
286        /* stderr is a char device handle, but is it the console? */
287       if (GetConsoleMode(stderr_handle, &ignored))
288           shortcut_stderr_to_console = TRUE;
289
290       /* Don't close stderr_handle */
291     }
292 #endif
293
294   return EXIT_SUCCESS;
295 }
296
297
298 svn_error_t *
299 svn_cmdline_cstring_from_utf8(const char **dest,
300                               const char *src,
301                               apr_pool_t *pool)
302 {
303 #ifdef CMDLINE_USE_CUSTOM_ENCODING
304   if (output_encoding != NULL)
305     return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool);
306 #endif
307
308   return svn_utf_cstring_from_utf8(dest, src, pool);
309 }
310
311
312 const char *
313 svn_cmdline_cstring_from_utf8_fuzzy(const char *src,
314                                     apr_pool_t *pool)
315 {
316   return svn_utf__cstring_from_utf8_fuzzy(src, pool,
317                                           svn_cmdline_cstring_from_utf8);
318 }
319
320
321 svn_error_t *
322 svn_cmdline_cstring_to_utf8(const char **dest,
323                             const char *src,
324                             apr_pool_t *pool)
325 {
326 #ifdef CMDLINE_USE_CUSTOM_ENCODING
327   if (input_encoding != NULL)
328     return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool);
329 #endif
330
331   return svn_utf_cstring_to_utf8(dest, src, pool);
332 }
333
334
335 svn_error_t *
336 svn_cmdline_path_local_style_from_utf8(const char **dest,
337                                        const char *src,
338                                        apr_pool_t *pool)
339 {
340   return svn_cmdline_cstring_from_utf8(dest,
341                                        svn_dirent_local_style(src, pool),
342                                        pool);
343 }
344
345 svn_error_t *
346 svn_cmdline__stdin_readline(const char **result,
347                             apr_pool_t *result_pool,
348                             apr_pool_t *scratch_pool)
349 {
350   svn_stringbuf_t *buf = NULL;
351   svn_stream_t *stdin_stream = NULL;
352   svn_boolean_t oob = FALSE;
353
354   SVN_ERR(svn_stream_for_stdin2(&stdin_stream, TRUE, scratch_pool));
355   SVN_ERR(svn_stream_readline(stdin_stream, &buf, APR_EOL_STR, &oob, result_pool));
356
357   *result = buf->data;
358
359   return SVN_NO_ERROR;
360 }
361
362 svn_error_t *
363 svn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...)
364 {
365   const char *message;
366   va_list ap;
367
368   /* A note about encoding issues:
369    * APR uses the execution character set, but here we give it UTF-8 strings,
370    * both the fmt argument and any other string arguments.  Since apr_pvsprintf
371    * only cares about and produces ASCII characters, this works under the
372    * assumption that all supported platforms use an execution character set
373    * with ASCII as a subset.
374    */
375
376   va_start(ap, fmt);
377   message = apr_pvsprintf(pool, fmt, ap);
378   va_end(ap);
379
380   return svn_cmdline_fputs(message, stdout, pool);
381 }
382
383 svn_error_t *
384 svn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...)
385 {
386   const char *message;
387   va_list ap;
388
389   /* See svn_cmdline_printf () for a note about character encoding issues. */
390
391   va_start(ap, fmt);
392   message = apr_pvsprintf(pool, fmt, ap);
393   va_end(ap);
394
395   return svn_cmdline_fputs(message, stream, pool);
396 }
397
398 svn_error_t *
399 svn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool)
400 {
401   svn_error_t *err;
402   const char *out;
403
404 #ifdef USE_WIN32_CONSOLE_SHORTCUT
405   /* For legacy reasons the Visual C++ runtime converts output to the console
406      from the native 'ansi' encoding, to unicode, then back to 'ansi' and then
407      onwards to the console which is implemented as unicode.
408
409      For operations like 'svn status -v' this may cause about 70% of the total
410      processing time, with absolutely no gain.
411
412      For this specific scenario this shortcut exists. It has the nice side
413      effect of allowing full unicode output to the console.
414
415      Note that this shortcut is not used when the output is redirected, as in
416      that case the data is put on the pipe/file after the first conversion to
417      ansi. In this case the most expensive conversion is already avoided.
418    */
419   if ((stream == stdout && shortcut_stdout_to_console)
420       || (stream == stderr && shortcut_stderr_to_console))
421     {
422       WCHAR *result;
423
424       if (string[0] == '\0')
425         return SVN_NO_ERROR;
426
427       SVN_ERR(svn_cmdline_fflush(stream)); /* Flush existing output */
428
429       SVN_ERR(svn_utf__win32_utf8_to_utf16(&result, string, NULL, pool));
430
431       if (_cputws(result))
432         {
433           if (apr_get_os_error())
434           {
435             return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
436           }
437         }
438
439       return SVN_NO_ERROR;
440     }
441 #endif
442
443   err = svn_cmdline_cstring_from_utf8(&out, string, pool);
444
445   if (err)
446     {
447       svn_error_clear(err);
448       out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
449     }
450
451   /* On POSIX systems, errno will be set on an error in fputs, but this might
452      not be the case on other platforms.  We reset errno and only
453      use it if it was set by the below fputs call.  Else, we just return
454      a generic error. */
455   errno = 0;
456
457   if (fputs(out, stream) == EOF)
458     {
459       if (apr_get_os_error()) /* is errno on POSIX */
460         {
461           /* ### Issue #3014: Return a specific error for broken pipes,
462            * ### with a single element in the error chain. */
463           if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
464             return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
465           else
466             return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
467         }
468       else
469         return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
470     }
471
472   return SVN_NO_ERROR;
473 }
474
475 svn_error_t *
476 svn_cmdline_fflush(FILE *stream)
477 {
478   /* See comment in svn_cmdline_fputs about use of errno and stdio. */
479   errno = 0;
480   if (fflush(stream) == EOF)
481     {
482       if (apr_get_os_error()) /* is errno on POSIX */
483         {
484           /* ### Issue #3014: Return a specific error for broken pipes,
485            * ### with a single element in the error chain. */
486           if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
487             return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
488           else
489             return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
490         }
491       else
492         return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
493     }
494
495   return SVN_NO_ERROR;
496 }
497
498 const char *svn_cmdline_output_encoding(apr_pool_t *pool)
499 {
500 #ifdef CMDLINE_USE_CUSTOM_ENCODING
501   if (output_encoding)
502     return apr_pstrdup(pool, output_encoding);
503 #endif
504
505   return SVN_APR_LOCALE_CHARSET;
506 }
507
508 int
509 svn_cmdline_handle_exit_error(svn_error_t *err,
510                               apr_pool_t *pool,
511                               const char *prefix)
512 {
513   /* Issue #3014:
514    * Don't print anything on broken pipes. The pipe was likely
515    * closed by the process at the other end. We expect that
516    * process to perform error reporting as necessary.
517    *
518    * ### This assumes that there is only one error in a chain for
519    * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
520   if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
521     svn_handle_error2(err, stderr, FALSE, prefix);
522   svn_error_clear(err);
523   if (pool)
524     svn_pool_destroy(pool);
525   return EXIT_FAILURE;
526 }
527
528 struct trust_server_cert_non_interactive_baton {
529   svn_boolean_t trust_server_cert_unknown_ca;
530   svn_boolean_t trust_server_cert_cn_mismatch;
531   svn_boolean_t trust_server_cert_expired;
532   svn_boolean_t trust_server_cert_not_yet_valid;
533   svn_boolean_t trust_server_cert_other_failure;
534 };
535
536 /* This implements 'svn_auth_ssl_server_trust_prompt_func_t'.
537
538    Don't actually prompt.  Instead, set *CRED_P to valid credentials
539    iff FAILURES is empty or may be accepted according to the flags
540    in BATON. If there are any other failure bits, then set *CRED_P
541    to null (that is, reject the cert).
542
543    Ignore MAY_SAVE; we don't save certs we never prompted for.
544
545    Ignore REALM and CERT_INFO,
546
547    Ignore any further films by George Lucas. */
548 static svn_error_t *
549 trust_server_cert_non_interactive(svn_auth_cred_ssl_server_trust_t **cred_p,
550                                   void *baton,
551                                   const char *realm,
552                                   apr_uint32_t failures,
553                                   const svn_auth_ssl_server_cert_info_t
554                                     *cert_info,
555                                   svn_boolean_t may_save,
556                                   apr_pool_t *pool)
557 {
558   struct trust_server_cert_non_interactive_baton *b = baton;
559   apr_uint32_t non_ignored_failures;
560   *cred_p = NULL;
561
562   /* Mask away bits we are instructed to ignore. */
563   non_ignored_failures = failures & ~(
564         (b->trust_server_cert_unknown_ca ? SVN_AUTH_SSL_UNKNOWNCA : 0)
565       | (b->trust_server_cert_cn_mismatch ? SVN_AUTH_SSL_CNMISMATCH : 0)
566       | (b->trust_server_cert_expired ? SVN_AUTH_SSL_EXPIRED : 0)
567       | (b->trust_server_cert_not_yet_valid ? SVN_AUTH_SSL_NOTYETVALID : 0)
568       | (b->trust_server_cert_other_failure ? SVN_AUTH_SSL_OTHER : 0)
569   );
570
571   /* If no failures remain, accept the certificate. */
572   if (non_ignored_failures == 0)
573     {
574       *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
575       (*cred_p)->may_save = FALSE;
576       (*cred_p)->accepted_failures = failures;
577     }
578
579   return SVN_NO_ERROR;
580 }
581
582 svn_error_t *
583 svn_cmdline_create_auth_baton2(svn_auth_baton_t **ab,
584                                svn_boolean_t non_interactive,
585                                const char *auth_username,
586                                const char *auth_password,
587                                const char *config_dir,
588                                svn_boolean_t no_auth_cache,
589                                svn_boolean_t trust_server_cert_unknown_ca,
590                                svn_boolean_t trust_server_cert_cn_mismatch,
591                                svn_boolean_t trust_server_cert_expired,
592                                svn_boolean_t trust_server_cert_not_yet_valid,
593                                svn_boolean_t trust_server_cert_other_failure,
594                                svn_config_t *cfg,
595                                svn_cancel_func_t cancel_func,
596                                void *cancel_baton,
597                                apr_pool_t *pool)
598
599 {
600   svn_boolean_t store_password_val = TRUE;
601   svn_boolean_t store_auth_creds_val = TRUE;
602   svn_auth_provider_object_t *provider;
603   svn_cmdline_prompt_baton2_t *pb = NULL;
604
605   /* The whole list of registered providers */
606   apr_array_header_t *providers;
607
608   /* Populate the registered providers with the platform-specific providers */
609   SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers,
610                                                           cfg, pool));
611
612   /* If we have a cancellation function, cram it and the stuff it
613      needs into the prompt baton. */
614   if (cancel_func)
615     {
616       pb = apr_palloc(pool, sizeof(*pb));
617       pb->cancel_func = cancel_func;
618       pb->cancel_baton = cancel_baton;
619       pb->config_dir = config_dir;
620     }
621
622   if (!non_interactive)
623     {
624       /* This provider doesn't prompt the user in order to get creds;
625          it prompts the user regarding the caching of creds. */
626       svn_auth_get_simple_provider2(&provider,
627                                     svn_cmdline_auth_plaintext_prompt,
628                                     pb, pool);
629     }
630   else
631     {
632       svn_auth_get_simple_provider2(&provider, NULL, NULL, pool);
633     }
634
635   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
636   svn_auth_get_username_provider(&provider, pool);
637   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
638
639   svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
640   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
641   svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
642   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
643
644   if (!non_interactive)
645     {
646       /* This provider doesn't prompt the user in order to get creds;
647          it prompts the user regarding the caching of creds. */
648       svn_auth_get_ssl_client_cert_pw_file_provider2
649         (&provider, svn_cmdline_auth_plaintext_passphrase_prompt,
650          pb, pool);
651     }
652   else
653     {
654       svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL,
655                                                      pool);
656     }
657   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
658
659   if (!non_interactive)
660     {
661       svn_boolean_t ssl_client_cert_file_prompt;
662
663       SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt,
664                                   SVN_CONFIG_SECTION_AUTH,
665                                   SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT,
666                                   FALSE));
667
668       /* Two basic prompt providers: username/password, and just username. */
669       svn_auth_get_simple_prompt_provider(&provider,
670                                           svn_cmdline_auth_simple_prompt,
671                                           pb,
672                                           2, /* retry limit */
673                                           pool);
674       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
675
676       svn_auth_get_username_prompt_provider
677         (&provider, svn_cmdline_auth_username_prompt, pb,
678          2, /* retry limit */ pool);
679       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
680
681       /* SSL prompt providers: server-certs and client-cert-passphrases.  */
682       svn_auth_get_ssl_server_trust_prompt_provider
683         (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool);
684       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
685
686       svn_auth_get_ssl_client_cert_pw_prompt_provider
687         (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool);
688       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
689
690       /* If configuration allows, add a provider for client-cert path
691          prompting, too. */
692       if (ssl_client_cert_file_prompt)
693         {
694           svn_auth_get_ssl_client_cert_prompt_provider
695             (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
696           APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
697         }
698     }
699   else if (trust_server_cert_unknown_ca || trust_server_cert_cn_mismatch ||
700            trust_server_cert_expired || trust_server_cert_not_yet_valid ||
701            trust_server_cert_other_failure)
702     {
703       struct trust_server_cert_non_interactive_baton *b;
704
705       b = apr_palloc(pool, sizeof(*b));
706       b->trust_server_cert_unknown_ca = trust_server_cert_unknown_ca;
707       b->trust_server_cert_cn_mismatch = trust_server_cert_cn_mismatch;
708       b->trust_server_cert_expired = trust_server_cert_expired;
709       b->trust_server_cert_not_yet_valid = trust_server_cert_not_yet_valid;
710       b->trust_server_cert_other_failure = trust_server_cert_other_failure;
711
712       /* Remember, only register this provider if non_interactive. */
713       svn_auth_get_ssl_server_trust_prompt_provider
714         (&provider, trust_server_cert_non_interactive, b, pool);
715       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
716     }
717
718   /* Build an authentication baton to give to libsvn_client. */
719   svn_auth_open(ab, providers, pool);
720
721   /* Place any default --username or --password credentials into the
722      auth_baton's run-time parameter hash. */
723   if (auth_username)
724     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
725                            auth_username);
726   if (auth_password)
727     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
728                            auth_password);
729
730   /* Same with the --non-interactive option. */
731   if (non_interactive)
732     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, "");
733
734   if (config_dir)
735     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR,
736                            config_dir);
737
738   /* Determine whether storing passwords in any form is allowed.
739    * This is the deprecated location for this option, the new
740    * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
741    * override the value we set here. */
742   SVN_ERR(svn_config_get_bool(cfg, &store_password_val,
743                               SVN_CONFIG_SECTION_AUTH,
744                               SVN_CONFIG_OPTION_STORE_PASSWORDS,
745                               SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS));
746
747   if (! store_password_val)
748     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, "");
749
750   /* Determine whether we are allowed to write to the auth/ area.
751    * This is the deprecated location for this option, the new
752    * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
753    * override the value we set here. */
754   SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val,
755                               SVN_CONFIG_SECTION_AUTH,
756                               SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
757                               SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS));
758
759   if (no_auth_cache || ! store_auth_creds_val)
760     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
761
762 #ifdef SVN_HAVE_GNOME_KEYRING
763   svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC,
764                          &svn_cmdline__auth_gnome_keyring_unlock_prompt);
765 #endif /* SVN_HAVE_GNOME_KEYRING */
766
767   return SVN_NO_ERROR;
768 }
769
770 svn_error_t *
771 svn_cmdline__getopt_init(apr_getopt_t **os,
772                          int argc,
773                          const char *argv[],
774                          apr_pool_t *pool)
775 {
776   apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv);
777   if (apr_err)
778     return svn_error_wrap_apr(apr_err,
779                               _("Error initializing command line arguments"));
780   return SVN_NO_ERROR;
781 }
782
783
784 void
785 svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
786                             const char* propname,
787                             svn_string_t *propval,
788                             svn_boolean_t inherited_prop,
789                             apr_pool_t *pool)
790 {
791   const char *xml_safe;
792   const char *encoding = NULL;
793
794   if (*outstr == NULL)
795     *outstr = svn_stringbuf_create_empty(pool);
796
797   if (svn_xml_is_xml_safe(propval->data, propval->len))
798     {
799       svn_stringbuf_t *xml_esc = NULL;
800       svn_xml_escape_cdata_string(&xml_esc, propval, pool);
801       xml_safe = xml_esc->data;
802     }
803   else
804     {
805       const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE,
806                                                                pool);
807       encoding = "base64";
808       xml_safe = base64ed->data;
809     }
810
811   if (encoding)
812     svn_xml_make_open_tag(
813       outstr, pool, svn_xml_protect_pcdata,
814       inherited_prop ? "inherited_property" : "property",
815       "name", propname,
816       "encoding", encoding, SVN_VA_NULL);
817   else
818     svn_xml_make_open_tag(
819       outstr, pool, svn_xml_protect_pcdata,
820       inherited_prop ? "inherited_property" : "property",
821       "name", propname, SVN_VA_NULL);
822
823   svn_stringbuf_appendcstr(*outstr, xml_safe);
824
825   svn_xml_make_close_tag(
826     outstr, pool,
827     inherited_prop ? "inherited_property" : "property");
828
829   return;
830 }
831
832 /* Return the most similar string to NEEDLE in HAYSTACK, which contains
833  * HAYSTACK_LEN elements.  Return NULL if no string is sufficiently similar.
834  */
835 /* See svn_cl__similarity_check() for a more general solution. */
836 static const char *
837 most_similar(const char *needle_cstr,
838              const char **haystack,
839              apr_size_t haystack_len,
840              apr_pool_t *scratch_pool)
841 {
842   const char *max_similar = NULL;
843   apr_size_t max_score = 0;
844   apr_size_t i;
845   svn_membuf_t membuf;
846   svn_string_t *needle_str = svn_string_create(needle_cstr, scratch_pool);
847
848   svn_membuf__create(&membuf, 64, scratch_pool);
849
850   for (i = 0; i < haystack_len; i++)
851     {
852       apr_size_t score;
853       svn_string_t *hay = svn_string_create(haystack[i], scratch_pool);
854
855       score = svn_string__similarity(needle_str, hay, &membuf, NULL);
856
857       /* If you update this factor, consider updating
858        * svn_cl__similarity_check(). */
859       if (score >= (2 * SVN_STRING__SIM_RANGE_MAX + 1) / 3
860           && score > max_score)
861         {
862           max_score = score;
863           max_similar = haystack[i];
864         }
865     }
866
867   return max_similar;
868 }
869
870 /* Verify that NEEDLE is in HAYSTACK, which contains HAYSTACK_LEN elements. */
871 static svn_error_t *
872 string_in_array(const char *needle,
873                 const char **haystack,
874                 apr_size_t haystack_len,
875                 apr_pool_t *scratch_pool)
876 {
877   const char *next_of_kin;
878   apr_size_t i;
879   for (i = 0; i < haystack_len; i++)
880     {
881       if (!strcmp(needle, haystack[i]))
882         return SVN_NO_ERROR;
883     }
884
885   /* Error. */
886   next_of_kin = most_similar(needle, haystack, haystack_len, scratch_pool);
887   if (next_of_kin)
888     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
889                              _("Ignoring unknown value '%s'; "
890                                "did you mean '%s'?"),
891                              needle, next_of_kin);
892   else
893     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
894                              _("Ignoring unknown value '%s'"),
895                              needle);
896 }
897
898 #include "config_keys.inc"
899
900 /* Validate the FILE, SECTION, and OPTION components of CONFIG_OPTION are
901  * known.  Return an error if not.  (An unknown value may be either a typo
902  * or added in a newer minor version of Subversion.) */
903 static svn_error_t *
904 validate_config_option(svn_cmdline__config_argument_t *config_option,
905                        apr_pool_t *scratch_pool)
906 {
907   svn_boolean_t arbitrary_keys = FALSE;
908
909   /* TODO: some day, we could also verify that OPTION is valid for SECTION;
910      i.e., forbid invalid combinations such as config:auth:diff-extensions. */
911
912 #define ARRAYLEN(x) ( sizeof((x)) / sizeof((x)[0]) )
913
914   SVN_ERR(string_in_array(config_option->file, svn__valid_config_files,
915                           ARRAYLEN(svn__valid_config_files),
916                           scratch_pool));
917   SVN_ERR(string_in_array(config_option->section, svn__valid_config_sections,
918                           ARRAYLEN(svn__valid_config_sections),
919                           scratch_pool));
920
921   /* Don't validate option names for sections such as servers[group],
922    * config[tunnels], and config[auto-props] that permit arbitrary options. */
923     {
924       int i;
925
926       for (i = 0; i < ARRAYLEN(svn__empty_config_sections); i++)
927         {
928         if (!strcmp(config_option->section, svn__empty_config_sections[i]))
929           arbitrary_keys = TRUE;
930         }
931     }
932
933   if (! arbitrary_keys)
934     SVN_ERR(string_in_array(config_option->option, svn__valid_config_options,
935                             ARRAYLEN(svn__valid_config_options),
936                             scratch_pool));
937
938 #undef ARRAYLEN
939
940   return SVN_NO_ERROR;
941 }
942
943 svn_error_t *
944 svn_cmdline__parse_config_option(apr_array_header_t *config_options,
945                                  const char *opt_arg,
946                                  const char *prefix,
947                                  apr_pool_t *pool)
948 {
949   svn_cmdline__config_argument_t *config_option;
950   const char *first_colon, *second_colon, *equals_sign;
951   apr_size_t len = strlen(opt_arg);
952   if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg))
953     {
954       if ((second_colon = strchr(first_colon + 1, ':')) &&
955           (second_colon != first_colon + 1))
956         {
957           if ((equals_sign = strchr(second_colon + 1, '=')) &&
958               (equals_sign != second_colon + 1))
959             {
960               svn_error_t *warning;
961
962               config_option = apr_pcalloc(pool, sizeof(*config_option));
963               config_option->file = apr_pstrndup(pool, opt_arg,
964                                                  first_colon - opt_arg);
965               config_option->section = apr_pstrndup(pool, first_colon + 1,
966                                                     second_colon - first_colon - 1);
967               config_option->option = apr_pstrndup(pool, second_colon + 1,
968                                                    equals_sign - second_colon - 1);
969
970               warning = validate_config_option(config_option, pool);
971               if (warning)
972                 {
973                   svn_handle_warning2(stderr, warning, prefix);
974                   svn_error_clear(warning);
975                 }
976
977               if (! (strchr(config_option->option, ':')))
978                 {
979                   config_option->value = apr_pstrndup(pool, equals_sign + 1,
980                                                       opt_arg + len - equals_sign - 1);
981                   APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *)
982                                        = config_option;
983                   return SVN_NO_ERROR;
984                 }
985             }
986         }
987     }
988   return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
989                           _("Invalid syntax of argument of --config-option"));
990 }
991
992 svn_error_t *
993 svn_cmdline__apply_config_options(apr_hash_t *config,
994                                   const apr_array_header_t *config_options,
995                                   const char *prefix,
996                                   const char *argument_name)
997 {
998   int i;
999
1000   for (i = 0; i < config_options->nelts; i++)
1001    {
1002      svn_config_t *cfg;
1003      svn_cmdline__config_argument_t *arg =
1004                           APR_ARRAY_IDX(config_options, i,
1005                                         svn_cmdline__config_argument_t *);
1006
1007      cfg = svn_hash_gets(config, arg->file);
1008
1009      if (cfg)
1010        {
1011          svn_config_set(cfg, arg->section, arg->option, arg->value);
1012        }
1013      else
1014        {
1015          svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1016              _("Unrecognized file in argument of %s"), argument_name);
1017
1018          svn_handle_warning2(stderr, err, prefix);
1019          svn_error_clear(err);
1020        }
1021     }
1022
1023   return SVN_NO_ERROR;
1024 }
1025
1026 /* Return a copy, allocated in POOL, of the next line of text from *STR
1027  * up to and including a CR and/or an LF. Change *STR to point to the
1028  * remainder of the string after the returned part. If there are no
1029  * characters to be returned, return NULL; never return an empty string.
1030  */
1031 static const char *
1032 next_line(const char **str, apr_pool_t *pool)
1033 {
1034   const char *start = *str;
1035   const char *p = *str;
1036
1037   /* n.b. Throughout this fn, we never read any character after a '\0'. */
1038   /* Skip over all non-EOL characters, if any. */
1039   while (*p != '\r' && *p != '\n' && *p != '\0')
1040     p++;
1041   /* Skip over \r\n or \n\r or \r or \n, if any. */
1042   if (*p == '\r' || *p == '\n')
1043     {
1044       char c = *p++;
1045
1046       if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r'))
1047         p++;
1048     }
1049
1050   /* Now p points after at most one '\n' and/or '\r'. */
1051   *str = p;
1052
1053   if (p == start)
1054     return NULL;
1055
1056   return svn_string_ncreate(start, p - start, pool)->data;
1057 }
1058
1059 const char *
1060 svn_cmdline__indent_string(const char *str,
1061                            const char *indent,
1062                            apr_pool_t *pool)
1063 {
1064   svn_stringbuf_t *out = svn_stringbuf_create_empty(pool);
1065   const char *line;
1066
1067   while ((line = next_line(&str, pool)))
1068     {
1069       svn_stringbuf_appendcstr(out, indent);
1070       svn_stringbuf_appendcstr(out, line);
1071     }
1072   return out->data;
1073 }
1074
1075 svn_error_t *
1076 svn_cmdline__print_prop_hash(svn_stream_t *out,
1077                              apr_hash_t *prop_hash,
1078                              svn_boolean_t names_only,
1079                              apr_pool_t *pool)
1080 {
1081   apr_array_header_t *sorted_props;
1082   int i;
1083
1084   sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
1085                                 pool);
1086   for (i = 0; i < sorted_props->nelts; i++)
1087     {
1088       svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
1089       const char *pname = item.key;
1090       svn_string_t *propval = item.value;
1091       const char *pname_stdout;
1092
1093       if (svn_prop_needs_translation(pname))
1094         SVN_ERR(svn_subst_detranslate_string(&propval, propval,
1095                                              TRUE, pool));
1096
1097       SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
1098
1099       if (out)
1100         {
1101           pname_stdout = apr_psprintf(pool, "  %s\n", pname_stdout);
1102           SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout,
1103                                               APR_EOL_STR,  /* 'native' eol */
1104                                               FALSE, /* no repair */
1105                                               NULL,  /* no keywords */
1106                                               FALSE, /* no expansion */
1107                                               pool));
1108
1109           SVN_ERR(svn_stream_puts(out, pname_stdout));
1110         }
1111       else
1112         {
1113           /* ### We leave these printfs for now, since if propval wasn't
1114              translated above, we don't know anything about its encoding.
1115              In fact, it might be binary data... */
1116           printf("  %s\n", pname_stdout);
1117         }
1118
1119       if (!names_only)
1120         {
1121           /* Add an extra newline to the value before indenting, so that
1122            * every line of output has the indentation whether the value
1123            * already ended in a newline or not. */
1124           const char *newval = apr_psprintf(pool, "%s\n", propval->data);
1125           const char *indented_newval = svn_cmdline__indent_string(newval,
1126                                                                    "    ",
1127                                                                    pool);
1128           if (out)
1129             {
1130               SVN_ERR(svn_stream_puts(out, indented_newval));
1131             }
1132           else
1133             {
1134               printf("%s", indented_newval);
1135             }
1136         }
1137     }
1138
1139   return SVN_NO_ERROR;
1140 }
1141
1142 svn_error_t *
1143 svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr,
1144                                  apr_hash_t *prop_hash,
1145                                  svn_boolean_t names_only,
1146                                  svn_boolean_t inherited_props,
1147                                  apr_pool_t *pool)
1148 {
1149   apr_array_header_t *sorted_props;
1150   int i;
1151
1152   if (*outstr == NULL)
1153     *outstr = svn_stringbuf_create_empty(pool);
1154
1155   sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
1156                                 pool);
1157   for (i = 0; i < sorted_props->nelts; i++)
1158     {
1159       svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
1160       const char *pname = item.key;
1161       svn_string_t *propval = item.value;
1162
1163       if (names_only)
1164         {
1165           svn_xml_make_open_tag(
1166             outstr, pool, svn_xml_self_closing,
1167             inherited_props ? "inherited_property" : "property",
1168             "name", pname, SVN_VA_NULL);
1169         }
1170       else
1171         {
1172           const char *pname_out;
1173
1174           if (svn_prop_needs_translation(pname))
1175             SVN_ERR(svn_subst_detranslate_string(&propval, propval,
1176                                                  TRUE, pool));
1177
1178           SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
1179
1180           svn_cmdline__print_xml_prop(outstr, pname_out, propval,
1181                                       inherited_props, pool);
1182         }
1183     }
1184
1185     return SVN_NO_ERROR;
1186 }
1187
1188 svn_boolean_t
1189 svn_cmdline__stdin_is_a_terminal(void)
1190 {
1191 #ifdef WIN32
1192   return (_isatty(STDIN_FILENO) != 0);
1193 #else
1194   return (isatty(STDIN_FILENO) != 0);
1195 #endif
1196 }
1197
1198 svn_boolean_t
1199 svn_cmdline__stdout_is_a_terminal(void)
1200 {
1201 #ifdef WIN32
1202   return (_isatty(STDOUT_FILENO) != 0);
1203 #else
1204   return (isatty(STDOUT_FILENO) != 0);
1205 #endif
1206 }
1207
1208 svn_boolean_t
1209 svn_cmdline__stderr_is_a_terminal(void)
1210 {
1211 #ifdef WIN32
1212   return (_isatty(STDERR_FILENO) != 0);
1213 #else
1214   return (isatty(STDERR_FILENO) != 0);
1215 #endif
1216 }
1217
1218 svn_boolean_t
1219 svn_cmdline__be_interactive(svn_boolean_t non_interactive,
1220                             svn_boolean_t force_interactive)
1221 {
1222   /* If neither --non-interactive nor --force-interactive was passed,
1223    * be interactive if stdin is a terminal.
1224    * If --force-interactive was passed, always be interactive. */
1225   if (!force_interactive && !non_interactive)
1226     {
1227       return svn_cmdline__stdin_is_a_terminal();
1228     }
1229   else if (force_interactive)
1230     return TRUE;
1231
1232   return !non_interactive;
1233 }
1234
1235
1236 /* Helper for the next two functions.  Set *EDITOR to some path to an
1237    editor binary.  Sources to search include: the EDITOR_CMD argument
1238    (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
1239    is not NULL), $VISUAL, $EDITOR.  Return
1240    SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
1241 static svn_error_t *
1242 find_editor_binary(const char **editor,
1243                    const char *editor_cmd,
1244                    apr_hash_t *config)
1245 {
1246   const char *e;
1247   struct svn_config_t *cfg;
1248
1249   /* Use the editor specified on the command line via --editor-cmd, if any. */
1250   e = editor_cmd;
1251
1252   /* Otherwise look for the Subversion-specific environment variable. */
1253   if (! e)
1254     e = getenv("SVN_EDITOR");
1255
1256   /* If not found then fall back on the config file. */
1257   if (! e)
1258     {
1259       cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
1260       svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS,
1261                      SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
1262     }
1263
1264   /* If not found yet then try general purpose environment variables. */
1265   if (! e)
1266     e = getenv("VISUAL");
1267   if (! e)
1268     e = getenv("EDITOR");
1269
1270 #ifdef SVN_CLIENT_EDITOR
1271   /* If still not found then fall back on the hard-coded default. */
1272   if (! e)
1273     e = SVN_CLIENT_EDITOR;
1274 #endif
1275
1276   /* Error if there is no editor specified */
1277   if (e)
1278     {
1279       const char *c;
1280
1281       for (c = e; *c; c++)
1282         if (!svn_ctype_isspace(*c))
1283           break;
1284
1285       if (! *c)
1286         return svn_error_create
1287           (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1288            _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
1289              "'editor-cmd' run-time configuration option is empty or "
1290              "consists solely of whitespace. Expected a shell command."));
1291     }
1292   else
1293     return svn_error_create
1294       (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1295        _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
1296          "set, and no 'editor-cmd' run-time configuration option was found"));
1297
1298   *editor = e;
1299   return SVN_NO_ERROR;
1300 }
1301
1302
1303 svn_error_t *
1304 svn_cmdline__edit_file_externally(const char *path,
1305                                   const char *editor_cmd,
1306                                   apr_hash_t *config,
1307                                   apr_pool_t *pool)
1308 {
1309   const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
1310   char *old_cwd;
1311   int sys_err;
1312   apr_status_t apr_err;
1313
1314   svn_dirent_split(&base_dir, &file_name, path, pool);
1315
1316   SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1317
1318   apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1319   if (apr_err)
1320     return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1321
1322   /* APR doesn't like "" directories */
1323   if (base_dir[0] == '\0')
1324     base_dir_apr = ".";
1325   else
1326     SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1327
1328   apr_err = apr_filepath_set(base_dir_apr, pool);
1329   if (apr_err)
1330     return svn_error_wrap_apr
1331       (apr_err, _("Can't change working directory to '%s'"), base_dir);
1332
1333   cmd = apr_psprintf(pool, "%s %s", editor, file_name);
1334   sys_err = system(cmd);
1335
1336   apr_err = apr_filepath_set(old_cwd, pool);
1337   if (apr_err)
1338     svn_handle_error2(svn_error_wrap_apr
1339                       (apr_err, _("Can't restore working directory")),
1340                       stderr, TRUE /* fatal */, "svn: ");
1341
1342   if (sys_err)
1343     /* Extracting any meaning from sys_err is platform specific, so just
1344        use the raw value. */
1345     return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1346                              _("system('%s') returned %d"), cmd, sys_err);
1347
1348   return SVN_NO_ERROR;
1349 }
1350
1351
1352 svn_error_t *
1353 svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */,
1354                                     const char **tmpfile_left /* UTF-8! */,
1355                                     const char *editor_cmd,
1356                                     const char *base_dir /* UTF-8! */,
1357                                     const svn_string_t *contents /* UTF-8! */,
1358                                     const char *filename,
1359                                     apr_hash_t *config,
1360                                     svn_boolean_t as_text,
1361                                     const char *encoding,
1362                                     apr_pool_t *pool)
1363 {
1364   const char *editor;
1365   const char *cmd;
1366   apr_file_t *tmp_file;
1367   const char *tmpfile_name;
1368   const char *tmpfile_native;
1369   const char *base_dir_apr;
1370   svn_string_t *translated_contents;
1371   apr_status_t apr_err;
1372   apr_size_t written;
1373   apr_finfo_t finfo_before, finfo_after;
1374   svn_error_t *err = SVN_NO_ERROR;
1375   char *old_cwd;
1376   int sys_err;
1377   svn_boolean_t remove_file = TRUE;
1378
1379   SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1380
1381   /* Convert file contents from UTF-8/LF if desired. */
1382   if (as_text)
1383     {
1384       const char *translated;
1385       SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
1386                                            APR_EOL_STR, FALSE,
1387                                            NULL, FALSE, pool));
1388       translated_contents = svn_string_create_empty(pool);
1389       if (encoding)
1390         SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
1391                                               translated, encoding, pool));
1392       else
1393         SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
1394                                           translated, pool));
1395       translated_contents->len = strlen(translated_contents->data);
1396     }
1397   else
1398     translated_contents = svn_string_dup(contents, pool);
1399
1400   /* Move to BASE_DIR to avoid getting characters that need quoting
1401      into tmpfile_name */
1402   apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1403   if (apr_err)
1404     return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1405
1406   /* APR doesn't like "" directories */
1407   if (base_dir[0] == '\0')
1408     base_dir_apr = ".";
1409   else
1410     SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1411   apr_err = apr_filepath_set(base_dir_apr, pool);
1412   if (apr_err)
1413     {
1414       return svn_error_wrap_apr
1415         (apr_err, _("Can't change working directory to '%s'"), base_dir);
1416     }
1417
1418   /*** From here on, any problems that occur require us to cd back!! ***/
1419
1420   /* Ask the working copy for a temporary file named FILENAME-something. */
1421   err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1422                                    "" /* dirpath */,
1423                                    filename,
1424                                    ".tmp",
1425                                    svn_io_file_del_none, pool, pool);
1426
1427   if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS))
1428     {
1429       const char *temp_dir_apr;
1430
1431       svn_error_clear(err);
1432
1433       SVN_ERR(svn_io_temp_dir(&base_dir, pool));
1434
1435       SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool));
1436       apr_err = apr_filepath_set(temp_dir_apr, pool);
1437       if (apr_err)
1438         {
1439           return svn_error_wrap_apr
1440             (apr_err, _("Can't change working directory to '%s'"), base_dir);
1441         }
1442
1443       err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1444                                        "" /* dirpath */,
1445                                        filename,
1446                                        ".tmp",
1447                                        svn_io_file_del_none, pool, pool);
1448     }
1449
1450   if (err)
1451     goto cleanup2;
1452
1453   /*** From here on, any problems that occur require us to cleanup
1454        the file we just created!! ***/
1455
1456   /* Dump initial CONTENTS to TMP_FILE. */
1457   err = svn_io_file_write_full(tmp_file, translated_contents->data,
1458                                translated_contents->len, &written,
1459                                pool);
1460
1461   err = svn_error_compose_create(err, svn_io_file_close(tmp_file, pool));
1462
1463   /* Make sure the whole CONTENTS were written, else return an error. */
1464   if (err)
1465     goto cleanup;
1466
1467   /* Get information about the temporary file before the user has
1468      been allowed to edit its contents. */
1469   err = svn_io_stat(&finfo_before, tmpfile_name, APR_FINFO_MTIME, pool);
1470   if (err)
1471     goto cleanup;
1472
1473   /* Backdate the file a little bit in case the editor is very fast
1474      and doesn't change the size.  (Use two seconds, since some
1475      filesystems have coarse granularity.)  It's OK if this call
1476      fails, so we don't check its return value.*/
1477   err = svn_io_set_file_affected_time(finfo_before.mtime
1478                                               - apr_time_from_sec(2),
1479                                       tmpfile_name, pool);
1480   svn_error_clear(err);
1481
1482   /* Stat it again to get the mtime we actually set. */
1483   err = svn_io_stat(&finfo_before, tmpfile_name,
1484                     APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1485   if (err)
1486     goto cleanup;
1487
1488   /* Prepare the editor command line.  */
1489   err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
1490   if (err)
1491     goto cleanup;
1492   cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
1493
1494   /* If the caller wants us to leave the file around, return the path
1495      of the file we'll use, and make a note not to destroy it.  */
1496   if (tmpfile_left)
1497     {
1498       *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool);
1499       remove_file = FALSE;
1500     }
1501
1502   /* Now, run the editor command line.  */
1503   sys_err = system(cmd);
1504   if (sys_err != 0)
1505     {
1506       /* Extracting any meaning from sys_err is platform specific, so just
1507          use the raw value. */
1508       err =  svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1509                                _("system('%s') returned %d"), cmd, sys_err);
1510       goto cleanup;
1511     }
1512
1513   /* Get information about the temporary file after the assumed editing. */
1514   err = svn_io_stat(&finfo_after, tmpfile_name,
1515                     APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1516   if (err)
1517     goto cleanup;
1518
1519   /* If the file looks changed... */
1520   if ((finfo_before.mtime != finfo_after.mtime) ||
1521       (finfo_before.size != finfo_after.size))
1522     {
1523       svn_stringbuf_t *edited_contents_s;
1524       err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool);
1525       if (err)
1526         goto cleanup;
1527
1528       *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s);
1529
1530       /* Translate back to UTF8/LF if desired. */
1531       if (as_text)
1532         {
1533           err = svn_subst_translate_string2(edited_contents, NULL, NULL,
1534                                             *edited_contents, encoding, FALSE,
1535                                             pool, pool);
1536           if (err)
1537             {
1538               err = svn_error_quick_wrap
1539                 (err,
1540                  _("Error normalizing edited contents to internal format"));
1541               goto cleanup;
1542             }
1543         }
1544     }
1545   else
1546     {
1547       /* No edits seem to have been made */
1548       *edited_contents = NULL;
1549     }
1550
1551  cleanup:
1552   if (remove_file)
1553     {
1554       /* Remove the file from disk.  */
1555       err = svn_error_compose_create(
1556               err,
1557               svn_io_remove_file2(tmpfile_name, FALSE, pool));
1558     }
1559
1560  cleanup2:
1561   /* If we against all probability can't cd back, all further relative
1562      file references would be screwed up, so we have to abort. */
1563   apr_err = apr_filepath_set(old_cwd, pool);
1564   if (apr_err)
1565     {
1566       svn_handle_error2(svn_error_wrap_apr
1567                         (apr_err, _("Can't restore working directory")),
1568                         stderr, TRUE /* fatal */, "svn: ");
1569     }
1570
1571   return svn_error_trace(err);
1572 }
1573
1574 svn_error_t *
1575 svn_cmdline__parse_trust_options(
1576                         svn_boolean_t *trust_server_cert_unknown_ca,
1577                         svn_boolean_t *trust_server_cert_cn_mismatch,
1578                         svn_boolean_t *trust_server_cert_expired,
1579                         svn_boolean_t *trust_server_cert_not_yet_valid,
1580                         svn_boolean_t *trust_server_cert_other_failure,
1581                         const char *opt_arg,
1582                         apr_pool_t *scratch_pool)
1583 {
1584   apr_array_header_t *failures;
1585   int i;
1586
1587   *trust_server_cert_unknown_ca = FALSE;
1588   *trust_server_cert_cn_mismatch = FALSE;
1589   *trust_server_cert_expired = FALSE;
1590   *trust_server_cert_not_yet_valid = FALSE;
1591   *trust_server_cert_other_failure = FALSE;
1592
1593   failures = svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, scratch_pool);
1594
1595   for (i = 0; i < failures->nelts; i++)
1596     {
1597       const char *value = APR_ARRAY_IDX(failures, i, const char *);
1598       if (!strcmp(value, "unknown-ca"))
1599         *trust_server_cert_unknown_ca = TRUE;
1600       else if (!strcmp(value, "cn-mismatch"))
1601         *trust_server_cert_cn_mismatch = TRUE;
1602       else if (!strcmp(value, "expired"))
1603         *trust_server_cert_expired = TRUE;
1604       else if (!strcmp(value, "not-yet-valid"))
1605         *trust_server_cert_not_yet_valid = TRUE;
1606       else if (!strcmp(value, "other"))
1607         *trust_server_cert_other_failure = TRUE;
1608       else
1609         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1610                                   _("Unknown value '%s' for %s.\n"
1611                                     "Supported values: %s"),
1612                                   value,
1613                                   "--trust-server-cert-failures",
1614                                   "unknown-ca, cn-mismatch, expired, "
1615                                   "not-yet-valid, other");
1616     }
1617
1618   return SVN_NO_ERROR;
1619 }
1620
1621 /* Flags to see if we've been cancelled by the client or not. */
1622 static volatile sig_atomic_t cancelled = FALSE;
1623 static volatile sig_atomic_t signum_cancelled = 0;
1624
1625 /* The signals we handle. */
1626 static int signal_map [] = {
1627   SIGINT
1628 #ifdef SIGBREAK
1629   /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
1630   , SIGBREAK
1631 #endif
1632 #ifdef SIGHUP
1633   , SIGHUP
1634 #endif
1635 #ifdef SIGTERM
1636   , SIGTERM
1637 #endif
1638 };
1639
1640 /* A signal handler to support cancellation. */
1641 static void
1642 signal_handler(int signum)
1643 {
1644   int i;
1645
1646   apr_signal(signum, SIG_IGN);
1647   cancelled = TRUE;
1648   for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i)
1649     if (signal_map[i] == signum)
1650       {
1651         signum_cancelled = i + 1;
1652         break;
1653       }
1654 }
1655
1656 /* An svn_cancel_func_t callback. */
1657 static svn_error_t *
1658 check_cancel(void *baton)
1659 {
1660   /* Cancel baton should be always NULL in command line client. */
1661   SVN_ERR_ASSERT(baton == NULL);
1662   if (cancelled)
1663     return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
1664   else
1665     return SVN_NO_ERROR;
1666 }
1667
1668 svn_cancel_func_t
1669 svn_cmdline__setup_cancellation_handler(void)
1670 {
1671   int i;
1672
1673   for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i)
1674     apr_signal(signal_map[i], signal_handler);
1675
1676 #ifdef SIGPIPE
1677   /* Disable SIGPIPE generation for the platforms that have it. */
1678   apr_signal(SIGPIPE, SIG_IGN);
1679 #endif
1680
1681 #ifdef SIGXFSZ
1682   /* Disable SIGXFSZ generation for the platforms that have it, otherwise
1683    * working with large files when compiled against an APR that doesn't have
1684    * large file support will crash the program, which is uncool. */
1685   apr_signal(SIGXFSZ, SIG_IGN);
1686 #endif
1687
1688   return check_cancel;
1689 }
1690
1691 void
1692 svn_cmdline__disable_cancellation_handler(void)
1693 {
1694   int i;
1695
1696   for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i)
1697     apr_signal(signal_map[i], SIG_DFL);
1698 }
1699
1700 void
1701 svn_cmdline__cancellation_exit(void)
1702 {
1703   int signum = 0;
1704
1705   if (cancelled && signum_cancelled)
1706     signum = signal_map[signum_cancelled - 1];
1707   if (signum)
1708     {
1709 #ifndef WIN32
1710       apr_signal(signum, SIG_DFL);
1711       /* No APR support for getpid() so cannot use apr_proc_kill(). */
1712       kill(getpid(), signum);
1713 #endif
1714     }
1715 }