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