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> 37289180Speter#include <conio.h> 38251881Speter#endif 39251881Speter 40251881Speter#include <apr.h> /* for STDIN_FILENO */ 41251881Speter#include <apr_errno.h> /* for apr_strerror */ 42369302Sdim#include <apr_version.h> 43369302Sdim#if APR_VERSION_AT_LEAST(1,5,0) 44362181Sdim#include <apr_escape.h> 45369302Sdim#else 46369302Sdim#include "private/svn_dep_compat.h" 47369302Sdim#endif 48251881Speter#include <apr_general.h> /* for apr_initialize/apr_terminate */ 49251881Speter#include <apr_strings.h> /* for apr_snprintf */ 50369302Sdim#include <apr_env.h> /* for apr_env_get */ 51251881Speter#include <apr_pools.h> 52362181Sdim#include <apr_signal.h> 53251881Speter 54251881Speter#include "svn_cmdline.h" 55251881Speter#include "svn_ctype.h" 56251881Speter#include "svn_dso.h" 57251881Speter#include "svn_dirent_uri.h" 58251881Speter#include "svn_hash.h" 59251881Speter#include "svn_path.h" 60251881Speter#include "svn_pools.h" 61251881Speter#include "svn_error.h" 62251881Speter#include "svn_nls.h" 63251881Speter#include "svn_utf.h" 64251881Speter#include "svn_auth.h" 65251881Speter#include "svn_xml.h" 66251881Speter#include "svn_base64.h" 67251881Speter#include "svn_config.h" 68251881Speter#include "svn_sorts.h" 69251881Speter#include "svn_props.h" 70251881Speter#include "svn_subst.h" 71251881Speter 72251881Speter#include "private/svn_cmdline_private.h" 73251881Speter#include "private/svn_utf_private.h" 74289180Speter#include "private/svn_sorts_private.h" 75251881Speter#include "private/svn_string_private.h" 76251881Speter 77251881Speter#include "svn_private_config.h" 78251881Speter 79251881Speter#include "win32_crashrpt.h" 80251881Speter 81289180Speter#if defined(WIN32) && defined(_MSC_VER) && (_MSC_VER < 1400) 82289180Speter/* Before Visual Studio 2005, the C runtime didn't handle encodings for the 83289180Speter for the stdio output handling. */ 84289180Speter#define CMDLINE_USE_CUSTOM_ENCODING 85289180Speter 86251881Speter/* The stdin encoding. If null, it's the same as the native encoding. */ 87251881Speterstatic const char *input_encoding = NULL; 88251881Speter 89251881Speter/* The stdout encoding. If null, it's the same as the native encoding. */ 90251881Speterstatic const char *output_encoding = NULL; 91289180Speter#elif defined(WIN32) && defined(_MSC_VER) 92289180Speter/* For now limit this code to Visual C++, as the result is highly dependent 93289180Speter on the CRT implementation */ 94289180Speter#define USE_WIN32_CONSOLE_SHORTCUT 95251881Speter 96289180Speter/* When TRUE, stdout/stderr is directly connected to a console */ 97289180Speterstatic svn_boolean_t shortcut_stdout_to_console = FALSE; 98289180Speterstatic svn_boolean_t shortcut_stderr_to_console = FALSE; 99289180Speter#endif 100251881Speter 101289180Speter 102251881Speterint 103251881Spetersvn_cmdline_init(const char *progname, FILE *error_stream) 104251881Speter{ 105251881Speter apr_status_t status; 106251881Speter apr_pool_t *pool; 107251881Speter svn_error_t *err; 108251881Speter char prefix_buf[64]; /* 64 is probably bigger than most program names */ 109251881Speter 110251881Speter#ifndef WIN32 111251881Speter { 112251881Speter struct stat st; 113251881Speter 114251881Speter /* The following makes sure that file descriptors 0 (stdin), 1 115251881Speter (stdout) and 2 (stderr) will not be "reused", because if 116251881Speter e.g. file descriptor 2 would be reused when opening a file, a 117251881Speter write to stderr would write to that file and most likely 118251881Speter corrupt it. */ 119251881Speter if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) || 120251881Speter (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) || 121251881Speter (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1)) 122251881Speter { 123251881Speter if (error_stream) 124251881Speter fprintf(error_stream, "%s: error: cannot open '/dev/null'\n", 125251881Speter progname); 126251881Speter return EXIT_FAILURE; 127251881Speter } 128251881Speter } 129251881Speter#endif 130251881Speter 131251881Speter /* Ignore any errors encountered while attempting to change stream 132251881Speter buffering, as the streams should retain their default buffering 133251881Speter modes. */ 134251881Speter if (error_stream) 135251881Speter setvbuf(error_stream, NULL, _IONBF, 0); 136251881Speter#ifndef WIN32 137251881Speter setvbuf(stdout, NULL, _IOLBF, 0); 138251881Speter#endif 139251881Speter 140251881Speter#ifdef WIN32 141289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING 142251881Speter /* Initialize the input and output encodings. */ 143251881Speter { 144251881Speter static char input_encoding_buffer[16]; 145251881Speter static char output_encoding_buffer[16]; 146251881Speter 147251881Speter apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer, 148251881Speter "CP%u", (unsigned) GetConsoleCP()); 149251881Speter input_encoding = input_encoding_buffer; 150251881Speter 151251881Speter apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer, 152251881Speter "CP%u", (unsigned) GetConsoleOutputCP()); 153251881Speter output_encoding = output_encoding_buffer; 154251881Speter } 155289180Speter#endif /* CMDLINE_USE_CUSTOM_ENCODING */ 156251881Speter 157251881Speter#ifdef SVN_USE_WIN32_CRASHHANDLER 158289180Speter if (!getenv("SVN_CMDLINE_DISABLE_CRASH_HANDLER")) 159289180Speter { 160289180Speter /* Attach (but don't load) the crash handler */ 161289180Speter SetUnhandledExceptionFilter(svn__unhandled_exception_filter); 162251881Speter 163251881Speter#if _MSC_VER >= 1400 164289180Speter /* ### This should work for VC++ 2002 (=1300) and later */ 165289180Speter /* Show the abort message on STDERR instead of a dialog to allow 166289180Speter scripts (e.g. our testsuite) to continue after an abort without 167289180Speter user intervention. Allow overriding for easier debugging. */ 168289180Speter if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT")) 169289180Speter { 170289180Speter /* In release mode: Redirect abort() errors to stderr */ 171289180Speter _set_error_mode(_OUT_TO_STDERR); 172251881Speter 173289180Speter /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr. 174289180Speter (Ignored in release builds) */ 175289180Speter _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR); 176289180Speter _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR); 177289180Speter _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR); 178289180Speter _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 179289180Speter _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 180289180Speter _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 181289180Speter } 182289180Speter#endif /* _MSC_VER >= 1400 */ 183251881Speter } 184251881Speter#endif /* SVN_USE_WIN32_CRASHHANDLER */ 185251881Speter 186251881Speter#endif /* WIN32 */ 187251881Speter 188251881Speter /* C programs default to the "C" locale. But because svn is supposed 189251881Speter to be i18n-aware, it should inherit the default locale of its 190251881Speter environment. */ 191251881Speter if (!setlocale(LC_ALL, "") 192251881Speter && !setlocale(LC_CTYPE, "")) 193251881Speter { 194251881Speter if (error_stream) 195251881Speter { 196251881Speter const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL }; 197251881Speter const char **env_var = &env_vars[0], *env_val = NULL; 198251881Speter while (*env_var) 199251881Speter { 200251881Speter env_val = getenv(*env_var); 201251881Speter if (env_val && env_val[0]) 202251881Speter break; 203251881Speter ++env_var; 204251881Speter } 205251881Speter 206251881Speter if (!*env_var) 207251881Speter { 208251881Speter /* Unlikely. Can setlocale fail if no env vars are set? */ 209251881Speter --env_var; 210251881Speter env_val = "not set"; 211251881Speter } 212251881Speter 213251881Speter fprintf(error_stream, 214251881Speter "%s: warning: cannot set LC_CTYPE locale\n" 215251881Speter "%s: warning: environment variable %s is %s\n" 216251881Speter "%s: warning: please check that your locale name is correct\n", 217251881Speter progname, progname, *env_var, env_val, progname); 218251881Speter } 219251881Speter } 220251881Speter 221251881Speter /* Initialize the APR subsystem, and register an atexit() function 222251881Speter to Uninitialize that subsystem at program exit. */ 223251881Speter status = apr_initialize(); 224251881Speter if (status) 225251881Speter { 226251881Speter if (error_stream) 227251881Speter { 228251881Speter char buf[1024]; 229251881Speter apr_strerror(status, buf, sizeof(buf) - 1); 230251881Speter fprintf(error_stream, 231251881Speter "%s: error: cannot initialize APR: %s\n", 232251881Speter progname, buf); 233251881Speter } 234251881Speter return EXIT_FAILURE; 235251881Speter } 236251881Speter 237251881Speter strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3); 238251881Speter prefix_buf[sizeof(prefix_buf) - 3] = '\0'; 239251881Speter strcat(prefix_buf, ": "); 240251881Speter 241251881Speter /* DSO pool must be created before any other pools used by the 242251881Speter application so that pool cleanup doesn't unload DSOs too 243251881Speter early. See docstring of svn_dso_initialize2(). */ 244251881Speter if ((err = svn_dso_initialize2())) 245251881Speter { 246251881Speter if (error_stream) 247251881Speter svn_handle_error2(err, error_stream, TRUE, prefix_buf); 248251881Speter 249251881Speter svn_error_clear(err); 250251881Speter return EXIT_FAILURE; 251251881Speter } 252251881Speter 253251881Speter if (0 > atexit(apr_terminate)) 254251881Speter { 255251881Speter if (error_stream) 256251881Speter fprintf(error_stream, 257251881Speter "%s: error: atexit registration failed\n", 258251881Speter progname); 259251881Speter return EXIT_FAILURE; 260251881Speter } 261251881Speter 262251881Speter /* Create a pool for use by the UTF-8 routines. It will be cleaned 263251881Speter up by APR at exit time. */ 264251881Speter pool = svn_pool_create(NULL); 265251881Speter svn_utf_initialize2(FALSE, pool); 266251881Speter 267251881Speter if ((err = svn_nls_init())) 268251881Speter { 269251881Speter if (error_stream) 270251881Speter svn_handle_error2(err, error_stream, TRUE, prefix_buf); 271251881Speter 272251881Speter svn_error_clear(err); 273251881Speter return EXIT_FAILURE; 274251881Speter } 275251881Speter 276289180Speter#ifdef USE_WIN32_CONSOLE_SHORTCUT 277289180Speter if (_isatty(STDOUT_FILENO)) 278289180Speter { 279289180Speter DWORD ignored; 280289180Speter HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); 281289180Speter 282289180Speter /* stdout is a char device handle, but is it the console? */ 283289180Speter if (GetConsoleMode(stdout_handle, &ignored)) 284289180Speter shortcut_stdout_to_console = TRUE; 285289180Speter 286289180Speter /* Don't close stdout_handle */ 287289180Speter } 288289180Speter if (_isatty(STDERR_FILENO)) 289289180Speter { 290289180Speter DWORD ignored; 291289180Speter HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); 292289180Speter 293289180Speter /* stderr is a char device handle, but is it the console? */ 294289180Speter if (GetConsoleMode(stderr_handle, &ignored)) 295289180Speter shortcut_stderr_to_console = TRUE; 296289180Speter 297289180Speter /* Don't close stderr_handle */ 298289180Speter } 299289180Speter#endif 300289180Speter 301251881Speter return EXIT_SUCCESS; 302251881Speter} 303251881Speter 304251881Speter 305251881Spetersvn_error_t * 306251881Spetersvn_cmdline_cstring_from_utf8(const char **dest, 307251881Speter const char *src, 308251881Speter apr_pool_t *pool) 309251881Speter{ 310289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING 311289180Speter if (output_encoding != NULL) 312251881Speter return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool); 313289180Speter#endif 314289180Speter 315289180Speter return svn_utf_cstring_from_utf8(dest, src, pool); 316251881Speter} 317251881Speter 318251881Speter 319251881Speterconst char * 320251881Spetersvn_cmdline_cstring_from_utf8_fuzzy(const char *src, 321251881Speter apr_pool_t *pool) 322251881Speter{ 323251881Speter return svn_utf__cstring_from_utf8_fuzzy(src, pool, 324251881Speter svn_cmdline_cstring_from_utf8); 325251881Speter} 326251881Speter 327251881Speter 328251881Spetersvn_error_t * 329251881Spetersvn_cmdline_cstring_to_utf8(const char **dest, 330251881Speter const char *src, 331251881Speter apr_pool_t *pool) 332251881Speter{ 333289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING 334289180Speter if (input_encoding != NULL) 335251881Speter return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool); 336289180Speter#endif 337289180Speter 338289180Speter return svn_utf_cstring_to_utf8(dest, src, pool); 339251881Speter} 340251881Speter 341251881Speter 342251881Spetersvn_error_t * 343251881Spetersvn_cmdline_path_local_style_from_utf8(const char **dest, 344251881Speter const char *src, 345251881Speter apr_pool_t *pool) 346251881Speter{ 347251881Speter return svn_cmdline_cstring_from_utf8(dest, 348251881Speter svn_dirent_local_style(src, pool), 349251881Speter pool); 350251881Speter} 351251881Speter 352251881Spetersvn_error_t * 353362181Sdimsvn_cmdline__stdin_readline(const char **result, 354362181Sdim apr_pool_t *result_pool, 355362181Sdim apr_pool_t *scratch_pool) 356362181Sdim{ 357362181Sdim svn_stringbuf_t *buf = NULL; 358362181Sdim svn_stream_t *stdin_stream = NULL; 359362181Sdim svn_boolean_t oob = FALSE; 360362181Sdim 361362181Sdim SVN_ERR(svn_stream_for_stdin2(&stdin_stream, TRUE, scratch_pool)); 362362181Sdim SVN_ERR(svn_stream_readline(stdin_stream, &buf, APR_EOL_STR, &oob, result_pool)); 363362181Sdim 364362181Sdim *result = buf->data; 365362181Sdim 366362181Sdim return SVN_NO_ERROR; 367362181Sdim} 368362181Sdim 369362181Sdimsvn_error_t * 370251881Spetersvn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...) 371251881Speter{ 372251881Speter const char *message; 373251881Speter va_list ap; 374251881Speter 375251881Speter /* A note about encoding issues: 376251881Speter * APR uses the execution character set, but here we give it UTF-8 strings, 377251881Speter * both the fmt argument and any other string arguments. Since apr_pvsprintf 378251881Speter * only cares about and produces ASCII characters, this works under the 379251881Speter * assumption that all supported platforms use an execution character set 380251881Speter * with ASCII as a subset. 381251881Speter */ 382251881Speter 383251881Speter va_start(ap, fmt); 384251881Speter message = apr_pvsprintf(pool, fmt, ap); 385251881Speter va_end(ap); 386251881Speter 387251881Speter return svn_cmdline_fputs(message, stdout, pool); 388251881Speter} 389251881Speter 390251881Spetersvn_error_t * 391251881Spetersvn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...) 392251881Speter{ 393251881Speter const char *message; 394251881Speter va_list ap; 395251881Speter 396251881Speter /* See svn_cmdline_printf () for a note about character encoding issues. */ 397251881Speter 398251881Speter va_start(ap, fmt); 399251881Speter message = apr_pvsprintf(pool, fmt, ap); 400251881Speter va_end(ap); 401251881Speter 402251881Speter return svn_cmdline_fputs(message, stream, pool); 403251881Speter} 404251881Speter 405251881Spetersvn_error_t * 406251881Spetersvn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool) 407251881Speter{ 408251881Speter svn_error_t *err; 409251881Speter const char *out; 410251881Speter 411289180Speter#ifdef USE_WIN32_CONSOLE_SHORTCUT 412289180Speter /* For legacy reasons the Visual C++ runtime converts output to the console 413289180Speter from the native 'ansi' encoding, to unicode, then back to 'ansi' and then 414289180Speter onwards to the console which is implemented as unicode. 415289180Speter 416289180Speter For operations like 'svn status -v' this may cause about 70% of the total 417289180Speter processing time, with absolutely no gain. 418289180Speter 419289180Speter For this specific scenario this shortcut exists. It has the nice side 420289180Speter effect of allowing full unicode output to the console. 421289180Speter 422289180Speter Note that this shortcut is not used when the output is redirected, as in 423289180Speter that case the data is put on the pipe/file after the first conversion to 424289180Speter ansi. In this case the most expensive conversion is already avoided. 425289180Speter */ 426289180Speter if ((stream == stdout && shortcut_stdout_to_console) 427289180Speter || (stream == stderr && shortcut_stderr_to_console)) 428289180Speter { 429289180Speter WCHAR *result; 430289180Speter 431289180Speter if (string[0] == '\0') 432289180Speter return SVN_NO_ERROR; 433289180Speter 434289180Speter SVN_ERR(svn_cmdline_fflush(stream)); /* Flush existing output */ 435289180Speter 436289180Speter SVN_ERR(svn_utf__win32_utf8_to_utf16(&result, string, NULL, pool)); 437289180Speter 438289180Speter if (_cputws(result)) 439289180Speter { 440289180Speter if (apr_get_os_error()) 441289180Speter { 442289180Speter return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 443289180Speter } 444289180Speter } 445289180Speter 446289180Speter return SVN_NO_ERROR; 447289180Speter } 448289180Speter#endif 449289180Speter 450251881Speter err = svn_cmdline_cstring_from_utf8(&out, string, pool); 451251881Speter 452251881Speter if (err) 453251881Speter { 454251881Speter svn_error_clear(err); 455251881Speter out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool); 456251881Speter } 457251881Speter 458251881Speter /* On POSIX systems, errno will be set on an error in fputs, but this might 459251881Speter not be the case on other platforms. We reset errno and only 460251881Speter use it if it was set by the below fputs call. Else, we just return 461251881Speter a generic error. */ 462251881Speter errno = 0; 463251881Speter 464251881Speter if (fputs(out, stream) == EOF) 465251881Speter { 466251881Speter if (apr_get_os_error()) /* is errno on POSIX */ 467251881Speter { 468251881Speter /* ### Issue #3014: Return a specific error for broken pipes, 469251881Speter * ### with a single element in the error chain. */ 470257936Speter if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error())) 471251881Speter return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); 472251881Speter else 473251881Speter return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 474251881Speter } 475251881Speter else 476251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 477251881Speter } 478251881Speter 479251881Speter return SVN_NO_ERROR; 480251881Speter} 481251881Speter 482251881Spetersvn_error_t * 483251881Spetersvn_cmdline_fflush(FILE *stream) 484251881Speter{ 485251881Speter /* See comment in svn_cmdline_fputs about use of errno and stdio. */ 486251881Speter errno = 0; 487251881Speter if (fflush(stream) == EOF) 488251881Speter { 489251881Speter if (apr_get_os_error()) /* is errno on POSIX */ 490251881Speter { 491251881Speter /* ### Issue #3014: Return a specific error for broken pipes, 492251881Speter * ### with a single element in the error chain. */ 493257936Speter if (SVN__APR_STATUS_IS_EPIPE(apr_get_os_error())) 494251881Speter return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); 495251881Speter else 496251881Speter return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 497251881Speter } 498251881Speter else 499251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 500251881Speter } 501251881Speter 502251881Speter return SVN_NO_ERROR; 503251881Speter} 504251881Speter 505251881Speterconst char *svn_cmdline_output_encoding(apr_pool_t *pool) 506251881Speter{ 507289180Speter#ifdef CMDLINE_USE_CUSTOM_ENCODING 508251881Speter if (output_encoding) 509251881Speter return apr_pstrdup(pool, output_encoding); 510289180Speter#endif 511289180Speter 512289180Speter return SVN_APR_LOCALE_CHARSET; 513251881Speter} 514251881Speter 515251881Speterint 516251881Spetersvn_cmdline_handle_exit_error(svn_error_t *err, 517251881Speter apr_pool_t *pool, 518251881Speter const char *prefix) 519251881Speter{ 520251881Speter /* Issue #3014: 521251881Speter * Don't print anything on broken pipes. The pipe was likely 522251881Speter * closed by the process at the other end. We expect that 523251881Speter * process to perform error reporting as necessary. 524251881Speter * 525251881Speter * ### This assumes that there is only one error in a chain for 526251881Speter * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ 527251881Speter if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) 528251881Speter svn_handle_error2(err, stderr, FALSE, prefix); 529251881Speter svn_error_clear(err); 530251881Speter if (pool) 531251881Speter svn_pool_destroy(pool); 532251881Speter return EXIT_FAILURE; 533251881Speter} 534251881Speter 535289180Speterstruct trust_server_cert_non_interactive_baton { 536289180Speter svn_boolean_t trust_server_cert_unknown_ca; 537289180Speter svn_boolean_t trust_server_cert_cn_mismatch; 538289180Speter svn_boolean_t trust_server_cert_expired; 539289180Speter svn_boolean_t trust_server_cert_not_yet_valid; 540289180Speter svn_boolean_t trust_server_cert_other_failure; 541289180Speter}; 542289180Speter 543251881Speter/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. 544251881Speter 545251881Speter Don't actually prompt. Instead, set *CRED_P to valid credentials 546289180Speter iff FAILURES is empty or may be accepted according to the flags 547289180Speter in BATON. If there are any other failure bits, then set *CRED_P 548289180Speter to null (that is, reject the cert). 549251881Speter 550251881Speter Ignore MAY_SAVE; we don't save certs we never prompted for. 551251881Speter 552289180Speter Ignore REALM and CERT_INFO, 553251881Speter 554251881Speter Ignore any further films by George Lucas. */ 555251881Speterstatic svn_error_t * 556289180Spetertrust_server_cert_non_interactive(svn_auth_cred_ssl_server_trust_t **cred_p, 557289180Speter void *baton, 558289180Speter const char *realm, 559289180Speter apr_uint32_t failures, 560289180Speter const svn_auth_ssl_server_cert_info_t 561289180Speter *cert_info, 562289180Speter svn_boolean_t may_save, 563289180Speter apr_pool_t *pool) 564251881Speter{ 565289180Speter struct trust_server_cert_non_interactive_baton *b = baton; 566289180Speter apr_uint32_t non_ignored_failures; 567251881Speter *cred_p = NULL; 568251881Speter 569289180Speter /* Mask away bits we are instructed to ignore. */ 570289180Speter non_ignored_failures = failures & ~( 571289180Speter (b->trust_server_cert_unknown_ca ? SVN_AUTH_SSL_UNKNOWNCA : 0) 572289180Speter | (b->trust_server_cert_cn_mismatch ? SVN_AUTH_SSL_CNMISMATCH : 0) 573289180Speter | (b->trust_server_cert_expired ? SVN_AUTH_SSL_EXPIRED : 0) 574289180Speter | (b->trust_server_cert_not_yet_valid ? SVN_AUTH_SSL_NOTYETVALID : 0) 575289180Speter | (b->trust_server_cert_other_failure ? SVN_AUTH_SSL_OTHER : 0) 576289180Speter ); 577289180Speter 578289180Speter /* If no failures remain, accept the certificate. */ 579289180Speter if (non_ignored_failures == 0) 580251881Speter { 581251881Speter *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); 582251881Speter (*cred_p)->may_save = FALSE; 583251881Speter (*cred_p)->accepted_failures = failures; 584251881Speter } 585251881Speter 586251881Speter return SVN_NO_ERROR; 587251881Speter} 588251881Speter 589251881Spetersvn_error_t * 590289180Spetersvn_cmdline_create_auth_baton2(svn_auth_baton_t **ab, 591289180Speter svn_boolean_t non_interactive, 592289180Speter const char *auth_username, 593289180Speter const char *auth_password, 594289180Speter const char *config_dir, 595289180Speter svn_boolean_t no_auth_cache, 596289180Speter svn_boolean_t trust_server_cert_unknown_ca, 597289180Speter svn_boolean_t trust_server_cert_cn_mismatch, 598289180Speter svn_boolean_t trust_server_cert_expired, 599289180Speter svn_boolean_t trust_server_cert_not_yet_valid, 600289180Speter svn_boolean_t trust_server_cert_other_failure, 601289180Speter svn_config_t *cfg, 602289180Speter svn_cancel_func_t cancel_func, 603289180Speter void *cancel_baton, 604289180Speter apr_pool_t *pool) 605289180Speter 606251881Speter{ 607251881Speter svn_boolean_t store_password_val = TRUE; 608251881Speter svn_boolean_t store_auth_creds_val = TRUE; 609251881Speter svn_auth_provider_object_t *provider; 610251881Speter svn_cmdline_prompt_baton2_t *pb = NULL; 611251881Speter 612251881Speter /* The whole list of registered providers */ 613251881Speter apr_array_header_t *providers; 614251881Speter 615251881Speter /* Populate the registered providers with the platform-specific providers */ 616251881Speter SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers, 617251881Speter cfg, pool)); 618251881Speter 619251881Speter /* If we have a cancellation function, cram it and the stuff it 620251881Speter needs into the prompt baton. */ 621251881Speter if (cancel_func) 622251881Speter { 623251881Speter pb = apr_palloc(pool, sizeof(*pb)); 624251881Speter pb->cancel_func = cancel_func; 625251881Speter pb->cancel_baton = cancel_baton; 626251881Speter pb->config_dir = config_dir; 627251881Speter } 628251881Speter 629251881Speter if (!non_interactive) 630251881Speter { 631251881Speter /* This provider doesn't prompt the user in order to get creds; 632251881Speter it prompts the user regarding the caching of creds. */ 633251881Speter svn_auth_get_simple_provider2(&provider, 634251881Speter svn_cmdline_auth_plaintext_prompt, 635251881Speter pb, pool); 636251881Speter } 637251881Speter else 638251881Speter { 639251881Speter svn_auth_get_simple_provider2(&provider, NULL, NULL, pool); 640251881Speter } 641251881Speter 642251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 643251881Speter svn_auth_get_username_provider(&provider, pool); 644251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 645251881Speter 646251881Speter svn_auth_get_ssl_server_trust_file_provider(&provider, pool); 647251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 648251881Speter svn_auth_get_ssl_client_cert_file_provider(&provider, pool); 649251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 650251881Speter 651251881Speter if (!non_interactive) 652251881Speter { 653251881Speter /* This provider doesn't prompt the user in order to get creds; 654251881Speter it prompts the user regarding the caching of creds. */ 655251881Speter svn_auth_get_ssl_client_cert_pw_file_provider2 656251881Speter (&provider, svn_cmdline_auth_plaintext_passphrase_prompt, 657251881Speter pb, pool); 658251881Speter } 659251881Speter else 660251881Speter { 661251881Speter svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL, 662251881Speter pool); 663251881Speter } 664251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 665251881Speter 666251881Speter if (!non_interactive) 667251881Speter { 668251881Speter svn_boolean_t ssl_client_cert_file_prompt; 669251881Speter 670251881Speter SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt, 671251881Speter SVN_CONFIG_SECTION_AUTH, 672251881Speter SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT, 673251881Speter FALSE)); 674251881Speter 675251881Speter /* Two basic prompt providers: username/password, and just username. */ 676251881Speter svn_auth_get_simple_prompt_provider(&provider, 677251881Speter svn_cmdline_auth_simple_prompt, 678251881Speter pb, 679251881Speter 2, /* retry limit */ 680251881Speter pool); 681251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 682251881Speter 683251881Speter svn_auth_get_username_prompt_provider 684251881Speter (&provider, svn_cmdline_auth_username_prompt, pb, 685251881Speter 2, /* retry limit */ pool); 686251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 687251881Speter 688251881Speter /* SSL prompt providers: server-certs and client-cert-passphrases. */ 689251881Speter svn_auth_get_ssl_server_trust_prompt_provider 690251881Speter (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool); 691251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 692251881Speter 693251881Speter svn_auth_get_ssl_client_cert_pw_prompt_provider 694251881Speter (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool); 695251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 696251881Speter 697251881Speter /* If configuration allows, add a provider for client-cert path 698251881Speter prompting, too. */ 699251881Speter if (ssl_client_cert_file_prompt) 700251881Speter { 701251881Speter svn_auth_get_ssl_client_cert_prompt_provider 702251881Speter (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool); 703251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 704251881Speter } 705251881Speter } 706289180Speter else if (trust_server_cert_unknown_ca || trust_server_cert_cn_mismatch || 707289180Speter trust_server_cert_expired || trust_server_cert_not_yet_valid || 708289180Speter trust_server_cert_other_failure) 709251881Speter { 710289180Speter struct trust_server_cert_non_interactive_baton *b; 711289180Speter 712289180Speter b = apr_palloc(pool, sizeof(*b)); 713289180Speter b->trust_server_cert_unknown_ca = trust_server_cert_unknown_ca; 714289180Speter b->trust_server_cert_cn_mismatch = trust_server_cert_cn_mismatch; 715289180Speter b->trust_server_cert_expired = trust_server_cert_expired; 716289180Speter b->trust_server_cert_not_yet_valid = trust_server_cert_not_yet_valid; 717289180Speter b->trust_server_cert_other_failure = trust_server_cert_other_failure; 718289180Speter 719251881Speter /* Remember, only register this provider if non_interactive. */ 720251881Speter svn_auth_get_ssl_server_trust_prompt_provider 721289180Speter (&provider, trust_server_cert_non_interactive, b, pool); 722251881Speter APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; 723251881Speter } 724251881Speter 725251881Speter /* Build an authentication baton to give to libsvn_client. */ 726251881Speter svn_auth_open(ab, providers, pool); 727251881Speter 728251881Speter /* Place any default --username or --password credentials into the 729251881Speter auth_baton's run-time parameter hash. */ 730251881Speter if (auth_username) 731251881Speter svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME, 732251881Speter auth_username); 733251881Speter if (auth_password) 734251881Speter svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD, 735251881Speter auth_password); 736251881Speter 737251881Speter /* Same with the --non-interactive option. */ 738251881Speter if (non_interactive) 739251881Speter svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, ""); 740251881Speter 741251881Speter if (config_dir) 742251881Speter svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR, 743251881Speter config_dir); 744251881Speter 745251881Speter /* Determine whether storing passwords in any form is allowed. 746251881Speter * This is the deprecated location for this option, the new 747251881Speter * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may 748251881Speter * override the value we set here. */ 749251881Speter SVN_ERR(svn_config_get_bool(cfg, &store_password_val, 750251881Speter SVN_CONFIG_SECTION_AUTH, 751251881Speter SVN_CONFIG_OPTION_STORE_PASSWORDS, 752251881Speter SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS)); 753251881Speter 754251881Speter if (! store_password_val) 755251881Speter svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, ""); 756251881Speter 757251881Speter /* Determine whether we are allowed to write to the auth/ area. 758251881Speter * This is the deprecated location for this option, the new 759251881Speter * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may 760251881Speter * override the value we set here. */ 761251881Speter SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val, 762251881Speter SVN_CONFIG_SECTION_AUTH, 763251881Speter SVN_CONFIG_OPTION_STORE_AUTH_CREDS, 764251881Speter SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS)); 765251881Speter 766251881Speter if (no_auth_cache || ! store_auth_creds_val) 767251881Speter svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, ""); 768251881Speter 769251881Speter#ifdef SVN_HAVE_GNOME_KEYRING 770251881Speter svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC, 771251881Speter &svn_cmdline__auth_gnome_keyring_unlock_prompt); 772251881Speter#endif /* SVN_HAVE_GNOME_KEYRING */ 773251881Speter 774251881Speter return SVN_NO_ERROR; 775251881Speter} 776251881Speter 777251881Spetersvn_error_t * 778251881Spetersvn_cmdline__getopt_init(apr_getopt_t **os, 779251881Speter int argc, 780251881Speter const char *argv[], 781251881Speter apr_pool_t *pool) 782251881Speter{ 783251881Speter apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv); 784251881Speter if (apr_err) 785251881Speter return svn_error_wrap_apr(apr_err, 786251881Speter _("Error initializing command line arguments")); 787251881Speter return SVN_NO_ERROR; 788251881Speter} 789251881Speter 790251881Speter 791251881Spetervoid 792251881Spetersvn_cmdline__print_xml_prop(svn_stringbuf_t **outstr, 793251881Speter const char* propname, 794251881Speter svn_string_t *propval, 795251881Speter svn_boolean_t inherited_prop, 796251881Speter apr_pool_t *pool) 797251881Speter{ 798251881Speter const char *xml_safe; 799251881Speter const char *encoding = NULL; 800251881Speter 801251881Speter if (*outstr == NULL) 802251881Speter *outstr = svn_stringbuf_create_empty(pool); 803251881Speter 804251881Speter if (svn_xml_is_xml_safe(propval->data, propval->len)) 805251881Speter { 806251881Speter svn_stringbuf_t *xml_esc = NULL; 807251881Speter svn_xml_escape_cdata_string(&xml_esc, propval, pool); 808251881Speter xml_safe = xml_esc->data; 809251881Speter } 810251881Speter else 811251881Speter { 812251881Speter const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE, 813251881Speter pool); 814251881Speter encoding = "base64"; 815251881Speter xml_safe = base64ed->data; 816251881Speter } 817251881Speter 818251881Speter if (encoding) 819251881Speter svn_xml_make_open_tag( 820251881Speter outstr, pool, svn_xml_protect_pcdata, 821251881Speter inherited_prop ? "inherited_property" : "property", 822251881Speter "name", propname, 823289180Speter "encoding", encoding, SVN_VA_NULL); 824251881Speter else 825251881Speter svn_xml_make_open_tag( 826251881Speter outstr, pool, svn_xml_protect_pcdata, 827251881Speter inherited_prop ? "inherited_property" : "property", 828289180Speter "name", propname, SVN_VA_NULL); 829251881Speter 830251881Speter svn_stringbuf_appendcstr(*outstr, xml_safe); 831251881Speter 832251881Speter svn_xml_make_close_tag( 833251881Speter outstr, pool, 834251881Speter inherited_prop ? "inherited_property" : "property"); 835251881Speter 836251881Speter return; 837251881Speter} 838251881Speter 839289180Speter/* Return the most similar string to NEEDLE in HAYSTACK, which contains 840289180Speter * HAYSTACK_LEN elements. Return NULL if no string is sufficiently similar. 841289180Speter */ 842289180Speter/* See svn_cl__similarity_check() for a more general solution. */ 843289180Speterstatic const char * 844289180Spetermost_similar(const char *needle_cstr, 845289180Speter const char **haystack, 846289180Speter apr_size_t haystack_len, 847289180Speter apr_pool_t *scratch_pool) 848289180Speter{ 849362181Sdim const char *max_similar = NULL; 850289180Speter apr_size_t max_score = 0; 851289180Speter apr_size_t i; 852289180Speter svn_membuf_t membuf; 853289180Speter svn_string_t *needle_str = svn_string_create(needle_cstr, scratch_pool); 854289180Speter 855289180Speter svn_membuf__create(&membuf, 64, scratch_pool); 856289180Speter 857289180Speter for (i = 0; i < haystack_len; i++) 858289180Speter { 859289180Speter apr_size_t score; 860289180Speter svn_string_t *hay = svn_string_create(haystack[i], scratch_pool); 861289180Speter 862289180Speter score = svn_string__similarity(needle_str, hay, &membuf, NULL); 863289180Speter 864289180Speter /* If you update this factor, consider updating 865289180Speter * svn_cl__similarity_check(). */ 866289180Speter if (score >= (2 * SVN_STRING__SIM_RANGE_MAX + 1) / 3 867289180Speter && score > max_score) 868289180Speter { 869289180Speter max_score = score; 870289180Speter max_similar = haystack[i]; 871289180Speter } 872289180Speter } 873289180Speter 874362181Sdim return max_similar; 875289180Speter} 876289180Speter 877289180Speter/* Verify that NEEDLE is in HAYSTACK, which contains HAYSTACK_LEN elements. */ 878289180Speterstatic svn_error_t * 879289180Speterstring_in_array(const char *needle, 880289180Speter const char **haystack, 881289180Speter apr_size_t haystack_len, 882289180Speter apr_pool_t *scratch_pool) 883289180Speter{ 884289180Speter const char *next_of_kin; 885289180Speter apr_size_t i; 886289180Speter for (i = 0; i < haystack_len; i++) 887289180Speter { 888289180Speter if (!strcmp(needle, haystack[i])) 889289180Speter return SVN_NO_ERROR; 890289180Speter } 891289180Speter 892289180Speter /* Error. */ 893289180Speter next_of_kin = most_similar(needle, haystack, haystack_len, scratch_pool); 894289180Speter if (next_of_kin) 895289180Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 896289180Speter _("Ignoring unknown value '%s'; " 897289180Speter "did you mean '%s'?"), 898289180Speter needle, next_of_kin); 899289180Speter else 900289180Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 901289180Speter _("Ignoring unknown value '%s'"), 902289180Speter needle); 903289180Speter} 904289180Speter 905289180Speter#include "config_keys.inc" 906289180Speter 907289180Speter/* Validate the FILE, SECTION, and OPTION components of CONFIG_OPTION are 908362181Sdim * known. Return an error if not. (An unknown value may be either a typo 909289180Speter * or added in a newer minor version of Subversion.) */ 910289180Speterstatic svn_error_t * 911289180Spetervalidate_config_option(svn_cmdline__config_argument_t *config_option, 912289180Speter apr_pool_t *scratch_pool) 913289180Speter{ 914289180Speter svn_boolean_t arbitrary_keys = FALSE; 915289180Speter 916289180Speter /* TODO: some day, we could also verify that OPTION is valid for SECTION; 917289180Speter i.e., forbid invalid combinations such as config:auth:diff-extensions. */ 918289180Speter 919289180Speter#define ARRAYLEN(x) ( sizeof((x)) / sizeof((x)[0]) ) 920289180Speter 921289180Speter SVN_ERR(string_in_array(config_option->file, svn__valid_config_files, 922289180Speter ARRAYLEN(svn__valid_config_files), 923289180Speter scratch_pool)); 924289180Speter SVN_ERR(string_in_array(config_option->section, svn__valid_config_sections, 925289180Speter ARRAYLEN(svn__valid_config_sections), 926289180Speter scratch_pool)); 927289180Speter 928289180Speter /* Don't validate option names for sections such as servers[group], 929289180Speter * config[tunnels], and config[auto-props] that permit arbitrary options. */ 930289180Speter { 931289180Speter int i; 932289180Speter 933289180Speter for (i = 0; i < ARRAYLEN(svn__empty_config_sections); i++) 934289180Speter { 935289180Speter if (!strcmp(config_option->section, svn__empty_config_sections[i])) 936289180Speter arbitrary_keys = TRUE; 937289180Speter } 938289180Speter } 939289180Speter 940289180Speter if (! arbitrary_keys) 941289180Speter SVN_ERR(string_in_array(config_option->option, svn__valid_config_options, 942289180Speter ARRAYLEN(svn__valid_config_options), 943289180Speter scratch_pool)); 944289180Speter 945289180Speter#undef ARRAYLEN 946289180Speter 947289180Speter return SVN_NO_ERROR; 948289180Speter} 949289180Speter 950251881Spetersvn_error_t * 951251881Spetersvn_cmdline__parse_config_option(apr_array_header_t *config_options, 952251881Speter const char *opt_arg, 953289180Speter const char *prefix, 954251881Speter apr_pool_t *pool) 955251881Speter{ 956251881Speter svn_cmdline__config_argument_t *config_option; 957251881Speter const char *first_colon, *second_colon, *equals_sign; 958251881Speter apr_size_t len = strlen(opt_arg); 959251881Speter if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg)) 960251881Speter { 961251881Speter if ((second_colon = strchr(first_colon + 1, ':')) && 962251881Speter (second_colon != first_colon + 1)) 963251881Speter { 964251881Speter if ((equals_sign = strchr(second_colon + 1, '=')) && 965251881Speter (equals_sign != second_colon + 1)) 966251881Speter { 967289180Speter svn_error_t *warning; 968289180Speter 969251881Speter config_option = apr_pcalloc(pool, sizeof(*config_option)); 970251881Speter config_option->file = apr_pstrndup(pool, opt_arg, 971251881Speter first_colon - opt_arg); 972251881Speter config_option->section = apr_pstrndup(pool, first_colon + 1, 973251881Speter second_colon - first_colon - 1); 974251881Speter config_option->option = apr_pstrndup(pool, second_colon + 1, 975251881Speter equals_sign - second_colon - 1); 976251881Speter 977289180Speter warning = validate_config_option(config_option, pool); 978289180Speter if (warning) 979289180Speter { 980289180Speter svn_handle_warning2(stderr, warning, prefix); 981289180Speter svn_error_clear(warning); 982289180Speter } 983289180Speter 984251881Speter if (! (strchr(config_option->option, ':'))) 985251881Speter { 986251881Speter config_option->value = apr_pstrndup(pool, equals_sign + 1, 987251881Speter opt_arg + len - equals_sign - 1); 988251881Speter APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *) 989251881Speter = config_option; 990251881Speter return SVN_NO_ERROR; 991251881Speter } 992251881Speter } 993251881Speter } 994251881Speter } 995251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 996251881Speter _("Invalid syntax of argument of --config-option")); 997251881Speter} 998251881Speter 999251881Spetersvn_error_t * 1000251881Spetersvn_cmdline__apply_config_options(apr_hash_t *config, 1001251881Speter const apr_array_header_t *config_options, 1002251881Speter const char *prefix, 1003251881Speter const char *argument_name) 1004251881Speter{ 1005251881Speter int i; 1006251881Speter 1007251881Speter for (i = 0; i < config_options->nelts; i++) 1008251881Speter { 1009251881Speter svn_config_t *cfg; 1010251881Speter svn_cmdline__config_argument_t *arg = 1011251881Speter APR_ARRAY_IDX(config_options, i, 1012251881Speter svn_cmdline__config_argument_t *); 1013251881Speter 1014251881Speter cfg = svn_hash_gets(config, arg->file); 1015251881Speter 1016251881Speter if (cfg) 1017251881Speter { 1018251881Speter svn_config_set(cfg, arg->section, arg->option, arg->value); 1019251881Speter } 1020251881Speter else 1021251881Speter { 1022251881Speter svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1023251881Speter _("Unrecognized file in argument of %s"), argument_name); 1024251881Speter 1025251881Speter svn_handle_warning2(stderr, err, prefix); 1026251881Speter svn_error_clear(err); 1027251881Speter } 1028251881Speter } 1029251881Speter 1030251881Speter return SVN_NO_ERROR; 1031251881Speter} 1032251881Speter 1033251881Speter/* Return a copy, allocated in POOL, of the next line of text from *STR 1034251881Speter * up to and including a CR and/or an LF. Change *STR to point to the 1035251881Speter * remainder of the string after the returned part. If there are no 1036251881Speter * characters to be returned, return NULL; never return an empty string. 1037251881Speter */ 1038251881Speterstatic const char * 1039251881Speternext_line(const char **str, apr_pool_t *pool) 1040251881Speter{ 1041251881Speter const char *start = *str; 1042251881Speter const char *p = *str; 1043251881Speter 1044251881Speter /* n.b. Throughout this fn, we never read any character after a '\0'. */ 1045251881Speter /* Skip over all non-EOL characters, if any. */ 1046251881Speter while (*p != '\r' && *p != '\n' && *p != '\0') 1047251881Speter p++; 1048251881Speter /* Skip over \r\n or \n\r or \r or \n, if any. */ 1049251881Speter if (*p == '\r' || *p == '\n') 1050251881Speter { 1051251881Speter char c = *p++; 1052251881Speter 1053251881Speter if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r')) 1054251881Speter p++; 1055251881Speter } 1056251881Speter 1057251881Speter /* Now p points after at most one '\n' and/or '\r'. */ 1058251881Speter *str = p; 1059251881Speter 1060251881Speter if (p == start) 1061251881Speter return NULL; 1062251881Speter 1063251881Speter return svn_string_ncreate(start, p - start, pool)->data; 1064251881Speter} 1065251881Speter 1066251881Speterconst char * 1067251881Spetersvn_cmdline__indent_string(const char *str, 1068251881Speter const char *indent, 1069251881Speter apr_pool_t *pool) 1070251881Speter{ 1071251881Speter svn_stringbuf_t *out = svn_stringbuf_create_empty(pool); 1072251881Speter const char *line; 1073251881Speter 1074251881Speter while ((line = next_line(&str, pool))) 1075251881Speter { 1076251881Speter svn_stringbuf_appendcstr(out, indent); 1077251881Speter svn_stringbuf_appendcstr(out, line); 1078251881Speter } 1079251881Speter return out->data; 1080251881Speter} 1081251881Speter 1082251881Spetersvn_error_t * 1083251881Spetersvn_cmdline__print_prop_hash(svn_stream_t *out, 1084251881Speter apr_hash_t *prop_hash, 1085251881Speter svn_boolean_t names_only, 1086251881Speter apr_pool_t *pool) 1087251881Speter{ 1088251881Speter apr_array_header_t *sorted_props; 1089251881Speter int i; 1090251881Speter 1091251881Speter sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, 1092251881Speter pool); 1093251881Speter for (i = 0; i < sorted_props->nelts; i++) 1094251881Speter { 1095251881Speter svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); 1096251881Speter const char *pname = item.key; 1097251881Speter svn_string_t *propval = item.value; 1098251881Speter const char *pname_stdout; 1099251881Speter 1100251881Speter if (svn_prop_needs_translation(pname)) 1101251881Speter SVN_ERR(svn_subst_detranslate_string(&propval, propval, 1102251881Speter TRUE, pool)); 1103251881Speter 1104251881Speter SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool)); 1105251881Speter 1106251881Speter if (out) 1107251881Speter { 1108251881Speter pname_stdout = apr_psprintf(pool, " %s\n", pname_stdout); 1109251881Speter SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout, 1110251881Speter APR_EOL_STR, /* 'native' eol */ 1111251881Speter FALSE, /* no repair */ 1112251881Speter NULL, /* no keywords */ 1113251881Speter FALSE, /* no expansion */ 1114251881Speter pool)); 1115251881Speter 1116251881Speter SVN_ERR(svn_stream_puts(out, pname_stdout)); 1117251881Speter } 1118251881Speter else 1119251881Speter { 1120251881Speter /* ### We leave these printfs for now, since if propval wasn't 1121251881Speter translated above, we don't know anything about its encoding. 1122251881Speter In fact, it might be binary data... */ 1123251881Speter printf(" %s\n", pname_stdout); 1124251881Speter } 1125251881Speter 1126251881Speter if (!names_only) 1127251881Speter { 1128251881Speter /* Add an extra newline to the value before indenting, so that 1129251881Speter * every line of output has the indentation whether the value 1130251881Speter * already ended in a newline or not. */ 1131251881Speter const char *newval = apr_psprintf(pool, "%s\n", propval->data); 1132251881Speter const char *indented_newval = svn_cmdline__indent_string(newval, 1133251881Speter " ", 1134251881Speter pool); 1135251881Speter if (out) 1136251881Speter { 1137251881Speter SVN_ERR(svn_stream_puts(out, indented_newval)); 1138251881Speter } 1139251881Speter else 1140251881Speter { 1141251881Speter printf("%s", indented_newval); 1142251881Speter } 1143251881Speter } 1144251881Speter } 1145251881Speter 1146251881Speter return SVN_NO_ERROR; 1147251881Speter} 1148251881Speter 1149251881Spetersvn_error_t * 1150251881Spetersvn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr, 1151251881Speter apr_hash_t *prop_hash, 1152251881Speter svn_boolean_t names_only, 1153251881Speter svn_boolean_t inherited_props, 1154251881Speter apr_pool_t *pool) 1155251881Speter{ 1156251881Speter apr_array_header_t *sorted_props; 1157251881Speter int i; 1158251881Speter 1159251881Speter if (*outstr == NULL) 1160251881Speter *outstr = svn_stringbuf_create_empty(pool); 1161251881Speter 1162251881Speter sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, 1163251881Speter pool); 1164251881Speter for (i = 0; i < sorted_props->nelts; i++) 1165251881Speter { 1166251881Speter svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); 1167251881Speter const char *pname = item.key; 1168251881Speter svn_string_t *propval = item.value; 1169251881Speter 1170251881Speter if (names_only) 1171251881Speter { 1172251881Speter svn_xml_make_open_tag( 1173251881Speter outstr, pool, svn_xml_self_closing, 1174251881Speter inherited_props ? "inherited_property" : "property", 1175289180Speter "name", pname, SVN_VA_NULL); 1176251881Speter } 1177251881Speter else 1178251881Speter { 1179251881Speter const char *pname_out; 1180251881Speter 1181251881Speter if (svn_prop_needs_translation(pname)) 1182251881Speter SVN_ERR(svn_subst_detranslate_string(&propval, propval, 1183251881Speter TRUE, pool)); 1184251881Speter 1185251881Speter SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool)); 1186251881Speter 1187251881Speter svn_cmdline__print_xml_prop(outstr, pname_out, propval, 1188251881Speter inherited_props, pool); 1189251881Speter } 1190251881Speter } 1191251881Speter 1192251881Speter return SVN_NO_ERROR; 1193251881Speter} 1194251881Speter 1195251881Spetersvn_boolean_t 1196362181Sdimsvn_cmdline__stdin_is_a_terminal(void) 1197362181Sdim{ 1198362181Sdim#ifdef WIN32 1199362181Sdim return (_isatty(STDIN_FILENO) != 0); 1200362181Sdim#else 1201362181Sdim return (isatty(STDIN_FILENO) != 0); 1202362181Sdim#endif 1203362181Sdim} 1204362181Sdim 1205362181Sdimsvn_boolean_t 1206362181Sdimsvn_cmdline__stdout_is_a_terminal(void) 1207362181Sdim{ 1208362181Sdim#ifdef WIN32 1209362181Sdim return (_isatty(STDOUT_FILENO) != 0); 1210362181Sdim#else 1211362181Sdim return (isatty(STDOUT_FILENO) != 0); 1212362181Sdim#endif 1213362181Sdim} 1214362181Sdim 1215362181Sdimsvn_boolean_t 1216362181Sdimsvn_cmdline__stderr_is_a_terminal(void) 1217362181Sdim{ 1218362181Sdim#ifdef WIN32 1219362181Sdim return (_isatty(STDERR_FILENO) != 0); 1220362181Sdim#else 1221362181Sdim return (isatty(STDERR_FILENO) != 0); 1222362181Sdim#endif 1223362181Sdim} 1224362181Sdim 1225362181Sdimsvn_boolean_t 1226251881Spetersvn_cmdline__be_interactive(svn_boolean_t non_interactive, 1227251881Speter svn_boolean_t force_interactive) 1228251881Speter{ 1229251881Speter /* If neither --non-interactive nor --force-interactive was passed, 1230251881Speter * be interactive if stdin is a terminal. 1231251881Speter * If --force-interactive was passed, always be interactive. */ 1232251881Speter if (!force_interactive && !non_interactive) 1233251881Speter { 1234362181Sdim return svn_cmdline__stdin_is_a_terminal(); 1235251881Speter } 1236251881Speter else if (force_interactive) 1237251881Speter return TRUE; 1238251881Speter 1239251881Speter return !non_interactive; 1240251881Speter} 1241251881Speter 1242251881Speter 1243362181Sdim/* Helper for the edit_externally functions. Set *EDITOR to some path to an 1244369302Sdim editor binary, in native C string on Unix/Linux platforms and in UTF-8 1245369302Sdim string on Windows platform. Sources to search include: the EDITOR_CMD 1246369302Sdim argument (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG 1247251881Speter is not NULL), $VISUAL, $EDITOR. Return 1248251881Speter SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */ 1249251881Speterstatic svn_error_t * 1250251881Speterfind_editor_binary(const char **editor, 1251251881Speter const char *editor_cmd, 1252369302Sdim apr_hash_t *config, 1253369302Sdim apr_pool_t *pool) 1254251881Speter{ 1255251881Speter const char *e; 1256369302Sdim const char *e_cfg; 1257251881Speter struct svn_config_t *cfg; 1258369302Sdim apr_status_t status; 1259251881Speter 1260251881Speter /* Use the editor specified on the command line via --editor-cmd, if any. */ 1261369302Sdim#ifdef WIN32 1262369302Sdim /* On Windows, editor_cmd is transcoded to the system active code page 1263369302Sdim because we use main() as a entry point without APR's (or our own) wrapper 1264369302Sdim in command line tools. */ 1265369302Sdim if (editor_cmd) 1266369302Sdim { 1267369302Sdim SVN_ERR(svn_utf_cstring_to_utf8(&e, editor_cmd, pool)); 1268369302Sdim } 1269369302Sdim else 1270369302Sdim { 1271369302Sdim e = NULL; 1272369302Sdim } 1273369302Sdim#else 1274251881Speter e = editor_cmd; 1275369302Sdim#endif 1276251881Speter 1277251881Speter /* Otherwise look for the Subversion-specific environment variable. */ 1278251881Speter if (! e) 1279369302Sdim { 1280369302Sdim status = apr_env_get((char **)&e, "SVN_EDITOR", pool); 1281369302Sdim if (status || ! *e) 1282369302Sdim { 1283369302Sdim e = NULL; 1284369302Sdim } 1285369302Sdim } 1286251881Speter 1287251881Speter /* If not found then fall back on the config file. */ 1288251881Speter if (! e) 1289251881Speter { 1290251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 1291369302Sdim svn_config_get(cfg, &e_cfg, SVN_CONFIG_SECTION_HELPERS, 1292251881Speter SVN_CONFIG_OPTION_EDITOR_CMD, NULL); 1293369302Sdim#ifdef WIN32 1294369302Sdim if (e_cfg) 1295369302Sdim { 1296369302Sdim /* On Windows, we assume that config values are set in system active 1297369302Sdim code page, so we need transcode it here. */ 1298369302Sdim SVN_ERR(svn_utf_cstring_to_utf8(&e, e_cfg, pool)); 1299369302Sdim } 1300369302Sdim#else 1301369302Sdim e = e_cfg; 1302369302Sdim#endif 1303251881Speter } 1304251881Speter 1305251881Speter /* If not found yet then try general purpose environment variables. */ 1306251881Speter if (! e) 1307369302Sdim { 1308369302Sdim status = apr_env_get((char**)&e, "VISUAL", pool); 1309369302Sdim if (status || ! *e) 1310369302Sdim { 1311369302Sdim e = NULL; 1312369302Sdim } 1313369302Sdim } 1314251881Speter if (! e) 1315369302Sdim { 1316369302Sdim status = apr_env_get((char**)&e, "EDITOR", pool); 1317369302Sdim if (status || ! *e) 1318369302Sdim { 1319369302Sdim e = NULL; 1320369302Sdim } 1321369302Sdim } 1322251881Speter 1323251881Speter#ifdef SVN_CLIENT_EDITOR 1324251881Speter /* If still not found then fall back on the hard-coded default. */ 1325251881Speter if (! e) 1326251881Speter e = SVN_CLIENT_EDITOR; 1327251881Speter#endif 1328251881Speter 1329251881Speter /* Error if there is no editor specified */ 1330251881Speter if (e) 1331251881Speter { 1332251881Speter const char *c; 1333251881Speter 1334251881Speter for (c = e; *c; c++) 1335251881Speter if (!svn_ctype_isspace(*c)) 1336251881Speter break; 1337251881Speter 1338251881Speter if (! *c) 1339251881Speter return svn_error_create 1340251881Speter (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, 1341251881Speter _("The EDITOR, SVN_EDITOR or VISUAL environment variable or " 1342251881Speter "'editor-cmd' run-time configuration option is empty or " 1343251881Speter "consists solely of whitespace. Expected a shell command.")); 1344251881Speter } 1345251881Speter else 1346251881Speter return svn_error_create 1347251881Speter (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, 1348251881Speter _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are " 1349251881Speter "set, and no 'editor-cmd' run-time configuration option was found")); 1350251881Speter 1351251881Speter *editor = e; 1352251881Speter return SVN_NO_ERROR; 1353251881Speter} 1354251881Speter 1355362181Sdim/* Wrapper around apr_pescape_shell() which also escapes whitespace. */ 1356362181Sdimstatic const char * 1357362181Sdimescape_path(apr_pool_t *pool, const char *orig_path) 1358362181Sdim{ 1359362181Sdim apr_size_t len, esc_len; 1360362181Sdim apr_status_t status; 1361251881Speter 1362362181Sdim len = strlen(orig_path); 1363362181Sdim esc_len = 0; 1364362181Sdim 1365362181Sdim status = apr_escape_shell(NULL, orig_path, len, &esc_len); 1366362181Sdim 1367362181Sdim if (status == APR_NOTFOUND) 1368362181Sdim { 1369362181Sdim /* No special characters found by APR, so just surround it in double 1370362181Sdim quotes in case there is whitespace, which APR (as of 1.6.5) doesn't 1371362181Sdim consider special. */ 1372362181Sdim return apr_psprintf(pool, "\"%s\"", orig_path); 1373362181Sdim } 1374362181Sdim else 1375362181Sdim { 1376362181Sdim#ifdef WIN32 1377362181Sdim const char *p; 1378362181Sdim /* Following the advice from 1379362181Sdim https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way 1380362181Sdim 1. Surround argument with double-quotes 1381362181Sdim 2. Escape backslashes, if they're followed by a double-quote, and double-quotes 1382362181Sdim 3. Escape any metacharacter, including double-quotes, with ^ */ 1383362181Sdim 1384362181Sdim /* Use APR's buffer size as an approximation for how large the escaped 1385362181Sdim string should be, plus 4 bytes for the leading/trailing ^" */ 1386362181Sdim svn_stringbuf_t *buf = svn_stringbuf_create_ensure(esc_len + 4, pool); 1387362181Sdim svn_stringbuf_appendcstr(buf, "^\""); 1388362181Sdim for (p = orig_path; *p; p++) 1389362181Sdim { 1390362181Sdim int nr_backslash = 0; 1391362181Sdim while (*p && *p == '\\') 1392362181Sdim { 1393362181Sdim nr_backslash++; 1394362181Sdim p++; 1395362181Sdim } 1396362181Sdim 1397362181Sdim if (!*p) 1398362181Sdim /* We've reached the end of the argument, so we need 2n backslash 1399362181Sdim characters. That will be interpreted as n backslashes and the 1400362181Sdim final double-quote character will be interpreted as the final 1401362181Sdim string delimiter. */ 1402362181Sdim svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2); 1403362181Sdim else if (*p == '"') 1404362181Sdim { 1405362181Sdim /* Double-quote as part of the argument means we need to double 1406362181Sdim any preceeding backslashes and then add one to escape the 1407362181Sdim double-quote. */ 1408362181Sdim svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2 + 1); 1409362181Sdim svn_stringbuf_appendbyte(buf, '^'); 1410362181Sdim svn_stringbuf_appendbyte(buf, *p); 1411362181Sdim } 1412362181Sdim else 1413362181Sdim { 1414362181Sdim /* Since there's no double-quote, we just insert any backslashes 1415362181Sdim literally. No escaping needed. */ 1416362181Sdim svn_stringbuf_appendfill(buf, '\\', nr_backslash); 1417362181Sdim if (strchr("()%!^<>&|", *p)) 1418362181Sdim svn_stringbuf_appendbyte(buf, '^'); 1419362181Sdim svn_stringbuf_appendbyte(buf, *p); 1420362181Sdim } 1421362181Sdim } 1422362181Sdim svn_stringbuf_appendcstr(buf, "^\""); 1423362181Sdim return buf->data; 1424362181Sdim#else 1425362181Sdim char *path, *p, *esc_path; 1426362181Sdim 1427362181Sdim /* Account for whitespace, since APR doesn't */ 1428362181Sdim for (p = (char *)orig_path; *p; p++) 1429362181Sdim if (strchr(" \t\n\r", *p)) 1430362181Sdim esc_len++; 1431362181Sdim 1432362181Sdim path = apr_pcalloc(pool, esc_len); 1433362181Sdim apr_escape_shell(path, orig_path, len, NULL); 1434362181Sdim 1435362181Sdim p = esc_path = apr_pcalloc(pool, len + esc_len + 1); 1436362181Sdim while (*path) 1437362181Sdim { 1438362181Sdim if (strchr(" \t\n\r", *path)) 1439362181Sdim *p++ = '\\'; 1440362181Sdim *p++ = *path++; 1441362181Sdim } 1442362181Sdim 1443362181Sdim return esc_path; 1444362181Sdim#endif 1445362181Sdim } 1446362181Sdim} 1447362181Sdim 1448251881Spetersvn_error_t * 1449251881Spetersvn_cmdline__edit_file_externally(const char *path, 1450251881Speter const char *editor_cmd, 1451251881Speter apr_hash_t *config, 1452251881Speter apr_pool_t *pool) 1453251881Speter{ 1454251881Speter const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr; 1455369302Sdim const char *file_name_local; 1456369302Sdim#ifdef WIN32 1457369302Sdim const WCHAR *wcmd; 1458369302Sdim#endif 1459251881Speter char *old_cwd; 1460251881Speter int sys_err; 1461251881Speter apr_status_t apr_err; 1462251881Speter 1463251881Speter svn_dirent_split(&base_dir, &file_name, path, pool); 1464251881Speter 1465369302Sdim SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool)); 1466251881Speter 1467251881Speter apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); 1468251881Speter if (apr_err) 1469251881Speter return svn_error_wrap_apr(apr_err, _("Can't get working directory")); 1470251881Speter 1471251881Speter /* APR doesn't like "" directories */ 1472251881Speter if (base_dir[0] == '\0') 1473251881Speter base_dir_apr = "."; 1474251881Speter else 1475251881Speter SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); 1476251881Speter 1477251881Speter apr_err = apr_filepath_set(base_dir_apr, pool); 1478251881Speter if (apr_err) 1479251881Speter return svn_error_wrap_apr 1480251881Speter (apr_err, _("Can't change working directory to '%s'"), base_dir); 1481251881Speter 1482369302Sdim SVN_ERR(svn_path_cstring_from_utf8(&file_name_local, 1483369302Sdim escape_path(pool, file_name), pool)); 1484362181Sdim /* editor is explicitly documented as being interpreted by the user's shell, 1485362181Sdim and as such should already be quoted/escaped as needed. */ 1486369302Sdim#ifndef WIN32 1487369302Sdim cmd = apr_psprintf(pool, "%s %s", editor, file_name_local); 1488251881Speter sys_err = system(cmd); 1489369302Sdim#else 1490369302Sdim cmd = apr_psprintf(pool, "\"%s %s\"", editor, file_name_local); 1491369302Sdim SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool)); 1492369302Sdim sys_err = _wsystem(wcmd); 1493369302Sdim#endif 1494251881Speter 1495251881Speter apr_err = apr_filepath_set(old_cwd, pool); 1496251881Speter if (apr_err) 1497251881Speter svn_handle_error2(svn_error_wrap_apr 1498251881Speter (apr_err, _("Can't restore working directory")), 1499251881Speter stderr, TRUE /* fatal */, "svn: "); 1500251881Speter 1501251881Speter if (sys_err) 1502251881Speter /* Extracting any meaning from sys_err is platform specific, so just 1503251881Speter use the raw value. */ 1504251881Speter return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 1505251881Speter _("system('%s') returned %d"), cmd, sys_err); 1506251881Speter 1507251881Speter return SVN_NO_ERROR; 1508251881Speter} 1509251881Speter 1510251881Speter 1511251881Spetersvn_error_t * 1512251881Spetersvn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, 1513251881Speter const char **tmpfile_left /* UTF-8! */, 1514251881Speter const char *editor_cmd, 1515251881Speter const char *base_dir /* UTF-8! */, 1516251881Speter const svn_string_t *contents /* UTF-8! */, 1517251881Speter const char *filename, 1518251881Speter apr_hash_t *config, 1519251881Speter svn_boolean_t as_text, 1520251881Speter const char *encoding, 1521251881Speter apr_pool_t *pool) 1522251881Speter{ 1523251881Speter const char *editor; 1524251881Speter const char *cmd; 1525369302Sdim#ifdef WIN32 1526369302Sdim const WCHAR *wcmd; 1527369302Sdim#endif 1528251881Speter apr_file_t *tmp_file; 1529251881Speter const char *tmpfile_name; 1530251881Speter const char *tmpfile_native; 1531362181Sdim const char *base_dir_apr; 1532251881Speter svn_string_t *translated_contents; 1533362181Sdim apr_status_t apr_err; 1534251881Speter apr_size_t written; 1535251881Speter apr_finfo_t finfo_before, finfo_after; 1536362181Sdim svn_error_t *err = SVN_NO_ERROR; 1537251881Speter char *old_cwd; 1538251881Speter int sys_err; 1539251881Speter svn_boolean_t remove_file = TRUE; 1540251881Speter 1541369302Sdim SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool)); 1542251881Speter 1543251881Speter /* Convert file contents from UTF-8/LF if desired. */ 1544251881Speter if (as_text) 1545251881Speter { 1546251881Speter const char *translated; 1547251881Speter SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated, 1548251881Speter APR_EOL_STR, FALSE, 1549251881Speter NULL, FALSE, pool)); 1550251881Speter translated_contents = svn_string_create_empty(pool); 1551251881Speter if (encoding) 1552251881Speter SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data, 1553251881Speter translated, encoding, pool)); 1554251881Speter else 1555251881Speter SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data, 1556251881Speter translated, pool)); 1557251881Speter translated_contents->len = strlen(translated_contents->data); 1558251881Speter } 1559251881Speter else 1560251881Speter translated_contents = svn_string_dup(contents, pool); 1561251881Speter 1562251881Speter /* Move to BASE_DIR to avoid getting characters that need quoting 1563251881Speter into tmpfile_name */ 1564251881Speter apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); 1565251881Speter if (apr_err) 1566251881Speter return svn_error_wrap_apr(apr_err, _("Can't get working directory")); 1567251881Speter 1568251881Speter /* APR doesn't like "" directories */ 1569251881Speter if (base_dir[0] == '\0') 1570251881Speter base_dir_apr = "."; 1571251881Speter else 1572251881Speter SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); 1573251881Speter apr_err = apr_filepath_set(base_dir_apr, pool); 1574251881Speter if (apr_err) 1575251881Speter { 1576251881Speter return svn_error_wrap_apr 1577251881Speter (apr_err, _("Can't change working directory to '%s'"), base_dir); 1578251881Speter } 1579251881Speter 1580251881Speter /*** From here on, any problems that occur require us to cd back!! ***/ 1581251881Speter 1582251881Speter /* Ask the working copy for a temporary file named FILENAME-something. */ 1583251881Speter err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, 1584251881Speter "" /* dirpath */, 1585251881Speter filename, 1586251881Speter ".tmp", 1587251881Speter svn_io_file_del_none, pool, pool); 1588251881Speter 1589251881Speter if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS)) 1590251881Speter { 1591251881Speter const char *temp_dir_apr; 1592251881Speter 1593251881Speter svn_error_clear(err); 1594251881Speter 1595251881Speter SVN_ERR(svn_io_temp_dir(&base_dir, pool)); 1596251881Speter 1597251881Speter SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool)); 1598251881Speter apr_err = apr_filepath_set(temp_dir_apr, pool); 1599251881Speter if (apr_err) 1600251881Speter { 1601251881Speter return svn_error_wrap_apr 1602251881Speter (apr_err, _("Can't change working directory to '%s'"), base_dir); 1603251881Speter } 1604251881Speter 1605251881Speter err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, 1606251881Speter "" /* dirpath */, 1607251881Speter filename, 1608251881Speter ".tmp", 1609251881Speter svn_io_file_del_none, pool, pool); 1610251881Speter } 1611251881Speter 1612251881Speter if (err) 1613251881Speter goto cleanup2; 1614251881Speter 1615251881Speter /*** From here on, any problems that occur require us to cleanup 1616251881Speter the file we just created!! ***/ 1617251881Speter 1618251881Speter /* Dump initial CONTENTS to TMP_FILE. */ 1619362181Sdim err = svn_io_file_write_full(tmp_file, translated_contents->data, 1620362181Sdim translated_contents->len, &written, 1621362181Sdim pool); 1622251881Speter 1623362181Sdim err = svn_error_compose_create(err, svn_io_file_close(tmp_file, pool)); 1624251881Speter 1625251881Speter /* Make sure the whole CONTENTS were written, else return an error. */ 1626251881Speter if (err) 1627251881Speter goto cleanup; 1628251881Speter 1629251881Speter /* Get information about the temporary file before the user has 1630251881Speter been allowed to edit its contents. */ 1631362181Sdim err = svn_io_stat(&finfo_before, tmpfile_name, APR_FINFO_MTIME, pool); 1632362181Sdim if (err) 1633362181Sdim goto cleanup; 1634251881Speter 1635251881Speter /* Backdate the file a little bit in case the editor is very fast 1636251881Speter and doesn't change the size. (Use two seconds, since some 1637251881Speter filesystems have coarse granularity.) It's OK if this call 1638251881Speter fails, so we don't check its return value.*/ 1639362181Sdim err = svn_io_set_file_affected_time(finfo_before.mtime 1640362181Sdim - apr_time_from_sec(2), 1641362181Sdim tmpfile_name, pool); 1642362181Sdim svn_error_clear(err); 1643251881Speter 1644251881Speter /* Stat it again to get the mtime we actually set. */ 1645362181Sdim err = svn_io_stat(&finfo_before, tmpfile_name, 1646362181Sdim APR_FINFO_MTIME | APR_FINFO_SIZE, pool); 1647362181Sdim if (err) 1648362181Sdim goto cleanup; 1649251881Speter 1650251881Speter /* Prepare the editor command line. */ 1651369302Sdim err = svn_utf_cstring_from_utf8(&tmpfile_native, 1652369302Sdim escape_path(pool, tmpfile_name), pool); 1653251881Speter if (err) 1654251881Speter goto cleanup; 1655251881Speter 1656362181Sdim /* editor is explicitly documented as being interpreted by the user's shell, 1657362181Sdim and as such should already be quoted/escaped as needed. */ 1658369302Sdim#ifndef WIN32 1659369302Sdim cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native); 1660369302Sdim#else 1661369302Sdim cmd = apr_psprintf(pool, "\"%s %s\"", editor, tmpfile_native); 1662369302Sdim#endif 1663362181Sdim 1664251881Speter /* If the caller wants us to leave the file around, return the path 1665251881Speter of the file we'll use, and make a note not to destroy it. */ 1666251881Speter if (tmpfile_left) 1667251881Speter { 1668251881Speter *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool); 1669251881Speter remove_file = FALSE; 1670251881Speter } 1671251881Speter 1672251881Speter /* Now, run the editor command line. */ 1673369302Sdim#ifndef WIN32 1674251881Speter sys_err = system(cmd); 1675369302Sdim#else 1676369302Sdim SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool)); 1677369302Sdim sys_err = _wsystem(wcmd); 1678369302Sdim#endif 1679251881Speter if (sys_err != 0) 1680251881Speter { 1681251881Speter /* Extracting any meaning from sys_err is platform specific, so just 1682251881Speter use the raw value. */ 1683251881Speter err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 1684251881Speter _("system('%s') returned %d"), cmd, sys_err); 1685251881Speter goto cleanup; 1686251881Speter } 1687251881Speter 1688251881Speter /* Get information about the temporary file after the assumed editing. */ 1689362181Sdim err = svn_io_stat(&finfo_after, tmpfile_name, 1690362181Sdim APR_FINFO_MTIME | APR_FINFO_SIZE, pool); 1691362181Sdim if (err) 1692362181Sdim goto cleanup; 1693251881Speter 1694251881Speter /* If the file looks changed... */ 1695251881Speter if ((finfo_before.mtime != finfo_after.mtime) || 1696251881Speter (finfo_before.size != finfo_after.size)) 1697251881Speter { 1698251881Speter svn_stringbuf_t *edited_contents_s; 1699251881Speter err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool); 1700251881Speter if (err) 1701251881Speter goto cleanup; 1702251881Speter 1703251881Speter *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s); 1704251881Speter 1705251881Speter /* Translate back to UTF8/LF if desired. */ 1706251881Speter if (as_text) 1707251881Speter { 1708289180Speter err = svn_subst_translate_string2(edited_contents, NULL, NULL, 1709251881Speter *edited_contents, encoding, FALSE, 1710251881Speter pool, pool); 1711251881Speter if (err) 1712251881Speter { 1713251881Speter err = svn_error_quick_wrap 1714251881Speter (err, 1715251881Speter _("Error normalizing edited contents to internal format")); 1716251881Speter goto cleanup; 1717251881Speter } 1718251881Speter } 1719251881Speter } 1720251881Speter else 1721251881Speter { 1722251881Speter /* No edits seem to have been made */ 1723251881Speter *edited_contents = NULL; 1724251881Speter } 1725251881Speter 1726251881Speter cleanup: 1727251881Speter if (remove_file) 1728251881Speter { 1729251881Speter /* Remove the file from disk. */ 1730362181Sdim err = svn_error_compose_create( 1731362181Sdim err, 1732362181Sdim svn_io_remove_file2(tmpfile_name, FALSE, pool)); 1733251881Speter } 1734251881Speter 1735251881Speter cleanup2: 1736251881Speter /* If we against all probability can't cd back, all further relative 1737251881Speter file references would be screwed up, so we have to abort. */ 1738251881Speter apr_err = apr_filepath_set(old_cwd, pool); 1739251881Speter if (apr_err) 1740251881Speter { 1741251881Speter svn_handle_error2(svn_error_wrap_apr 1742251881Speter (apr_err, _("Can't restore working directory")), 1743251881Speter stderr, TRUE /* fatal */, "svn: "); 1744251881Speter } 1745251881Speter 1746251881Speter return svn_error_trace(err); 1747251881Speter} 1748289180Speter 1749289180Spetersvn_error_t * 1750289180Spetersvn_cmdline__parse_trust_options( 1751289180Speter svn_boolean_t *trust_server_cert_unknown_ca, 1752289180Speter svn_boolean_t *trust_server_cert_cn_mismatch, 1753289180Speter svn_boolean_t *trust_server_cert_expired, 1754289180Speter svn_boolean_t *trust_server_cert_not_yet_valid, 1755289180Speter svn_boolean_t *trust_server_cert_other_failure, 1756289180Speter const char *opt_arg, 1757289180Speter apr_pool_t *scratch_pool) 1758289180Speter{ 1759289180Speter apr_array_header_t *failures; 1760289180Speter int i; 1761289180Speter 1762289180Speter *trust_server_cert_unknown_ca = FALSE; 1763289180Speter *trust_server_cert_cn_mismatch = FALSE; 1764289180Speter *trust_server_cert_expired = FALSE; 1765289180Speter *trust_server_cert_not_yet_valid = FALSE; 1766289180Speter *trust_server_cert_other_failure = FALSE; 1767289180Speter 1768289180Speter failures = svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, scratch_pool); 1769289180Speter 1770289180Speter for (i = 0; i < failures->nelts; i++) 1771289180Speter { 1772289180Speter const char *value = APR_ARRAY_IDX(failures, i, const char *); 1773289180Speter if (!strcmp(value, "unknown-ca")) 1774289180Speter *trust_server_cert_unknown_ca = TRUE; 1775289180Speter else if (!strcmp(value, "cn-mismatch")) 1776289180Speter *trust_server_cert_cn_mismatch = TRUE; 1777289180Speter else if (!strcmp(value, "expired")) 1778289180Speter *trust_server_cert_expired = TRUE; 1779289180Speter else if (!strcmp(value, "not-yet-valid")) 1780289180Speter *trust_server_cert_not_yet_valid = TRUE; 1781289180Speter else if (!strcmp(value, "other")) 1782289180Speter *trust_server_cert_other_failure = TRUE; 1783289180Speter else 1784289180Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1785289180Speter _("Unknown value '%s' for %s.\n" 1786289180Speter "Supported values: %s"), 1787289180Speter value, 1788289180Speter "--trust-server-cert-failures", 1789289180Speter "unknown-ca, cn-mismatch, expired, " 1790289180Speter "not-yet-valid, other"); 1791289180Speter } 1792289180Speter 1793289180Speter return SVN_NO_ERROR; 1794289180Speter} 1795362181Sdim 1796362181Sdim/* Flags to see if we've been cancelled by the client or not. */ 1797362181Sdimstatic volatile sig_atomic_t cancelled = FALSE; 1798362181Sdimstatic volatile sig_atomic_t signum_cancelled = 0; 1799362181Sdim 1800362181Sdim/* The signals we handle. */ 1801362181Sdimstatic int signal_map [] = { 1802362181Sdim SIGINT 1803362181Sdim#ifdef SIGBREAK 1804362181Sdim /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ 1805362181Sdim , SIGBREAK 1806362181Sdim#endif 1807362181Sdim#ifdef SIGHUP 1808362181Sdim , SIGHUP 1809362181Sdim#endif 1810362181Sdim#ifdef SIGTERM 1811362181Sdim , SIGTERM 1812362181Sdim#endif 1813362181Sdim}; 1814362181Sdim 1815362181Sdim/* A signal handler to support cancellation. */ 1816362181Sdimstatic void 1817362181Sdimsignal_handler(int signum) 1818362181Sdim{ 1819362181Sdim int i; 1820362181Sdim 1821362181Sdim apr_signal(signum, SIG_IGN); 1822362181Sdim cancelled = TRUE; 1823362181Sdim for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i) 1824362181Sdim if (signal_map[i] == signum) 1825362181Sdim { 1826362181Sdim signum_cancelled = i + 1; 1827362181Sdim break; 1828362181Sdim } 1829362181Sdim} 1830362181Sdim 1831362181Sdim/* An svn_cancel_func_t callback. */ 1832362181Sdimstatic svn_error_t * 1833362181Sdimcheck_cancel(void *baton) 1834362181Sdim{ 1835362181Sdim /* Cancel baton should be always NULL in command line client. */ 1836362181Sdim SVN_ERR_ASSERT(baton == NULL); 1837362181Sdim if (cancelled) 1838362181Sdim return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); 1839362181Sdim else 1840362181Sdim return SVN_NO_ERROR; 1841362181Sdim} 1842362181Sdim 1843362181Sdimsvn_cancel_func_t 1844362181Sdimsvn_cmdline__setup_cancellation_handler(void) 1845362181Sdim{ 1846362181Sdim int i; 1847362181Sdim 1848362181Sdim for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i) 1849362181Sdim apr_signal(signal_map[i], signal_handler); 1850362181Sdim 1851362181Sdim#ifdef SIGPIPE 1852362181Sdim /* Disable SIGPIPE generation for the platforms that have it. */ 1853362181Sdim apr_signal(SIGPIPE, SIG_IGN); 1854362181Sdim#endif 1855362181Sdim 1856362181Sdim#ifdef SIGXFSZ 1857362181Sdim /* Disable SIGXFSZ generation for the platforms that have it, otherwise 1858362181Sdim * working with large files when compiled against an APR that doesn't have 1859362181Sdim * large file support will crash the program, which is uncool. */ 1860362181Sdim apr_signal(SIGXFSZ, SIG_IGN); 1861362181Sdim#endif 1862362181Sdim 1863362181Sdim return check_cancel; 1864362181Sdim} 1865362181Sdim 1866362181Sdimvoid 1867362181Sdimsvn_cmdline__disable_cancellation_handler(void) 1868362181Sdim{ 1869362181Sdim int i; 1870362181Sdim 1871362181Sdim for (i = 0; i < sizeof(signal_map)/sizeof(signal_map[0]); ++i) 1872362181Sdim apr_signal(signal_map[i], SIG_DFL); 1873362181Sdim} 1874362181Sdim 1875362181Sdimvoid 1876362181Sdimsvn_cmdline__cancellation_exit(void) 1877362181Sdim{ 1878362181Sdim int signum = 0; 1879362181Sdim 1880362181Sdim if (cancelled && signum_cancelled) 1881362181Sdim signum = signal_map[signum_cancelled - 1]; 1882362181Sdim if (signum) 1883362181Sdim { 1884362181Sdim#ifndef WIN32 1885362181Sdim apr_signal(signum, SIG_DFL); 1886362181Sdim /* No APR support for getpid() so cannot use apr_proc_kill(). */ 1887362181Sdim kill(getpid(), signum); 1888362181Sdim#endif 1889362181Sdim } 1890362181Sdim} 1891