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