]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_subr/cmdline.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.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 (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 (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 server-cert, client-cert, and client-cert-password providers. */
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   svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
518   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
519   svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
520   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
521
522   if (!non_interactive)
523     {
524       /* This provider doesn't prompt the user in order to get creds;
525          it prompts the user regarding the caching of creds. */
526       svn_auth_get_ssl_client_cert_pw_file_provider2
527         (&provider, svn_cmdline_auth_plaintext_passphrase_prompt,
528          pb, pool);
529     }
530   else
531     {
532       svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL,
533                                                      pool);
534     }
535   APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
536
537   if (!non_interactive)
538     {
539       svn_boolean_t ssl_client_cert_file_prompt;
540
541       SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt,
542                                   SVN_CONFIG_SECTION_AUTH,
543                                   SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT,
544                                   FALSE));
545
546       /* Two basic prompt providers: username/password, and just username. */
547       svn_auth_get_simple_prompt_provider(&provider,
548                                           svn_cmdline_auth_simple_prompt,
549                                           pb,
550                                           2, /* retry limit */
551                                           pool);
552       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
553
554       svn_auth_get_username_prompt_provider
555         (&provider, svn_cmdline_auth_username_prompt, pb,
556          2, /* retry limit */ pool);
557       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
558
559       /* SSL prompt providers: server-certs and client-cert-passphrases.  */
560       svn_auth_get_ssl_server_trust_prompt_provider
561         (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool);
562       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
563
564       svn_auth_get_ssl_client_cert_pw_prompt_provider
565         (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool);
566       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
567
568       /* If configuration allows, add a provider for client-cert path
569          prompting, too. */
570       if (ssl_client_cert_file_prompt)
571         {
572           svn_auth_get_ssl_client_cert_prompt_provider
573             (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
574           APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
575         }
576     }
577   else if (trust_server_cert)
578     {
579       /* Remember, only register this provider if non_interactive. */
580       svn_auth_get_ssl_server_trust_prompt_provider
581         (&provider, ssl_trust_unknown_server_cert, NULL, pool);
582       APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
583     }
584
585   /* Build an authentication baton to give to libsvn_client. */
586   svn_auth_open(ab, providers, pool);
587
588   /* Place any default --username or --password credentials into the
589      auth_baton's run-time parameter hash. */
590   if (auth_username)
591     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
592                            auth_username);
593   if (auth_password)
594     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
595                            auth_password);
596
597   /* Same with the --non-interactive option. */
598   if (non_interactive)
599     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, "");
600
601   if (config_dir)
602     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR,
603                            config_dir);
604
605   /* Determine whether storing passwords in any form is allowed.
606    * This is the deprecated location for this option, the new
607    * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
608    * override the value we set here. */
609   SVN_ERR(svn_config_get_bool(cfg, &store_password_val,
610                               SVN_CONFIG_SECTION_AUTH,
611                               SVN_CONFIG_OPTION_STORE_PASSWORDS,
612                               SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS));
613
614   if (! store_password_val)
615     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, "");
616
617   /* Determine whether we are allowed to write to the auth/ area.
618    * This is the deprecated location for this option, the new
619    * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
620    * override the value we set here. */
621   SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val,
622                               SVN_CONFIG_SECTION_AUTH,
623                               SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
624                               SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS));
625
626   if (no_auth_cache || ! store_auth_creds_val)
627     svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
628
629 #ifdef SVN_HAVE_GNOME_KEYRING
630   svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC,
631                          &svn_cmdline__auth_gnome_keyring_unlock_prompt);
632 #endif /* SVN_HAVE_GNOME_KEYRING */
633
634   return SVN_NO_ERROR;
635 }
636
637 svn_error_t *
638 svn_cmdline__getopt_init(apr_getopt_t **os,
639                          int argc,
640                          const char *argv[],
641                          apr_pool_t *pool)
642 {
643   apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv);
644   if (apr_err)
645     return svn_error_wrap_apr(apr_err,
646                               _("Error initializing command line arguments"));
647   return SVN_NO_ERROR;
648 }
649
650
651 void
652 svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
653                             const char* propname,
654                             svn_string_t *propval,
655                             svn_boolean_t inherited_prop,
656                             apr_pool_t *pool)
657 {
658   const char *xml_safe;
659   const char *encoding = NULL;
660
661   if (*outstr == NULL)
662     *outstr = svn_stringbuf_create_empty(pool);
663
664   if (svn_xml_is_xml_safe(propval->data, propval->len))
665     {
666       svn_stringbuf_t *xml_esc = NULL;
667       svn_xml_escape_cdata_string(&xml_esc, propval, pool);
668       xml_safe = xml_esc->data;
669     }
670   else
671     {
672       const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE,
673                                                                pool);
674       encoding = "base64";
675       xml_safe = base64ed->data;
676     }
677
678   if (encoding)
679     svn_xml_make_open_tag(
680       outstr, pool, svn_xml_protect_pcdata,
681       inherited_prop ? "inherited_property" : "property",
682       "name", propname,
683       "encoding", encoding, NULL);
684   else
685     svn_xml_make_open_tag(
686       outstr, pool, svn_xml_protect_pcdata,
687       inherited_prop ? "inherited_property" : "property",
688       "name", propname, NULL);
689
690   svn_stringbuf_appendcstr(*outstr, xml_safe);
691
692   svn_xml_make_close_tag(
693     outstr, pool,
694     inherited_prop ? "inherited_property" : "property");
695
696   return;
697 }
698
699 svn_error_t *
700 svn_cmdline__parse_config_option(apr_array_header_t *config_options,
701                                  const char *opt_arg,
702                                  apr_pool_t *pool)
703 {
704   svn_cmdline__config_argument_t *config_option;
705   const char *first_colon, *second_colon, *equals_sign;
706   apr_size_t len = strlen(opt_arg);
707   if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg))
708     {
709       if ((second_colon = strchr(first_colon + 1, ':')) &&
710           (second_colon != first_colon + 1))
711         {
712           if ((equals_sign = strchr(second_colon + 1, '=')) &&
713               (equals_sign != second_colon + 1))
714             {
715               config_option = apr_pcalloc(pool, sizeof(*config_option));
716               config_option->file = apr_pstrndup(pool, opt_arg,
717                                                  first_colon - opt_arg);
718               config_option->section = apr_pstrndup(pool, first_colon + 1,
719                                                     second_colon - first_colon - 1);
720               config_option->option = apr_pstrndup(pool, second_colon + 1,
721                                                    equals_sign - second_colon - 1);
722
723               if (! (strchr(config_option->option, ':')))
724                 {
725                   config_option->value = apr_pstrndup(pool, equals_sign + 1,
726                                                       opt_arg + len - equals_sign - 1);
727                   APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *)
728                                        = config_option;
729                   return SVN_NO_ERROR;
730                 }
731             }
732         }
733     }
734   return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
735                           _("Invalid syntax of argument of --config-option"));
736 }
737
738 svn_error_t *
739 svn_cmdline__apply_config_options(apr_hash_t *config,
740                                   const apr_array_header_t *config_options,
741                                   const char *prefix,
742                                   const char *argument_name)
743 {
744   int i;
745
746   for (i = 0; i < config_options->nelts; i++)
747    {
748      svn_config_t *cfg;
749      svn_cmdline__config_argument_t *arg =
750                           APR_ARRAY_IDX(config_options, i,
751                                         svn_cmdline__config_argument_t *);
752
753      cfg = svn_hash_gets(config, arg->file);
754
755      if (cfg)
756        {
757          svn_config_set(cfg, arg->section, arg->option, arg->value);
758        }
759      else
760        {
761          svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
762              _("Unrecognized file in argument of %s"), argument_name);
763
764          svn_handle_warning2(stderr, err, prefix);
765          svn_error_clear(err);
766        }
767     }
768
769   return SVN_NO_ERROR;
770 }
771
772 /* Return a copy, allocated in POOL, of the next line of text from *STR
773  * up to and including a CR and/or an LF. Change *STR to point to the
774  * remainder of the string after the returned part. If there are no
775  * characters to be returned, return NULL; never return an empty string.
776  */
777 static const char *
778 next_line(const char **str, apr_pool_t *pool)
779 {
780   const char *start = *str;
781   const char *p = *str;
782
783   /* n.b. Throughout this fn, we never read any character after a '\0'. */
784   /* Skip over all non-EOL characters, if any. */
785   while (*p != '\r' && *p != '\n' && *p != '\0')
786     p++;
787   /* Skip over \r\n or \n\r or \r or \n, if any. */
788   if (*p == '\r' || *p == '\n')
789     {
790       char c = *p++;
791
792       if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r'))
793         p++;
794     }
795
796   /* Now p points after at most one '\n' and/or '\r'. */
797   *str = p;
798
799   if (p == start)
800     return NULL;
801
802   return svn_string_ncreate(start, p - start, pool)->data;
803 }
804
805 const char *
806 svn_cmdline__indent_string(const char *str,
807                            const char *indent,
808                            apr_pool_t *pool)
809 {
810   svn_stringbuf_t *out = svn_stringbuf_create_empty(pool);
811   const char *line;
812
813   while ((line = next_line(&str, pool)))
814     {
815       svn_stringbuf_appendcstr(out, indent);
816       svn_stringbuf_appendcstr(out, line);
817     }
818   return out->data;
819 }
820
821 svn_error_t *
822 svn_cmdline__print_prop_hash(svn_stream_t *out,
823                              apr_hash_t *prop_hash,
824                              svn_boolean_t names_only,
825                              apr_pool_t *pool)
826 {
827   apr_array_header_t *sorted_props;
828   int i;
829
830   sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
831                                 pool);
832   for (i = 0; i < sorted_props->nelts; i++)
833     {
834       svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
835       const char *pname = item.key;
836       svn_string_t *propval = item.value;
837       const char *pname_stdout;
838
839       if (svn_prop_needs_translation(pname))
840         SVN_ERR(svn_subst_detranslate_string(&propval, propval,
841                                              TRUE, pool));
842
843       SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
844
845       if (out)
846         {
847           pname_stdout = apr_psprintf(pool, "  %s\n", pname_stdout);
848           SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout,
849                                               APR_EOL_STR,  /* 'native' eol */
850                                               FALSE, /* no repair */
851                                               NULL,  /* no keywords */
852                                               FALSE, /* no expansion */
853                                               pool));
854
855           SVN_ERR(svn_stream_puts(out, pname_stdout));
856         }
857       else
858         {
859           /* ### We leave these printfs for now, since if propval wasn't
860              translated above, we don't know anything about its encoding.
861              In fact, it might be binary data... */
862           printf("  %s\n", pname_stdout);
863         }
864
865       if (!names_only)
866         {
867           /* Add an extra newline to the value before indenting, so that
868            * every line of output has the indentation whether the value
869            * already ended in a newline or not. */
870           const char *newval = apr_psprintf(pool, "%s\n", propval->data);
871           const char *indented_newval = svn_cmdline__indent_string(newval,
872                                                                    "    ",
873                                                                    pool);
874           if (out)
875             {
876               SVN_ERR(svn_stream_puts(out, indented_newval));
877             }
878           else
879             {
880               printf("%s", indented_newval);
881             }
882         }
883     }
884
885   return SVN_NO_ERROR;
886 }
887
888 svn_error_t *
889 svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr,
890                                  apr_hash_t *prop_hash,
891                                  svn_boolean_t names_only,
892                                  svn_boolean_t inherited_props,
893                                  apr_pool_t *pool)
894 {
895   apr_array_header_t *sorted_props;
896   int i;
897
898   if (*outstr == NULL)
899     *outstr = svn_stringbuf_create_empty(pool);
900
901   sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
902                                 pool);
903   for (i = 0; i < sorted_props->nelts; i++)
904     {
905       svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
906       const char *pname = item.key;
907       svn_string_t *propval = item.value;
908
909       if (names_only)
910         {
911           svn_xml_make_open_tag(
912             outstr, pool, svn_xml_self_closing,
913             inherited_props ? "inherited_property" : "property",
914             "name", pname, NULL);
915         }
916       else
917         {
918           const char *pname_out;
919
920           if (svn_prop_needs_translation(pname))
921             SVN_ERR(svn_subst_detranslate_string(&propval, propval,
922                                                  TRUE, pool));
923
924           SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
925
926           svn_cmdline__print_xml_prop(outstr, pname_out, propval,
927                                       inherited_props, pool);
928         }
929     }
930
931     return SVN_NO_ERROR;
932 }
933
934 svn_boolean_t
935 svn_cmdline__be_interactive(svn_boolean_t non_interactive,
936                             svn_boolean_t force_interactive)
937 {
938   /* If neither --non-interactive nor --force-interactive was passed,
939    * be interactive if stdin is a terminal.
940    * If --force-interactive was passed, always be interactive. */
941   if (!force_interactive && !non_interactive)
942     {
943 #ifdef WIN32
944       return (_isatty(STDIN_FILENO) != 0);
945 #else
946       return (isatty(STDIN_FILENO) != 0);
947 #endif
948     }
949   else if (force_interactive)
950     return TRUE;
951
952   return !non_interactive;
953 }
954
955
956 /* Helper for the next two functions.  Set *EDITOR to some path to an
957    editor binary.  Sources to search include: the EDITOR_CMD argument
958    (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
959    is not NULL), $VISUAL, $EDITOR.  Return
960    SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
961 static svn_error_t *
962 find_editor_binary(const char **editor,
963                    const char *editor_cmd,
964                    apr_hash_t *config)
965 {
966   const char *e;
967   struct svn_config_t *cfg;
968
969   /* Use the editor specified on the command line via --editor-cmd, if any. */
970   e = editor_cmd;
971
972   /* Otherwise look for the Subversion-specific environment variable. */
973   if (! e)
974     e = getenv("SVN_EDITOR");
975
976   /* If not found then fall back on the config file. */
977   if (! e)
978     {
979       cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
980       svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS,
981                      SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
982     }
983
984   /* If not found yet then try general purpose environment variables. */
985   if (! e)
986     e = getenv("VISUAL");
987   if (! e)
988     e = getenv("EDITOR");
989
990 #ifdef SVN_CLIENT_EDITOR
991   /* If still not found then fall back on the hard-coded default. */
992   if (! e)
993     e = SVN_CLIENT_EDITOR;
994 #endif
995
996   /* Error if there is no editor specified */
997   if (e)
998     {
999       const char *c;
1000
1001       for (c = e; *c; c++)
1002         if (!svn_ctype_isspace(*c))
1003           break;
1004
1005       if (! *c)
1006         return svn_error_create
1007           (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1008            _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
1009              "'editor-cmd' run-time configuration option is empty or "
1010              "consists solely of whitespace. Expected a shell command."));
1011     }
1012   else
1013     return svn_error_create
1014       (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1015        _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
1016          "set, and no 'editor-cmd' run-time configuration option was found"));
1017
1018   *editor = e;
1019   return SVN_NO_ERROR;
1020 }
1021
1022
1023 svn_error_t *
1024 svn_cmdline__edit_file_externally(const char *path,
1025                                   const char *editor_cmd,
1026                                   apr_hash_t *config,
1027                                   apr_pool_t *pool)
1028 {
1029   const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
1030   char *old_cwd;
1031   int sys_err;
1032   apr_status_t apr_err;
1033
1034   svn_dirent_split(&base_dir, &file_name, path, pool);
1035
1036   SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1037
1038   apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1039   if (apr_err)
1040     return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1041
1042   /* APR doesn't like "" directories */
1043   if (base_dir[0] == '\0')
1044     base_dir_apr = ".";
1045   else
1046     SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1047
1048   apr_err = apr_filepath_set(base_dir_apr, pool);
1049   if (apr_err)
1050     return svn_error_wrap_apr
1051       (apr_err, _("Can't change working directory to '%s'"), base_dir);
1052
1053   cmd = apr_psprintf(pool, "%s %s", editor, file_name);
1054   sys_err = system(cmd);
1055
1056   apr_err = apr_filepath_set(old_cwd, pool);
1057   if (apr_err)
1058     svn_handle_error2(svn_error_wrap_apr
1059                       (apr_err, _("Can't restore working directory")),
1060                       stderr, TRUE /* fatal */, "svn: ");
1061
1062   if (sys_err)
1063     /* Extracting any meaning from sys_err is platform specific, so just
1064        use the raw value. */
1065     return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1066                              _("system('%s') returned %d"), cmd, sys_err);
1067
1068   return SVN_NO_ERROR;
1069 }
1070
1071
1072 svn_error_t *
1073 svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */,
1074                                     const char **tmpfile_left /* UTF-8! */,
1075                                     const char *editor_cmd,
1076                                     const char *base_dir /* UTF-8! */,
1077                                     const svn_string_t *contents /* UTF-8! */,
1078                                     const char *filename,
1079                                     apr_hash_t *config,
1080                                     svn_boolean_t as_text,
1081                                     const char *encoding,
1082                                     apr_pool_t *pool)
1083 {
1084   const char *editor;
1085   const char *cmd;
1086   apr_file_t *tmp_file;
1087   const char *tmpfile_name;
1088   const char *tmpfile_native;
1089   const char *tmpfile_apr, *base_dir_apr;
1090   svn_string_t *translated_contents;
1091   apr_status_t apr_err, apr_err2;
1092   apr_size_t written;
1093   apr_finfo_t finfo_before, finfo_after;
1094   svn_error_t *err = SVN_NO_ERROR, *err2;
1095   char *old_cwd;
1096   int sys_err;
1097   svn_boolean_t remove_file = TRUE;
1098
1099   SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1100
1101   /* Convert file contents from UTF-8/LF if desired. */
1102   if (as_text)
1103     {
1104       const char *translated;
1105       SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
1106                                            APR_EOL_STR, FALSE,
1107                                            NULL, FALSE, pool));
1108       translated_contents = svn_string_create_empty(pool);
1109       if (encoding)
1110         SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
1111                                               translated, encoding, pool));
1112       else
1113         SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
1114                                           translated, pool));
1115       translated_contents->len = strlen(translated_contents->data);
1116     }
1117   else
1118     translated_contents = svn_string_dup(contents, pool);
1119
1120   /* Move to BASE_DIR to avoid getting characters that need quoting
1121      into tmpfile_name */
1122   apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1123   if (apr_err)
1124     return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1125
1126   /* APR doesn't like "" directories */
1127   if (base_dir[0] == '\0')
1128     base_dir_apr = ".";
1129   else
1130     SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1131   apr_err = apr_filepath_set(base_dir_apr, pool);
1132   if (apr_err)
1133     {
1134       return svn_error_wrap_apr
1135         (apr_err, _("Can't change working directory to '%s'"), base_dir);
1136     }
1137
1138   /*** From here on, any problems that occur require us to cd back!! ***/
1139
1140   /* Ask the working copy for a temporary file named FILENAME-something. */
1141   err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1142                                    "" /* dirpath */,
1143                                    filename,
1144                                    ".tmp",
1145                                    svn_io_file_del_none, pool, pool);
1146
1147   if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS))
1148     {
1149       const char *temp_dir_apr;
1150
1151       svn_error_clear(err);
1152
1153       SVN_ERR(svn_io_temp_dir(&base_dir, pool));
1154
1155       SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool));
1156       apr_err = apr_filepath_set(temp_dir_apr, pool);
1157       if (apr_err)
1158         {
1159           return svn_error_wrap_apr
1160             (apr_err, _("Can't change working directory to '%s'"), base_dir);
1161         }
1162
1163       err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1164                                        "" /* dirpath */,
1165                                        filename,
1166                                        ".tmp",
1167                                        svn_io_file_del_none, pool, pool);
1168     }
1169
1170   if (err)
1171     goto cleanup2;
1172
1173   /*** From here on, any problems that occur require us to cleanup
1174        the file we just created!! ***/
1175
1176   /* Dump initial CONTENTS to TMP_FILE. */
1177   apr_err = apr_file_write_full(tmp_file, translated_contents->data,
1178                                 translated_contents->len, &written);
1179
1180   apr_err2 = apr_file_close(tmp_file);
1181   if (! apr_err)
1182     apr_err = apr_err2;
1183
1184   /* Make sure the whole CONTENTS were written, else return an error. */
1185   if (apr_err)
1186     {
1187       err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"),
1188                                tmpfile_name);
1189       goto cleanup;
1190     }
1191
1192   err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool);
1193   if (err)
1194     goto cleanup;
1195
1196   /* Get information about the temporary file before the user has
1197      been allowed to edit its contents. */
1198   apr_err = apr_stat(&finfo_before, tmpfile_apr,
1199                      APR_FINFO_MTIME, pool);
1200   if (apr_err)
1201     {
1202       err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1203       goto cleanup;
1204     }
1205
1206   /* Backdate the file a little bit in case the editor is very fast
1207      and doesn't change the size.  (Use two seconds, since some
1208      filesystems have coarse granularity.)  It's OK if this call
1209      fails, so we don't check its return value.*/
1210   apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool);
1211
1212   /* Stat it again to get the mtime we actually set. */
1213   apr_err = apr_stat(&finfo_before, tmpfile_apr,
1214                      APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1215   if (apr_err)
1216     {
1217       err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1218       goto cleanup;
1219     }
1220
1221   /* Prepare the editor command line.  */
1222   err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
1223   if (err)
1224     goto cleanup;
1225   cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
1226
1227   /* If the caller wants us to leave the file around, return the path
1228      of the file we'll use, and make a note not to destroy it.  */
1229   if (tmpfile_left)
1230     {
1231       *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool);
1232       remove_file = FALSE;
1233     }
1234
1235   /* Now, run the editor command line.  */
1236   sys_err = system(cmd);
1237   if (sys_err != 0)
1238     {
1239       /* Extracting any meaning from sys_err is platform specific, so just
1240          use the raw value. */
1241       err =  svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1242                                _("system('%s') returned %d"), cmd, sys_err);
1243       goto cleanup;
1244     }
1245
1246   /* Get information about the temporary file after the assumed editing. */
1247   apr_err = apr_stat(&finfo_after, tmpfile_apr,
1248                      APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1249   if (apr_err)
1250     {
1251       err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1252       goto cleanup;
1253     }
1254
1255   /* If the file looks changed... */
1256   if ((finfo_before.mtime != finfo_after.mtime) ||
1257       (finfo_before.size != finfo_after.size))
1258     {
1259       svn_stringbuf_t *edited_contents_s;
1260       err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool);
1261       if (err)
1262         goto cleanup;
1263
1264       *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s);
1265
1266       /* Translate back to UTF8/LF if desired. */
1267       if (as_text)
1268         {
1269           err = svn_subst_translate_string2(edited_contents, FALSE, FALSE,
1270                                             *edited_contents, encoding, FALSE,
1271                                             pool, pool);
1272           if (err)
1273             {
1274               err = svn_error_quick_wrap
1275                 (err,
1276                  _("Error normalizing edited contents to internal format"));
1277               goto cleanup;
1278             }
1279         }
1280     }
1281   else
1282     {
1283       /* No edits seem to have been made */
1284       *edited_contents = NULL;
1285     }
1286
1287  cleanup:
1288   if (remove_file)
1289     {
1290       /* Remove the file from disk.  */
1291       err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool);
1292
1293       /* Only report remove error if there was no previous error. */
1294       if (! err && err2)
1295         err = err2;
1296       else
1297         svn_error_clear(err2);
1298     }
1299
1300  cleanup2:
1301   /* If we against all probability can't cd back, all further relative
1302      file references would be screwed up, so we have to abort. */
1303   apr_err = apr_filepath_set(old_cwd, pool);
1304   if (apr_err)
1305     {
1306       svn_handle_error2(svn_error_wrap_apr
1307                         (apr_err, _("Can't restore working directory")),
1308                         stderr, TRUE /* fatal */, "svn: ");
1309     }
1310
1311   return svn_error_trace(err);
1312 }