1251881Speter/*
2251881Speter * cmdline.c :  Helpers for command-line programs.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter#include <stdlib.h>             /* for atexit() */
26251881Speter#include <stdio.h>              /* for setvbuf() */
27251881Speter#include <locale.h>             /* for setlocale() */
28251881Speter
29251881Speter#ifndef WIN32
30251881Speter#include <sys/types.h>
31251881Speter#include <sys/stat.h>
32251881Speter#include <fcntl.h>
33251881Speter#include <unistd.h>
34251881Speter#else
35251881Speter#include <crtdbg.h>
36251881Speter#include <io.h>
37251881Speter#endif
38251881Speter
39251881Speter#include <apr.h>                /* for STDIN_FILENO */
40251881Speter#include <apr_errno.h>          /* for apr_strerror */
41251881Speter#include <apr_general.h>        /* for apr_initialize/apr_terminate */
42251881Speter#include <apr_strings.h>        /* for apr_snprintf */
43251881Speter#include <apr_pools.h>
44251881Speter
45251881Speter#include "svn_cmdline.h"
46251881Speter#include "svn_ctype.h"
47251881Speter#include "svn_dso.h"
48251881Speter#include "svn_dirent_uri.h"
49251881Speter#include "svn_hash.h"
50251881Speter#include "svn_path.h"
51251881Speter#include "svn_pools.h"
52251881Speter#include "svn_error.h"
53251881Speter#include "svn_nls.h"
54251881Speter#include "svn_utf.h"
55251881Speter#include "svn_auth.h"
56251881Speter#include "svn_xml.h"
57251881Speter#include "svn_base64.h"
58251881Speter#include "svn_config.h"
59251881Speter#include "svn_sorts.h"
60251881Speter#include "svn_props.h"
61251881Speter#include "svn_subst.h"
62251881Speter
63251881Speter#include "private/svn_cmdline_private.h"
64251881Speter#include "private/svn_utf_private.h"
65251881Speter#include "private/svn_string_private.h"
66251881Speter
67251881Speter#include "svn_private_config.h"
68251881Speter
69251881Speter#include "win32_crashrpt.h"
70251881Speter
71251881Speter/* The stdin encoding. If null, it's the same as the native encoding. */
72251881Speterstatic const char *input_encoding = NULL;
73251881Speter
74251881Speter/* The stdout encoding. If null, it's the same as the native encoding. */
75251881Speterstatic const char *output_encoding = NULL;
76251881Speter
77251881Speter
78251881Speterint
79251881Spetersvn_cmdline_init(const char *progname, FILE *error_stream)
80251881Speter{
81251881Speter  apr_status_t status;
82251881Speter  apr_pool_t *pool;
83251881Speter  svn_error_t *err;
84251881Speter  char prefix_buf[64];  /* 64 is probably bigger than most program names */
85251881Speter
86251881Speter#ifndef WIN32
87251881Speter  {
88251881Speter    struct stat st;
89251881Speter
90251881Speter    /* The following makes sure that file descriptors 0 (stdin), 1
91251881Speter       (stdout) and 2 (stderr) will not be "reused", because if
92251881Speter       e.g. file descriptor 2 would be reused when opening a file, a
93251881Speter       write to stderr would write to that file and most likely
94251881Speter       corrupt it. */
95251881Speter    if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) ||
96251881Speter        (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) ||
97251881Speter        (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1))
98251881Speter      {
99251881Speter        if (error_stream)
100251881Speter          fprintf(error_stream, "%s: error: cannot open '/dev/null'\n",
101251881Speter                  progname);
102251881Speter        return EXIT_FAILURE;
103251881Speter      }
104251881Speter  }
105251881Speter#endif
106251881Speter
107251881Speter  /* Ignore any errors encountered while attempting to change stream
108251881Speter     buffering, as the streams should retain their default buffering
109251881Speter     modes. */
110251881Speter  if (error_stream)
111251881Speter    setvbuf(error_stream, NULL, _IONBF, 0);
112251881Speter#ifndef WIN32
113251881Speter  setvbuf(stdout, NULL, _IOLBF, 0);
114251881Speter#endif
115251881Speter
116251881Speter#ifdef WIN32
117251881Speter#if _MSC_VER < 1400
118251881Speter  /* Initialize the input and output encodings. */
119251881Speter  {
120251881Speter    static char input_encoding_buffer[16];
121251881Speter    static char output_encoding_buffer[16];
122251881Speter
123251881Speter    apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer,
124251881Speter                 "CP%u", (unsigned) GetConsoleCP());
125251881Speter    input_encoding = input_encoding_buffer;
126251881Speter
127251881Speter    apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer,
128251881Speter                 "CP%u", (unsigned) GetConsoleOutputCP());
129251881Speter    output_encoding = output_encoding_buffer;
130251881Speter  }
131251881Speter#endif /* _MSC_VER < 1400 */
132251881Speter
133251881Speter#ifdef SVN_USE_WIN32_CRASHHANDLER
134251881Speter  /* Attach (but don't load) the crash handler */
135251881Speter  SetUnhandledExceptionFilter(svn__unhandled_exception_filter);
136251881Speter
137251881Speter#if _MSC_VER >= 1400
138251881Speter  /* ### This should work for VC++ 2002 (=1300) and later */
139251881Speter  /* Show the abort message on STDERR instead of a dialog to allow
140251881Speter     scripts (e.g. our testsuite) to continue after an abort without
141251881Speter     user intervention. Allow overriding for easier debugging. */
142251881Speter  if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT"))
143251881Speter    {
144251881Speter      /* In release mode: Redirect abort() errors to stderr */
145251881Speter      _set_error_mode(_OUT_TO_STDERR);
146251881Speter
147251881Speter      /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr.
148251881Speter         (Ignored in release builds) */
149251881Speter      _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR);
150251881Speter      _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR);
151251881Speter      _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR);
152251881Speter      _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
153251881Speter      _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
154251881Speter      _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
155251881Speter    }
156251881Speter#endif /* _MSC_VER >= 1400 */
157251881Speter
158251881Speter#endif /* SVN_USE_WIN32_CRASHHANDLER */
159251881Speter
160251881Speter#endif /* WIN32 */
161251881Speter
162251881Speter  /* C programs default to the "C" locale. But because svn is supposed
163251881Speter     to be i18n-aware, it should inherit the default locale of its
164251881Speter     environment.  */
165251881Speter  if (!setlocale(LC_ALL, "")
166251881Speter      && !setlocale(LC_CTYPE, ""))
167251881Speter    {
168251881Speter      if (error_stream)
169251881Speter        {
170251881Speter          const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL };
171251881Speter          const char **env_var = &env_vars[0], *env_val = NULL;
172251881Speter          while (*env_var)
173251881Speter            {
174251881Speter              env_val = getenv(*env_var);
175251881Speter              if (env_val && env_val[0])
176251881Speter                break;
177251881Speter              ++env_var;
178251881Speter            }
179251881Speter
180251881Speter          if (!*env_var)
181251881Speter            {
182251881Speter              /* Unlikely. Can setlocale fail if no env vars are set? */
183251881Speter              --env_var;
184251881Speter              env_val = "not set";
185251881Speter            }
186251881Speter
187251881Speter          fprintf(error_stream,
188251881Speter                  "%s: warning: cannot set LC_CTYPE locale\n"
189251881Speter                  "%s: warning: environment variable %s is %s\n"
190251881Speter                  "%s: warning: please check that your locale name is correct\n",
191251881Speter                  progname, progname, *env_var, env_val, progname);
192251881Speter        }
193251881Speter    }
194251881Speter
195251881Speter  /* Initialize the APR subsystem, and register an atexit() function
196251881Speter     to Uninitialize that subsystem at program exit. */
197251881Speter  status = apr_initialize();
198251881Speter  if (status)
199251881Speter    {
200251881Speter      if (error_stream)
201251881Speter        {
202251881Speter          char buf[1024];
203251881Speter          apr_strerror(status, buf, sizeof(buf) - 1);
204251881Speter          fprintf(error_stream,
205251881Speter                  "%s: error: cannot initialize APR: %s\n",
206251881Speter                  progname, buf);
207251881Speter        }
208251881Speter      return EXIT_FAILURE;
209251881Speter    }
210251881Speter
211251881Speter  strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3);
212251881Speter  prefix_buf[sizeof(prefix_buf) - 3] = '\0';
213251881Speter  strcat(prefix_buf, ": ");
214251881Speter
215251881Speter  /* DSO pool must be created before any other pools used by the
216251881Speter     application so that pool cleanup doesn't unload DSOs too
217251881Speter     early. See docstring of svn_dso_initialize2(). */
218251881Speter  if ((err = svn_dso_initialize2()))
219251881Speter    {
220251881Speter      if (error_stream)
221251881Speter        svn_handle_error2(err, error_stream, TRUE, prefix_buf);
222251881Speter
223251881Speter      svn_error_clear(err);
224251881Speter      return EXIT_FAILURE;
225251881Speter    }
226251881Speter
227251881Speter  if (0 > atexit(apr_terminate))
228251881Speter    {
229251881Speter      if (error_stream)
230251881Speter        fprintf(error_stream,
231251881Speter                "%s: error: atexit registration failed\n",
232251881Speter                progname);
233251881Speter      return EXIT_FAILURE;
234251881Speter    }
235251881Speter
236251881Speter  /* Create a pool for use by the UTF-8 routines.  It will be cleaned
237251881Speter     up by APR at exit time. */
238251881Speter  pool = svn_pool_create(NULL);
239251881Speter  svn_utf_initialize2(FALSE, pool);
240251881Speter
241251881Speter  if ((err = svn_nls_init()))
242251881Speter    {
243251881Speter      if (error_stream)
244251881Speter        svn_handle_error2(err, error_stream, TRUE, prefix_buf);
245251881Speter
246251881Speter      svn_error_clear(err);
247251881Speter      return EXIT_FAILURE;
248251881Speter    }
249251881Speter
250251881Speter  return EXIT_SUCCESS;
251251881Speter}
252251881Speter
253251881Speter
254251881Spetersvn_error_t *
255251881Spetersvn_cmdline_cstring_from_utf8(const char **dest,
256251881Speter                              const char *src,
257251881Speter                              apr_pool_t *pool)
258251881Speter{
259251881Speter  if (output_encoding == NULL)
260251881Speter    return svn_utf_cstring_from_utf8(dest, src, pool);
261251881Speter  else
262251881Speter    return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool);
263251881Speter}
264251881Speter
265251881Speter
266251881Speterconst char *
267251881Spetersvn_cmdline_cstring_from_utf8_fuzzy(const char *src,
268251881Speter                                    apr_pool_t *pool)
269251881Speter{
270251881Speter  return svn_utf__cstring_from_utf8_fuzzy(src, pool,
271251881Speter                                          svn_cmdline_cstring_from_utf8);
272251881Speter}
273251881Speter
274251881Speter
275251881Spetersvn_error_t *
276251881Spetersvn_cmdline_cstring_to_utf8(const char **dest,
277251881Speter                            const char *src,
278251881Speter                            apr_pool_t *pool)
279251881Speter{
280251881Speter  if (input_encoding == NULL)
281251881Speter    return svn_utf_cstring_to_utf8(dest, src, pool);
282251881Speter  else
283251881Speter    return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool);
284251881Speter}
285251881Speter
286251881Speter
287251881Spetersvn_error_t *
288251881Spetersvn_cmdline_path_local_style_from_utf8(const char **dest,
289251881Speter                                       const char *src,
290251881Speter                                       apr_pool_t *pool)
291251881Speter{
292251881Speter  return svn_cmdline_cstring_from_utf8(dest,
293251881Speter                                       svn_dirent_local_style(src, pool),
294251881Speter                                       pool);
295251881Speter}
296251881Speter
297251881Spetersvn_error_t *
298251881Spetersvn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...)
299251881Speter{
300251881Speter  const char *message;
301251881Speter  va_list ap;
302251881Speter
303251881Speter  /* A note about encoding issues:
304251881Speter   * APR uses the execution character set, but here we give it UTF-8 strings,
305251881Speter   * both the fmt argument and any other string arguments.  Since apr_pvsprintf
306251881Speter   * only cares about and produces ASCII characters, this works under the
307251881Speter   * assumption that all supported platforms use an execution character set
308251881Speter   * with ASCII as a subset.
309251881Speter   */
310251881Speter
311251881Speter  va_start(ap, fmt);
312251881Speter  message = apr_pvsprintf(pool, fmt, ap);
313251881Speter  va_end(ap);
314251881Speter
315251881Speter  return svn_cmdline_fputs(message, stdout, pool);
316251881Speter}
317251881Speter
318251881Spetersvn_error_t *
319251881Spetersvn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...)
320251881Speter{
321251881Speter  const char *message;
322251881Speter  va_list ap;
323251881Speter
324251881Speter  /* See svn_cmdline_printf () for a note about character encoding issues. */
325251881Speter
326251881Speter  va_start(ap, fmt);
327251881Speter  message = apr_pvsprintf(pool, fmt, ap);
328251881Speter  va_end(ap);
329251881Speter
330251881Speter  return svn_cmdline_fputs(message, stream, pool);
331251881Speter}
332251881Speter
333251881Spetersvn_error_t *
334251881Spetersvn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool)
335251881Speter{
336251881Speter  svn_error_t *err;
337251881Speter  const char *out;
338251881Speter
339251881Speter  err = svn_cmdline_cstring_from_utf8(&out, string, pool);
340251881Speter
341251881Speter  if (err)
342251881Speter    {
343251881Speter      svn_error_clear(err);
344251881Speter      out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
345251881Speter    }
346251881Speter
347251881Speter  /* On POSIX systems, errno will be set on an error in fputs, but this might
348251881Speter     not be the case on other platforms.  We reset errno and only
349251881Speter     use it if it was set by the below fputs call.  Else, we just return
350251881Speter     a generic error. */
351251881Speter  errno = 0;
352251881Speter
353251881Speter  if (fputs(out, stream) == EOF)
354251881Speter    {
355251881Speter      if (apr_get_os_error()) /* is errno on POSIX */
356251881Speter        {
357251881Speter          /* ### Issue #3014: Return a specific error for broken pipes,
358251881Speter           * ### with a single element in the error chain. */
359262253Speter          if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
360251881Speter            return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
361251881Speter          else
362251881Speter            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
363251881Speter        }
364251881Speter      else
365251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
366251881Speter    }
367251881Speter
368251881Speter  return SVN_NO_ERROR;
369251881Speter}
370251881Speter
371251881Spetersvn_error_t *
372251881Spetersvn_cmdline_fflush(FILE *stream)
373251881Speter{
374251881Speter  /* See comment in svn_cmdline_fputs about use of errno and stdio. */
375251881Speter  errno = 0;
376251881Speter  if (fflush(stream) == EOF)
377251881Speter    {
378251881Speter      if (apr_get_os_error()) /* is errno on POSIX */
379251881Speter        {
380251881Speter          /* ### Issue #3014: Return a specific error for broken pipes,
381251881Speter           * ### with a single element in the error chain. */
382262253Speter          if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error()))
383251881Speter            return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
384251881Speter          else
385251881Speter            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
386251881Speter        }
387251881Speter      else
388251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
389251881Speter    }
390251881Speter
391251881Speter  return SVN_NO_ERROR;
392251881Speter}
393251881Speter
394251881Speterconst char *svn_cmdline_output_encoding(apr_pool_t *pool)
395251881Speter{
396251881Speter  if (output_encoding)
397251881Speter    return apr_pstrdup(pool, output_encoding);
398251881Speter  else
399251881Speter    return SVN_APR_LOCALE_CHARSET;
400251881Speter}
401251881Speter
402251881Speterint
403251881Spetersvn_cmdline_handle_exit_error(svn_error_t *err,
404251881Speter                              apr_pool_t *pool,
405251881Speter                              const char *prefix)
406251881Speter{
407251881Speter  /* Issue #3014:
408251881Speter   * Don't print anything on broken pipes. The pipe was likely
409251881Speter   * closed by the process at the other end. We expect that
410251881Speter   * process to perform error reporting as necessary.
411251881Speter   *
412251881Speter   * ### This assumes that there is only one error in a chain for
413251881Speter   * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
414251881Speter  if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
415251881Speter    svn_handle_error2(err, stderr, FALSE, prefix);
416251881Speter  svn_error_clear(err);
417251881Speter  if (pool)
418251881Speter    svn_pool_destroy(pool);
419251881Speter  return EXIT_FAILURE;
420251881Speter}
421251881Speter
422251881Speter/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'.
423251881Speter
424251881Speter   Don't actually prompt.  Instead, set *CRED_P to valid credentials
425251881Speter   iff FAILURES is empty or is exactly SVN_AUTH_SSL_UNKNOWNCA.  If
426251881Speter   there are any other failure bits, then set *CRED_P to null (that
427251881Speter   is, reject the cert).
428251881Speter
429251881Speter   Ignore MAY_SAVE; we don't save certs we never prompted for.
430251881Speter
431251881Speter   Ignore BATON, REALM, and CERT_INFO,
432251881Speter
433251881Speter   Ignore any further films by George Lucas. */
434251881Speterstatic svn_error_t *
435251881Speterssl_trust_unknown_server_cert
436251881Speter  (svn_auth_cred_ssl_server_trust_t **cred_p,
437251881Speter   void *baton,
438251881Speter   const char *realm,
439251881Speter   apr_uint32_t failures,
440251881Speter   const svn_auth_ssl_server_cert_info_t *cert_info,
441251881Speter   svn_boolean_t may_save,
442251881Speter   apr_pool_t *pool)
443251881Speter{
444251881Speter  *cred_p = NULL;
445251881Speter
446251881Speter  if (failures == 0 || failures == SVN_AUTH_SSL_UNKNOWNCA)
447251881Speter    {
448251881Speter      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
449251881Speter      (*cred_p)->may_save = FALSE;
450251881Speter      (*cred_p)->accepted_failures = failures;
451251881Speter    }
452251881Speter
453251881Speter  return SVN_NO_ERROR;
454251881Speter}
455251881Speter
456251881Spetersvn_error_t *
457251881Spetersvn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
458251881Speter                              svn_boolean_t non_interactive,
459251881Speter                              const char *auth_username,
460251881Speter                              const char *auth_password,
461251881Speter                              const char *config_dir,
462251881Speter                              svn_boolean_t no_auth_cache,
463251881Speter                              svn_boolean_t trust_server_cert,
464251881Speter                              svn_config_t *cfg,
465251881Speter                              svn_cancel_func_t cancel_func,
466251881Speter                              void *cancel_baton,
467251881Speter                              apr_pool_t *pool)
468251881Speter{
469251881Speter  svn_boolean_t store_password_val = TRUE;
470251881Speter  svn_boolean_t store_auth_creds_val = TRUE;
471251881Speter  svn_auth_provider_object_t *provider;
472251881Speter  svn_cmdline_prompt_baton2_t *pb = NULL;
473251881Speter
474251881Speter  /* The whole list of registered providers */
475251881Speter  apr_array_header_t *providers;
476251881Speter
477251881Speter  /* Populate the registered providers with the platform-specific providers */
478251881Speter  SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers,
479251881Speter                                                          cfg, pool));
480251881Speter
481251881Speter  /* If we have a cancellation function, cram it and the stuff it
482251881Speter     needs into the prompt baton. */
483251881Speter  if (cancel_func)
484251881Speter    {
485251881Speter      pb = apr_palloc(pool, sizeof(*pb));
486251881Speter      pb->cancel_func = cancel_func;
487251881Speter      pb->cancel_baton = cancel_baton;
488251881Speter      pb->config_dir = config_dir;
489251881Speter    }
490251881Speter
491251881Speter  if (!non_interactive)
492251881Speter    {
493251881Speter      /* This provider doesn't prompt the user in order to get creds;
494251881Speter         it prompts the user regarding the caching of creds. */
495251881Speter      svn_auth_get_simple_provider2(&provider,
496251881Speter                                    svn_cmdline_auth_plaintext_prompt,
497251881Speter                                    pb, pool);
498251881Speter    }
499251881Speter  else
500251881Speter    {
501251881Speter      svn_auth_get_simple_provider2(&provider, NULL, NULL, pool);
502251881Speter    }
503251881Speter
504251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
505251881Speter  svn_auth_get_username_provider(&provider, pool);
506251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
507251881Speter
508262253Speter  /* The windows ssl server certificate CRYPTOAPI provider. */
509251881Speter  SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
510251881Speter                                                  "windows",
511251881Speter                                                  "ssl_server_trust",
512251881Speter                                                  pool));
513251881Speter
514251881Speter  if (provider)
515251881Speter    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
516251881Speter
517262253Speter  /* The windows ssl authority certificate CRYPTOAPI provider. */
518262253Speter  SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
519262253Speter                                                  "windows",
520262253Speter                                                  "ssl_server_authority",
521262253Speter                                                  pool));
522262253Speter
523262253Speter  if (provider)
524262253Speter    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
525262253Speter
526251881Speter  svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
527251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
528251881Speter  svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
529251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
530251881Speter
531251881Speter  if (!non_interactive)
532251881Speter    {
533251881Speter      /* This provider doesn't prompt the user in order to get creds;
534251881Speter         it prompts the user regarding the caching of creds. */
535251881Speter      svn_auth_get_ssl_client_cert_pw_file_provider2
536251881Speter        (&provider, svn_cmdline_auth_plaintext_passphrase_prompt,
537251881Speter         pb, pool);
538251881Speter    }
539251881Speter  else
540251881Speter    {
541251881Speter      svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL,
542251881Speter                                                     pool);
543251881Speter    }
544251881Speter  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
545251881Speter
546251881Speter  if (!non_interactive)
547251881Speter    {
548251881Speter      svn_boolean_t ssl_client_cert_file_prompt;
549251881Speter
550251881Speter      SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt,
551251881Speter                                  SVN_CONFIG_SECTION_AUTH,
552251881Speter                                  SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT,
553251881Speter                                  FALSE));
554251881Speter
555251881Speter      /* Two basic prompt providers: username/password, and just username. */
556251881Speter      svn_auth_get_simple_prompt_provider(&provider,
557251881Speter                                          svn_cmdline_auth_simple_prompt,
558251881Speter                                          pb,
559251881Speter                                          2, /* retry limit */
560251881Speter                                          pool);
561251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
562251881Speter
563251881Speter      svn_auth_get_username_prompt_provider
564251881Speter        (&provider, svn_cmdline_auth_username_prompt, pb,
565251881Speter         2, /* retry limit */ pool);
566251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
567251881Speter
568251881Speter      /* SSL prompt providers: server-certs and client-cert-passphrases.  */
569251881Speter      svn_auth_get_ssl_server_trust_prompt_provider
570251881Speter        (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool);
571251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
572251881Speter
573251881Speter      svn_auth_get_ssl_client_cert_pw_prompt_provider
574251881Speter        (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool);
575251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
576251881Speter
577251881Speter      /* If configuration allows, add a provider for client-cert path
578251881Speter         prompting, too. */
579251881Speter      if (ssl_client_cert_file_prompt)
580251881Speter        {
581251881Speter          svn_auth_get_ssl_client_cert_prompt_provider
582251881Speter            (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
583251881Speter          APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
584251881Speter        }
585251881Speter    }
586251881Speter  else if (trust_server_cert)
587251881Speter    {
588251881Speter      /* Remember, only register this provider if non_interactive. */
589251881Speter      svn_auth_get_ssl_server_trust_prompt_provider
590251881Speter        (&provider, ssl_trust_unknown_server_cert, NULL, pool);
591251881Speter      APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
592251881Speter    }
593251881Speter
594251881Speter  /* Build an authentication baton to give to libsvn_client. */
595251881Speter  svn_auth_open(ab, providers, pool);
596251881Speter
597251881Speter  /* Place any default --username or --password credentials into the
598251881Speter     auth_baton's run-time parameter hash. */
599251881Speter  if (auth_username)
600251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
601251881Speter                           auth_username);
602251881Speter  if (auth_password)
603251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
604251881Speter                           auth_password);
605251881Speter
606251881Speter  /* Same with the --non-interactive option. */
607251881Speter  if (non_interactive)
608251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, "");
609251881Speter
610251881Speter  if (config_dir)
611251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR,
612251881Speter                           config_dir);
613251881Speter
614251881Speter  /* Determine whether storing passwords in any form is allowed.
615251881Speter   * This is the deprecated location for this option, the new
616251881Speter   * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
617251881Speter   * override the value we set here. */
618251881Speter  SVN_ERR(svn_config_get_bool(cfg, &store_password_val,
619251881Speter                              SVN_CONFIG_SECTION_AUTH,
620251881Speter                              SVN_CONFIG_OPTION_STORE_PASSWORDS,
621251881Speter                              SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS));
622251881Speter
623251881Speter  if (! store_password_val)
624251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, "");
625251881Speter
626251881Speter  /* Determine whether we are allowed to write to the auth/ area.
627251881Speter   * This is the deprecated location for this option, the new
628251881Speter   * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
629251881Speter   * override the value we set here. */
630251881Speter  SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val,
631251881Speter                              SVN_CONFIG_SECTION_AUTH,
632251881Speter                              SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
633251881Speter                              SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS));
634251881Speter
635251881Speter  if (no_auth_cache || ! store_auth_creds_val)
636251881Speter    svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
637251881Speter
638251881Speter#ifdef SVN_HAVE_GNOME_KEYRING
639251881Speter  svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC,
640251881Speter                         &svn_cmdline__auth_gnome_keyring_unlock_prompt);
641251881Speter#endif /* SVN_HAVE_GNOME_KEYRING */
642251881Speter
643251881Speter  return SVN_NO_ERROR;
644251881Speter}
645251881Speter
646251881Spetersvn_error_t *
647251881Spetersvn_cmdline__getopt_init(apr_getopt_t **os,
648251881Speter                         int argc,
649251881Speter                         const char *argv[],
650251881Speter                         apr_pool_t *pool)
651251881Speter{
652251881Speter  apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv);
653251881Speter  if (apr_err)
654251881Speter    return svn_error_wrap_apr(apr_err,
655251881Speter                              _("Error initializing command line arguments"));
656251881Speter  return SVN_NO_ERROR;
657251881Speter}
658251881Speter
659251881Speter
660251881Spetervoid
661251881Spetersvn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
662251881Speter                            const char* propname,
663251881Speter                            svn_string_t *propval,
664251881Speter                            svn_boolean_t inherited_prop,
665251881Speter                            apr_pool_t *pool)
666251881Speter{
667251881Speter  const char *xml_safe;
668251881Speter  const char *encoding = NULL;
669251881Speter
670251881Speter  if (*outstr == NULL)
671251881Speter    *outstr = svn_stringbuf_create_empty(pool);
672251881Speter
673251881Speter  if (svn_xml_is_xml_safe(propval->data, propval->len))
674251881Speter    {
675251881Speter      svn_stringbuf_t *xml_esc = NULL;
676251881Speter      svn_xml_escape_cdata_string(&xml_esc, propval, pool);
677251881Speter      xml_safe = xml_esc->data;
678251881Speter    }
679251881Speter  else
680251881Speter    {
681251881Speter      const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE,
682251881Speter                                                               pool);
683251881Speter      encoding = "base64";
684251881Speter      xml_safe = base64ed->data;
685251881Speter    }
686251881Speter
687251881Speter  if (encoding)
688251881Speter    svn_xml_make_open_tag(
689251881Speter      outstr, pool, svn_xml_protect_pcdata,
690251881Speter      inherited_prop ? "inherited_property" : "property",
691251881Speter      "name", propname,
692251881Speter      "encoding", encoding, NULL);
693251881Speter  else
694251881Speter    svn_xml_make_open_tag(
695251881Speter      outstr, pool, svn_xml_protect_pcdata,
696251881Speter      inherited_prop ? "inherited_property" : "property",
697251881Speter      "name", propname, NULL);
698251881Speter
699251881Speter  svn_stringbuf_appendcstr(*outstr, xml_safe);
700251881Speter
701251881Speter  svn_xml_make_close_tag(
702251881Speter    outstr, pool,
703251881Speter    inherited_prop ? "inherited_property" : "property");
704251881Speter
705251881Speter  return;
706251881Speter}
707251881Speter
708251881Spetersvn_error_t *
709251881Spetersvn_cmdline__parse_config_option(apr_array_header_t *config_options,
710251881Speter                                 const char *opt_arg,
711251881Speter                                 apr_pool_t *pool)
712251881Speter{
713251881Speter  svn_cmdline__config_argument_t *config_option;
714251881Speter  const char *first_colon, *second_colon, *equals_sign;
715251881Speter  apr_size_t len = strlen(opt_arg);
716251881Speter  if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg))
717251881Speter    {
718251881Speter      if ((second_colon = strchr(first_colon + 1, ':')) &&
719251881Speter          (second_colon != first_colon + 1))
720251881Speter        {
721251881Speter          if ((equals_sign = strchr(second_colon + 1, '=')) &&
722251881Speter              (equals_sign != second_colon + 1))
723251881Speter            {
724251881Speter              config_option = apr_pcalloc(pool, sizeof(*config_option));
725251881Speter              config_option->file = apr_pstrndup(pool, opt_arg,
726251881Speter                                                 first_colon - opt_arg);
727251881Speter              config_option->section = apr_pstrndup(pool, first_colon + 1,
728251881Speter                                                    second_colon - first_colon - 1);
729251881Speter              config_option->option = apr_pstrndup(pool, second_colon + 1,
730251881Speter                                                   equals_sign - second_colon - 1);
731251881Speter
732251881Speter              if (! (strchr(config_option->option, ':')))
733251881Speter                {
734251881Speter                  config_option->value = apr_pstrndup(pool, equals_sign + 1,
735251881Speter                                                      opt_arg + len - equals_sign - 1);
736251881Speter                  APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *)
737251881Speter                                       = config_option;
738251881Speter                  return SVN_NO_ERROR;
739251881Speter                }
740251881Speter            }
741251881Speter        }
742251881Speter    }
743251881Speter  return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
744251881Speter                          _("Invalid syntax of argument of --config-option"));
745251881Speter}
746251881Speter
747251881Spetersvn_error_t *
748251881Spetersvn_cmdline__apply_config_options(apr_hash_t *config,
749251881Speter                                  const apr_array_header_t *config_options,
750251881Speter                                  const char *prefix,
751251881Speter                                  const char *argument_name)
752251881Speter{
753251881Speter  int i;
754251881Speter
755251881Speter  for (i = 0; i < config_options->nelts; i++)
756251881Speter   {
757251881Speter     svn_config_t *cfg;
758251881Speter     svn_cmdline__config_argument_t *arg =
759251881Speter                          APR_ARRAY_IDX(config_options, i,
760251881Speter                                        svn_cmdline__config_argument_t *);
761251881Speter
762251881Speter     cfg = svn_hash_gets(config, arg->file);
763251881Speter
764251881Speter     if (cfg)
765251881Speter       {
766251881Speter         svn_config_set(cfg, arg->section, arg->option, arg->value);
767251881Speter       }
768251881Speter     else
769251881Speter       {
770251881Speter         svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
771251881Speter             _("Unrecognized file in argument of %s"), argument_name);
772251881Speter
773251881Speter         svn_handle_warning2(stderr, err, prefix);
774251881Speter         svn_error_clear(err);
775251881Speter       }
776251881Speter    }
777251881Speter
778251881Speter  return SVN_NO_ERROR;
779251881Speter}
780251881Speter
781251881Speter/* Return a copy, allocated in POOL, of the next line of text from *STR
782251881Speter * up to and including a CR and/or an LF. Change *STR to point to the
783251881Speter * remainder of the string after the returned part. If there are no
784251881Speter * characters to be returned, return NULL; never return an empty string.
785251881Speter */
786251881Speterstatic const char *
787251881Speternext_line(const char **str, apr_pool_t *pool)
788251881Speter{
789251881Speter  const char *start = *str;
790251881Speter  const char *p = *str;
791251881Speter
792251881Speter  /* n.b. Throughout this fn, we never read any character after a '\0'. */
793251881Speter  /* Skip over all non-EOL characters, if any. */
794251881Speter  while (*p != '\r' && *p != '\n' && *p != '\0')
795251881Speter    p++;
796251881Speter  /* Skip over \r\n or \n\r or \r or \n, if any. */
797251881Speter  if (*p == '\r' || *p == '\n')
798251881Speter    {
799251881Speter      char c = *p++;
800251881Speter
801251881Speter      if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r'))
802251881Speter        p++;
803251881Speter    }
804251881Speter
805251881Speter  /* Now p points after at most one '\n' and/or '\r'. */
806251881Speter  *str = p;
807251881Speter
808251881Speter  if (p == start)
809251881Speter    return NULL;
810251881Speter
811251881Speter  return svn_string_ncreate(start, p - start, pool)->data;
812251881Speter}
813251881Speter
814251881Speterconst char *
815251881Spetersvn_cmdline__indent_string(const char *str,
816251881Speter                           const char *indent,
817251881Speter                           apr_pool_t *pool)
818251881Speter{
819251881Speter  svn_stringbuf_t *out = svn_stringbuf_create_empty(pool);
820251881Speter  const char *line;
821251881Speter
822251881Speter  while ((line = next_line(&str, pool)))
823251881Speter    {
824251881Speter      svn_stringbuf_appendcstr(out, indent);
825251881Speter      svn_stringbuf_appendcstr(out, line);
826251881Speter    }
827251881Speter  return out->data;
828251881Speter}
829251881Speter
830251881Spetersvn_error_t *
831251881Spetersvn_cmdline__print_prop_hash(svn_stream_t *out,
832251881Speter                             apr_hash_t *prop_hash,
833251881Speter                             svn_boolean_t names_only,
834251881Speter                             apr_pool_t *pool)
835251881Speter{
836251881Speter  apr_array_header_t *sorted_props;
837251881Speter  int i;
838251881Speter
839251881Speter  sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
840251881Speter                                pool);
841251881Speter  for (i = 0; i < sorted_props->nelts; i++)
842251881Speter    {
843251881Speter      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
844251881Speter      const char *pname = item.key;
845251881Speter      svn_string_t *propval = item.value;
846251881Speter      const char *pname_stdout;
847251881Speter
848251881Speter      if (svn_prop_needs_translation(pname))
849251881Speter        SVN_ERR(svn_subst_detranslate_string(&propval, propval,
850251881Speter                                             TRUE, pool));
851251881Speter
852251881Speter      SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
853251881Speter
854251881Speter      if (out)
855251881Speter        {
856251881Speter          pname_stdout = apr_psprintf(pool, "  %s\n", pname_stdout);
857251881Speter          SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout,
858251881Speter                                              APR_EOL_STR,  /* 'native' eol */
859251881Speter                                              FALSE, /* no repair */
860251881Speter                                              NULL,  /* no keywords */
861251881Speter                                              FALSE, /* no expansion */
862251881Speter                                              pool));
863251881Speter
864251881Speter          SVN_ERR(svn_stream_puts(out, pname_stdout));
865251881Speter        }
866251881Speter      else
867251881Speter        {
868251881Speter          /* ### We leave these printfs for now, since if propval wasn't
869251881Speter             translated above, we don't know anything about its encoding.
870251881Speter             In fact, it might be binary data... */
871251881Speter          printf("  %s\n", pname_stdout);
872251881Speter        }
873251881Speter
874251881Speter      if (!names_only)
875251881Speter        {
876251881Speter          /* Add an extra newline to the value before indenting, so that
877251881Speter           * every line of output has the indentation whether the value
878251881Speter           * already ended in a newline or not. */
879251881Speter          const char *newval = apr_psprintf(pool, "%s\n", propval->data);
880251881Speter          const char *indented_newval = svn_cmdline__indent_string(newval,
881251881Speter                                                                   "    ",
882251881Speter                                                                   pool);
883251881Speter          if (out)
884251881Speter            {
885251881Speter              SVN_ERR(svn_stream_puts(out, indented_newval));
886251881Speter            }
887251881Speter          else
888251881Speter            {
889251881Speter              printf("%s", indented_newval);
890251881Speter            }
891251881Speter        }
892251881Speter    }
893251881Speter
894251881Speter  return SVN_NO_ERROR;
895251881Speter}
896251881Speter
897251881Spetersvn_error_t *
898251881Spetersvn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr,
899251881Speter                                 apr_hash_t *prop_hash,
900251881Speter                                 svn_boolean_t names_only,
901251881Speter                                 svn_boolean_t inherited_props,
902251881Speter                                 apr_pool_t *pool)
903251881Speter{
904251881Speter  apr_array_header_t *sorted_props;
905251881Speter  int i;
906251881Speter
907251881Speter  if (*outstr == NULL)
908251881Speter    *outstr = svn_stringbuf_create_empty(pool);
909251881Speter
910251881Speter  sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
911251881Speter                                pool);
912251881Speter  for (i = 0; i < sorted_props->nelts; i++)
913251881Speter    {
914251881Speter      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
915251881Speter      const char *pname = item.key;
916251881Speter      svn_string_t *propval = item.value;
917251881Speter
918251881Speter      if (names_only)
919251881Speter        {
920251881Speter          svn_xml_make_open_tag(
921251881Speter            outstr, pool, svn_xml_self_closing,
922251881Speter            inherited_props ? "inherited_property" : "property",
923251881Speter            "name", pname, NULL);
924251881Speter        }
925251881Speter      else
926251881Speter        {
927251881Speter          const char *pname_out;
928251881Speter
929251881Speter          if (svn_prop_needs_translation(pname))
930251881Speter            SVN_ERR(svn_subst_detranslate_string(&propval, propval,
931251881Speter                                                 TRUE, pool));
932251881Speter
933251881Speter          SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
934251881Speter
935251881Speter          svn_cmdline__print_xml_prop(outstr, pname_out, propval,
936251881Speter                                      inherited_props, pool);
937251881Speter        }
938251881Speter    }
939251881Speter
940251881Speter    return SVN_NO_ERROR;
941251881Speter}
942251881Speter
943251881Spetersvn_boolean_t
944251881Spetersvn_cmdline__be_interactive(svn_boolean_t non_interactive,
945251881Speter                            svn_boolean_t force_interactive)
946251881Speter{
947251881Speter  /* If neither --non-interactive nor --force-interactive was passed,
948251881Speter   * be interactive if stdin is a terminal.
949251881Speter   * If --force-interactive was passed, always be interactive. */
950251881Speter  if (!force_interactive && !non_interactive)
951251881Speter    {
952251881Speter#ifdef WIN32
953251881Speter      return (_isatty(STDIN_FILENO) != 0);
954251881Speter#else
955251881Speter      return (isatty(STDIN_FILENO) != 0);
956251881Speter#endif
957251881Speter    }
958251881Speter  else if (force_interactive)
959251881Speter    return TRUE;
960251881Speter
961251881Speter  return !non_interactive;
962251881Speter}
963251881Speter
964251881Speter
965251881Speter/* Helper for the next two functions.  Set *EDITOR to some path to an
966251881Speter   editor binary.  Sources to search include: the EDITOR_CMD argument
967251881Speter   (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
968251881Speter   is not NULL), $VISUAL, $EDITOR.  Return
969251881Speter   SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
970251881Speterstatic svn_error_t *
971251881Speterfind_editor_binary(const char **editor,
972251881Speter                   const char *editor_cmd,
973251881Speter                   apr_hash_t *config)
974251881Speter{
975251881Speter  const char *e;
976251881Speter  struct svn_config_t *cfg;
977251881Speter
978251881Speter  /* Use the editor specified on the command line via --editor-cmd, if any. */
979251881Speter  e = editor_cmd;
980251881Speter
981251881Speter  /* Otherwise look for the Subversion-specific environment variable. */
982251881Speter  if (! e)
983251881Speter    e = getenv("SVN_EDITOR");
984251881Speter
985251881Speter  /* If not found then fall back on the config file. */
986251881Speter  if (! e)
987251881Speter    {
988251881Speter      cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
989251881Speter      svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS,
990251881Speter                     SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
991251881Speter    }
992251881Speter
993251881Speter  /* If not found yet then try general purpose environment variables. */
994251881Speter  if (! e)
995251881Speter    e = getenv("VISUAL");
996251881Speter  if (! e)
997251881Speter    e = getenv("EDITOR");
998251881Speter
999251881Speter#ifdef SVN_CLIENT_EDITOR
1000251881Speter  /* If still not found then fall back on the hard-coded default. */
1001251881Speter  if (! e)
1002251881Speter    e = SVN_CLIENT_EDITOR;
1003251881Speter#endif
1004251881Speter
1005251881Speter  /* Error if there is no editor specified */
1006251881Speter  if (e)
1007251881Speter    {
1008251881Speter      const char *c;
1009251881Speter
1010251881Speter      for (c = e; *c; c++)
1011251881Speter        if (!svn_ctype_isspace(*c))
1012251881Speter          break;
1013251881Speter
1014251881Speter      if (! *c)
1015251881Speter        return svn_error_create
1016251881Speter          (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1017251881Speter           _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
1018251881Speter             "'editor-cmd' run-time configuration option is empty or "
1019251881Speter             "consists solely of whitespace. Expected a shell command."));
1020251881Speter    }
1021251881Speter  else
1022251881Speter    return svn_error_create
1023251881Speter      (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
1024251881Speter       _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
1025251881Speter         "set, and no 'editor-cmd' run-time configuration option was found"));
1026251881Speter
1027251881Speter  *editor = e;
1028251881Speter  return SVN_NO_ERROR;
1029251881Speter}
1030251881Speter
1031251881Speter
1032251881Spetersvn_error_t *
1033251881Spetersvn_cmdline__edit_file_externally(const char *path,
1034251881Speter                                  const char *editor_cmd,
1035251881Speter                                  apr_hash_t *config,
1036251881Speter                                  apr_pool_t *pool)
1037251881Speter{
1038251881Speter  const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
1039251881Speter  char *old_cwd;
1040251881Speter  int sys_err;
1041251881Speter  apr_status_t apr_err;
1042251881Speter
1043251881Speter  svn_dirent_split(&base_dir, &file_name, path, pool);
1044251881Speter
1045251881Speter  SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1046251881Speter
1047251881Speter  apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1048251881Speter  if (apr_err)
1049251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1050251881Speter
1051251881Speter  /* APR doesn't like "" directories */
1052251881Speter  if (base_dir[0] == '\0')
1053251881Speter    base_dir_apr = ".";
1054251881Speter  else
1055251881Speter    SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1056251881Speter
1057251881Speter  apr_err = apr_filepath_set(base_dir_apr, pool);
1058251881Speter  if (apr_err)
1059251881Speter    return svn_error_wrap_apr
1060251881Speter      (apr_err, _("Can't change working directory to '%s'"), base_dir);
1061251881Speter
1062251881Speter  cmd = apr_psprintf(pool, "%s %s", editor, file_name);
1063251881Speter  sys_err = system(cmd);
1064251881Speter
1065251881Speter  apr_err = apr_filepath_set(old_cwd, pool);
1066251881Speter  if (apr_err)
1067251881Speter    svn_handle_error2(svn_error_wrap_apr
1068251881Speter                      (apr_err, _("Can't restore working directory")),
1069251881Speter                      stderr, TRUE /* fatal */, "svn: ");
1070251881Speter
1071251881Speter  if (sys_err)
1072251881Speter    /* Extracting any meaning from sys_err is platform specific, so just
1073251881Speter       use the raw value. */
1074251881Speter    return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1075251881Speter                             _("system('%s') returned %d"), cmd, sys_err);
1076251881Speter
1077251881Speter  return SVN_NO_ERROR;
1078251881Speter}
1079251881Speter
1080251881Speter
1081251881Spetersvn_error_t *
1082251881Spetersvn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */,
1083251881Speter                                    const char **tmpfile_left /* UTF-8! */,
1084251881Speter                                    const char *editor_cmd,
1085251881Speter                                    const char *base_dir /* UTF-8! */,
1086251881Speter                                    const svn_string_t *contents /* UTF-8! */,
1087251881Speter                                    const char *filename,
1088251881Speter                                    apr_hash_t *config,
1089251881Speter                                    svn_boolean_t as_text,
1090251881Speter                                    const char *encoding,
1091251881Speter                                    apr_pool_t *pool)
1092251881Speter{
1093251881Speter  const char *editor;
1094251881Speter  const char *cmd;
1095251881Speter  apr_file_t *tmp_file;
1096251881Speter  const char *tmpfile_name;
1097251881Speter  const char *tmpfile_native;
1098251881Speter  const char *tmpfile_apr, *base_dir_apr;
1099251881Speter  svn_string_t *translated_contents;
1100251881Speter  apr_status_t apr_err, apr_err2;
1101251881Speter  apr_size_t written;
1102251881Speter  apr_finfo_t finfo_before, finfo_after;
1103251881Speter  svn_error_t *err = SVN_NO_ERROR, *err2;
1104251881Speter  char *old_cwd;
1105251881Speter  int sys_err;
1106251881Speter  svn_boolean_t remove_file = TRUE;
1107251881Speter
1108251881Speter  SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
1109251881Speter
1110251881Speter  /* Convert file contents from UTF-8/LF if desired. */
1111251881Speter  if (as_text)
1112251881Speter    {
1113251881Speter      const char *translated;
1114251881Speter      SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
1115251881Speter                                           APR_EOL_STR, FALSE,
1116251881Speter                                           NULL, FALSE, pool));
1117251881Speter      translated_contents = svn_string_create_empty(pool);
1118251881Speter      if (encoding)
1119251881Speter        SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
1120251881Speter                                              translated, encoding, pool));
1121251881Speter      else
1122251881Speter        SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
1123251881Speter                                          translated, pool));
1124251881Speter      translated_contents->len = strlen(translated_contents->data);
1125251881Speter    }
1126251881Speter  else
1127251881Speter    translated_contents = svn_string_dup(contents, pool);
1128251881Speter
1129251881Speter  /* Move to BASE_DIR to avoid getting characters that need quoting
1130251881Speter     into tmpfile_name */
1131251881Speter  apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
1132251881Speter  if (apr_err)
1133251881Speter    return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
1134251881Speter
1135251881Speter  /* APR doesn't like "" directories */
1136251881Speter  if (base_dir[0] == '\0')
1137251881Speter    base_dir_apr = ".";
1138251881Speter  else
1139251881Speter    SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
1140251881Speter  apr_err = apr_filepath_set(base_dir_apr, pool);
1141251881Speter  if (apr_err)
1142251881Speter    {
1143251881Speter      return svn_error_wrap_apr
1144251881Speter        (apr_err, _("Can't change working directory to '%s'"), base_dir);
1145251881Speter    }
1146251881Speter
1147251881Speter  /*** From here on, any problems that occur require us to cd back!! ***/
1148251881Speter
1149251881Speter  /* Ask the working copy for a temporary file named FILENAME-something. */
1150251881Speter  err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1151251881Speter                                   "" /* dirpath */,
1152251881Speter                                   filename,
1153251881Speter                                   ".tmp",
1154251881Speter                                   svn_io_file_del_none, pool, pool);
1155251881Speter
1156251881Speter  if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS))
1157251881Speter    {
1158251881Speter      const char *temp_dir_apr;
1159251881Speter
1160251881Speter      svn_error_clear(err);
1161251881Speter
1162251881Speter      SVN_ERR(svn_io_temp_dir(&base_dir, pool));
1163251881Speter
1164251881Speter      SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool));
1165251881Speter      apr_err = apr_filepath_set(temp_dir_apr, pool);
1166251881Speter      if (apr_err)
1167251881Speter        {
1168251881Speter          return svn_error_wrap_apr
1169251881Speter            (apr_err, _("Can't change working directory to '%s'"), base_dir);
1170251881Speter        }
1171251881Speter
1172251881Speter      err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
1173251881Speter                                       "" /* dirpath */,
1174251881Speter                                       filename,
1175251881Speter                                       ".tmp",
1176251881Speter                                       svn_io_file_del_none, pool, pool);
1177251881Speter    }
1178251881Speter
1179251881Speter  if (err)
1180251881Speter    goto cleanup2;
1181251881Speter
1182251881Speter  /*** From here on, any problems that occur require us to cleanup
1183251881Speter       the file we just created!! ***/
1184251881Speter
1185251881Speter  /* Dump initial CONTENTS to TMP_FILE. */
1186251881Speter  apr_err = apr_file_write_full(tmp_file, translated_contents->data,
1187251881Speter                                translated_contents->len, &written);
1188251881Speter
1189251881Speter  apr_err2 = apr_file_close(tmp_file);
1190251881Speter  if (! apr_err)
1191251881Speter    apr_err = apr_err2;
1192251881Speter
1193251881Speter  /* Make sure the whole CONTENTS were written, else return an error. */
1194251881Speter  if (apr_err)
1195251881Speter    {
1196251881Speter      err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"),
1197251881Speter                               tmpfile_name);
1198251881Speter      goto cleanup;
1199251881Speter    }
1200251881Speter
1201251881Speter  err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool);
1202251881Speter  if (err)
1203251881Speter    goto cleanup;
1204251881Speter
1205251881Speter  /* Get information about the temporary file before the user has
1206251881Speter     been allowed to edit its contents. */
1207251881Speter  apr_err = apr_stat(&finfo_before, tmpfile_apr,
1208251881Speter                     APR_FINFO_MTIME, pool);
1209251881Speter  if (apr_err)
1210251881Speter    {
1211251881Speter      err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1212251881Speter      goto cleanup;
1213251881Speter    }
1214251881Speter
1215251881Speter  /* Backdate the file a little bit in case the editor is very fast
1216251881Speter     and doesn't change the size.  (Use two seconds, since some
1217251881Speter     filesystems have coarse granularity.)  It's OK if this call
1218251881Speter     fails, so we don't check its return value.*/
1219251881Speter  apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool);
1220251881Speter
1221251881Speter  /* Stat it again to get the mtime we actually set. */
1222251881Speter  apr_err = apr_stat(&finfo_before, tmpfile_apr,
1223251881Speter                     APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1224251881Speter  if (apr_err)
1225251881Speter    {
1226251881Speter      err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1227251881Speter      goto cleanup;
1228251881Speter    }
1229251881Speter
1230251881Speter  /* Prepare the editor command line.  */
1231251881Speter  err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
1232251881Speter  if (err)
1233251881Speter    goto cleanup;
1234251881Speter  cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
1235251881Speter
1236251881Speter  /* If the caller wants us to leave the file around, return the path
1237251881Speter     of the file we'll use, and make a note not to destroy it.  */
1238251881Speter  if (tmpfile_left)
1239251881Speter    {
1240251881Speter      *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool);
1241251881Speter      remove_file = FALSE;
1242251881Speter    }
1243251881Speter
1244251881Speter  /* Now, run the editor command line.  */
1245251881Speter  sys_err = system(cmd);
1246251881Speter  if (sys_err != 0)
1247251881Speter    {
1248251881Speter      /* Extracting any meaning from sys_err is platform specific, so just
1249251881Speter         use the raw value. */
1250251881Speter      err =  svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
1251251881Speter                               _("system('%s') returned %d"), cmd, sys_err);
1252251881Speter      goto cleanup;
1253251881Speter    }
1254251881Speter
1255251881Speter  /* Get information about the temporary file after the assumed editing. */
1256251881Speter  apr_err = apr_stat(&finfo_after, tmpfile_apr,
1257251881Speter                     APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
1258251881Speter  if (apr_err)
1259251881Speter    {
1260251881Speter      err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
1261251881Speter      goto cleanup;
1262251881Speter    }
1263251881Speter
1264251881Speter  /* If the file looks changed... */
1265251881Speter  if ((finfo_before.mtime != finfo_after.mtime) ||
1266251881Speter      (finfo_before.size != finfo_after.size))
1267251881Speter    {
1268251881Speter      svn_stringbuf_t *edited_contents_s;
1269251881Speter      err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool);
1270251881Speter      if (err)
1271251881Speter        goto cleanup;
1272251881Speter
1273251881Speter      *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s);
1274251881Speter
1275251881Speter      /* Translate back to UTF8/LF if desired. */
1276251881Speter      if (as_text)
1277251881Speter        {
1278251881Speter          err = svn_subst_translate_string2(edited_contents, FALSE, FALSE,
1279251881Speter                                            *edited_contents, encoding, FALSE,
1280251881Speter                                            pool, pool);
1281251881Speter          if (err)
1282251881Speter            {
1283251881Speter              err = svn_error_quick_wrap
1284251881Speter                (err,
1285251881Speter                 _("Error normalizing edited contents to internal format"));
1286251881Speter              goto cleanup;
1287251881Speter            }
1288251881Speter        }
1289251881Speter    }
1290251881Speter  else
1291251881Speter    {
1292251881Speter      /* No edits seem to have been made */
1293251881Speter      *edited_contents = NULL;
1294251881Speter    }
1295251881Speter
1296251881Speter cleanup:
1297251881Speter  if (remove_file)
1298251881Speter    {
1299251881Speter      /* Remove the file from disk.  */
1300251881Speter      err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool);
1301251881Speter
1302251881Speter      /* Only report remove error if there was no previous error. */
1303251881Speter      if (! err && err2)
1304251881Speter        err = err2;
1305251881Speter      else
1306251881Speter        svn_error_clear(err2);
1307251881Speter    }
1308251881Speter
1309251881Speter cleanup2:
1310251881Speter  /* If we against all probability can't cd back, all further relative
1311251881Speter     file references would be screwed up, so we have to abort. */
1312251881Speter  apr_err = apr_filepath_set(old_cwd, pool);
1313251881Speter  if (apr_err)
1314251881Speter    {
1315251881Speter      svn_handle_error2(svn_error_wrap_apr
1316251881Speter                        (apr_err, _("Can't restore working directory")),
1317251881Speter                        stderr, TRUE /* fatal */, "svn: ");
1318251881Speter    }
1319251881Speter
1320251881Speter  return svn_error_trace(err);
1321251881Speter}
1322