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