diff options
Diffstat (limited to 'subversion/libsvn_subr/cmdline.c')
-rw-r--r-- | subversion/libsvn_subr/cmdline.c | 1312 |
1 files changed, 1312 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/cmdline.c b/subversion/libsvn_subr/cmdline.c new file mode 100644 index 0000000..8484c96 --- /dev/null +++ b/subversion/libsvn_subr/cmdline.c @@ -0,0 +1,1312 @@ +/* + * cmdline.c : Helpers for command-line programs. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <stdlib.h> /* for atexit() */ +#include <stdio.h> /* for setvbuf() */ +#include <locale.h> /* for setlocale() */ + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#else +#include <crtdbg.h> +#include <io.h> +#endif + +#include <apr.h> /* for STDIN_FILENO */ +#include <apr_errno.h> /* for apr_strerror */ +#include <apr_general.h> /* for apr_initialize/apr_terminate */ +#include <apr_strings.h> /* for apr_snprintf */ +#include <apr_pools.h> + +#include "svn_cmdline.h" +#include "svn_ctype.h" +#include "svn_dso.h" +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_nls.h" +#include "svn_utf.h" +#include "svn_auth.h" +#include "svn_xml.h" +#include "svn_base64.h" +#include "svn_config.h" +#include "svn_sorts.h" +#include "svn_props.h" +#include "svn_subst.h" + +#include "private/svn_cmdline_private.h" +#include "private/svn_utf_private.h" +#include "private/svn_string_private.h" + +#include "svn_private_config.h" + +#include "win32_crashrpt.h" + +/* The stdin encoding. If null, it's the same as the native encoding. */ +static const char *input_encoding = NULL; + +/* The stdout encoding. If null, it's the same as the native encoding. */ +static const char *output_encoding = NULL; + + +int +svn_cmdline_init(const char *progname, FILE *error_stream) +{ + apr_status_t status; + apr_pool_t *pool; + svn_error_t *err; + char prefix_buf[64]; /* 64 is probably bigger than most program names */ + +#ifndef WIN32 + { + struct stat st; + + /* The following makes sure that file descriptors 0 (stdin), 1 + (stdout) and 2 (stderr) will not be "reused", because if + e.g. file descriptor 2 would be reused when opening a file, a + write to stderr would write to that file and most likely + corrupt it. */ + if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) || + (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) || + (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1)) + { + if (error_stream) + fprintf(error_stream, "%s: error: cannot open '/dev/null'\n", + progname); + return EXIT_FAILURE; + } + } +#endif + + /* Ignore any errors encountered while attempting to change stream + buffering, as the streams should retain their default buffering + modes. */ + if (error_stream) + setvbuf(error_stream, NULL, _IONBF, 0); +#ifndef WIN32 + setvbuf(stdout, NULL, _IOLBF, 0); +#endif + +#ifdef WIN32 +#if _MSC_VER < 1400 + /* Initialize the input and output encodings. */ + { + static char input_encoding_buffer[16]; + static char output_encoding_buffer[16]; + + apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer, + "CP%u", (unsigned) GetConsoleCP()); + input_encoding = input_encoding_buffer; + + apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer, + "CP%u", (unsigned) GetConsoleOutputCP()); + output_encoding = output_encoding_buffer; + } +#endif /* _MSC_VER < 1400 */ + +#ifdef SVN_USE_WIN32_CRASHHANDLER + /* Attach (but don't load) the crash handler */ + SetUnhandledExceptionFilter(svn__unhandled_exception_filter); + +#if _MSC_VER >= 1400 + /* ### This should work for VC++ 2002 (=1300) and later */ + /* Show the abort message on STDERR instead of a dialog to allow + scripts (e.g. our testsuite) to continue after an abort without + user intervention. Allow overriding for easier debugging. */ + if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT")) + { + /* In release mode: Redirect abort() errors to stderr */ + _set_error_mode(_OUT_TO_STDERR); + + /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr. + (Ignored in release builds) */ + _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + } +#endif /* _MSC_VER >= 1400 */ + +#endif /* SVN_USE_WIN32_CRASHHANDLER */ + +#endif /* WIN32 */ + + /* C programs default to the "C" locale. But because svn is supposed + to be i18n-aware, it should inherit the default locale of its + environment. */ + if (!setlocale(LC_ALL, "") + && !setlocale(LC_CTYPE, "")) + { + if (error_stream) + { + const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL }; + const char **env_var = &env_vars[0], *env_val = NULL; + while (*env_var) + { + env_val = getenv(*env_var); + if (env_val && env_val[0]) + break; + ++env_var; + } + + if (!*env_var) + { + /* Unlikely. Can setlocale fail if no env vars are set? */ + --env_var; + env_val = "not set"; + } + + fprintf(error_stream, + "%s: warning: cannot set LC_CTYPE locale\n" + "%s: warning: environment variable %s is %s\n" + "%s: warning: please check that your locale name is correct\n", + progname, progname, *env_var, env_val, progname); + } + } + + /* Initialize the APR subsystem, and register an atexit() function + to Uninitialize that subsystem at program exit. */ + status = apr_initialize(); + if (status) + { + if (error_stream) + { + char buf[1024]; + apr_strerror(status, buf, sizeof(buf) - 1); + fprintf(error_stream, + "%s: error: cannot initialize APR: %s\n", + progname, buf); + } + return EXIT_FAILURE; + } + + strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3); + prefix_buf[sizeof(prefix_buf) - 3] = '\0'; + strcat(prefix_buf, ": "); + + /* DSO pool must be created before any other pools used by the + application so that pool cleanup doesn't unload DSOs too + early. See docstring of svn_dso_initialize2(). */ + if ((err = svn_dso_initialize2())) + { + if (error_stream) + svn_handle_error2(err, error_stream, TRUE, prefix_buf); + + svn_error_clear(err); + return EXIT_FAILURE; + } + + if (0 > atexit(apr_terminate)) + { + if (error_stream) + fprintf(error_stream, + "%s: error: atexit registration failed\n", + progname); + return EXIT_FAILURE; + } + + /* Create a pool for use by the UTF-8 routines. It will be cleaned + up by APR at exit time. */ + pool = svn_pool_create(NULL); + svn_utf_initialize2(FALSE, pool); + + if ((err = svn_nls_init())) + { + if (error_stream) + svn_handle_error2(err, error_stream, TRUE, prefix_buf); + + svn_error_clear(err); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + + +svn_error_t * +svn_cmdline_cstring_from_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + if (output_encoding == NULL) + return svn_utf_cstring_from_utf8(dest, src, pool); + else + return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool); +} + + +const char * +svn_cmdline_cstring_from_utf8_fuzzy(const char *src, + apr_pool_t *pool) +{ + return svn_utf__cstring_from_utf8_fuzzy(src, pool, + svn_cmdline_cstring_from_utf8); +} + + +svn_error_t * +svn_cmdline_cstring_to_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + if (input_encoding == NULL) + return svn_utf_cstring_to_utf8(dest, src, pool); + else + return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool); +} + + +svn_error_t * +svn_cmdline_path_local_style_from_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + return svn_cmdline_cstring_from_utf8(dest, + svn_dirent_local_style(src, pool), + pool); +} + +svn_error_t * +svn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...) +{ + const char *message; + va_list ap; + + /* A note about encoding issues: + * APR uses the execution character set, but here we give it UTF-8 strings, + * both the fmt argument and any other string arguments. Since apr_pvsprintf + * only cares about and produces ASCII characters, this works under the + * assumption that all supported platforms use an execution character set + * with ASCII as a subset. + */ + + va_start(ap, fmt); + message = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + + return svn_cmdline_fputs(message, stdout, pool); +} + +svn_error_t * +svn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...) +{ + const char *message; + va_list ap; + + /* See svn_cmdline_printf () for a note about character encoding issues. */ + + va_start(ap, fmt); + message = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + + return svn_cmdline_fputs(message, stream, pool); +} + +svn_error_t * +svn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool) +{ + svn_error_t *err; + const char *out; + + err = svn_cmdline_cstring_from_utf8(&out, string, pool); + + if (err) + { + svn_error_clear(err); + out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool); + } + + /* On POSIX systems, errno will be set on an error in fputs, but this might + not be the case on other platforms. We reset errno and only + use it if it was set by the below fputs call. Else, we just return + a generic error. */ + errno = 0; + + if (fputs(out, stream) == EOF) + { + if (apr_get_os_error()) /* is errno on POSIX */ + { + /* ### Issue #3014: Return a specific error for broken pipes, + * ### with a single element in the error chain. */ + if (APR_STATUS_IS_EPIPE(apr_get_os_error())) + return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); + else + return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); + } + else + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline_fflush(FILE *stream) +{ + /* See comment in svn_cmdline_fputs about use of errno and stdio. */ + errno = 0; + if (fflush(stream) == EOF) + { + if (apr_get_os_error()) /* is errno on POSIX */ + { + /* ### Issue #3014: Return a specific error for broken pipes, + * ### with a single element in the error chain. */ + if (APR_STATUS_IS_EPIPE(apr_get_os_error())) + return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); + else + return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); + } + else + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); + } + + return SVN_NO_ERROR; +} + +const char *svn_cmdline_output_encoding(apr_pool_t *pool) +{ + if (output_encoding) + return apr_pstrdup(pool, output_encoding); + else + return SVN_APR_LOCALE_CHARSET; +} + +int +svn_cmdline_handle_exit_error(svn_error_t *err, + apr_pool_t *pool, + const char *prefix) +{ + /* Issue #3014: + * Don't print anything on broken pipes. The pipe was likely + * closed by the process at the other end. We expect that + * process to perform error reporting as necessary. + * + * ### This assumes that there is only one error in a chain for + * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ + if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) + svn_handle_error2(err, stderr, FALSE, prefix); + svn_error_clear(err); + if (pool) + svn_pool_destroy(pool); + return EXIT_FAILURE; +} + +/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. + + Don't actually prompt. Instead, set *CRED_P to valid credentials + iff FAILURES is empty or is exactly SVN_AUTH_SSL_UNKNOWNCA. If + there are any other failure bits, then set *CRED_P to null (that + is, reject the cert). + + Ignore MAY_SAVE; we don't save certs we never prompted for. + + Ignore BATON, REALM, and CERT_INFO, + + Ignore any further films by George Lucas. */ +static svn_error_t * +ssl_trust_unknown_server_cert + (svn_auth_cred_ssl_server_trust_t **cred_p, + void *baton, + const char *realm, + apr_uint32_t failures, + const svn_auth_ssl_server_cert_info_t *cert_info, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + *cred_p = NULL; + + if (failures == 0 || failures == SVN_AUTH_SSL_UNKNOWNCA) + { + *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); + (*cred_p)->may_save = FALSE; + (*cred_p)->accepted_failures = failures; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline_create_auth_baton(svn_auth_baton_t **ab, + svn_boolean_t non_interactive, + const char *auth_username, + const char *auth_password, + const char *config_dir, + svn_boolean_t no_auth_cache, + svn_boolean_t trust_server_cert, + svn_config_t *cfg, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_boolean_t store_password_val = TRUE; + svn_boolean_t store_auth_creds_val = TRUE; + svn_auth_provider_object_t *provider; + svn_cmdline_prompt_baton2_t *pb = NULL; + + /* The whole list of registered providers */ + apr_array_header_t *providers; + + /* Populate the registered providers with the platform-specific providers */ + SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers, + cfg, pool)); + + /* If we have a cancellation function, cram it and the stuff it + needs into the prompt baton. */ + if (cancel_func) + { + pb = apr_palloc(pool, sizeof(*pb)); + pb->cancel_func = cancel_func; + pb->cancel_baton = cancel_baton; + pb->config_dir = config_dir; + } + + if (!non_interactive) + { + /* This provider doesn't prompt the user in order to get creds; + it prompts the user regarding the caching of creds. */ + svn_auth_get_simple_provider2(&provider, + svn_cmdline_auth_plaintext_prompt, + pb, pool); + } + else + { + svn_auth_get_simple_provider2(&provider, NULL, NULL, pool); + } + + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + svn_auth_get_username_provider(&provider, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + /* The server-cert, client-cert, and client-cert-password providers. */ + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "windows", + "ssl_server_trust", + pool)); + + if (provider) + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + svn_auth_get_ssl_server_trust_file_provider(&provider, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + svn_auth_get_ssl_client_cert_file_provider(&provider, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + if (!non_interactive) + { + /* This provider doesn't prompt the user in order to get creds; + it prompts the user regarding the caching of creds. */ + svn_auth_get_ssl_client_cert_pw_file_provider2 + (&provider, svn_cmdline_auth_plaintext_passphrase_prompt, + pb, pool); + } + else + { + svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL, + pool); + } + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + if (!non_interactive) + { + svn_boolean_t ssl_client_cert_file_prompt; + + SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT, + FALSE)); + + /* Two basic prompt providers: username/password, and just username. */ + svn_auth_get_simple_prompt_provider(&provider, + svn_cmdline_auth_simple_prompt, + pb, + 2, /* retry limit */ + pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + svn_auth_get_username_prompt_provider + (&provider, svn_cmdline_auth_username_prompt, pb, + 2, /* retry limit */ pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + /* SSL prompt providers: server-certs and client-cert-passphrases. */ + svn_auth_get_ssl_server_trust_prompt_provider + (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + svn_auth_get_ssl_client_cert_pw_prompt_provider + (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + /* If configuration allows, add a provider for client-cert path + prompting, too. */ + if (ssl_client_cert_file_prompt) + { + svn_auth_get_ssl_client_cert_prompt_provider + (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + } + } + else if (trust_server_cert) + { + /* Remember, only register this provider if non_interactive. */ + svn_auth_get_ssl_server_trust_prompt_provider + (&provider, ssl_trust_unknown_server_cert, NULL, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + } + + /* Build an authentication baton to give to libsvn_client. */ + svn_auth_open(ab, providers, pool); + + /* Place any default --username or --password credentials into the + auth_baton's run-time parameter hash. */ + if (auth_username) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME, + auth_username); + if (auth_password) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD, + auth_password); + + /* Same with the --non-interactive option. */ + if (non_interactive) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, ""); + + if (config_dir) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR, + config_dir); + + /* Determine whether storing passwords in any form is allowed. + * This is the deprecated location for this option, the new + * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may + * override the value we set here. */ + SVN_ERR(svn_config_get_bool(cfg, &store_password_val, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_STORE_PASSWORDS, + SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS)); + + if (! store_password_val) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, ""); + + /* Determine whether we are allowed to write to the auth/ area. + * This is the deprecated location for this option, the new + * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may + * override the value we set here. */ + SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_STORE_AUTH_CREDS, + SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS)); + + if (no_auth_cache || ! store_auth_creds_val) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, ""); + +#ifdef SVN_HAVE_GNOME_KEYRING + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC, + &svn_cmdline__auth_gnome_keyring_unlock_prompt); +#endif /* SVN_HAVE_GNOME_KEYRING */ + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline__getopt_init(apr_getopt_t **os, + int argc, + const char *argv[], + apr_pool_t *pool) +{ + apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Error initializing command line arguments")); + return SVN_NO_ERROR; +} + + +void +svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr, + const char* propname, + svn_string_t *propval, + svn_boolean_t inherited_prop, + apr_pool_t *pool) +{ + const char *xml_safe; + const char *encoding = NULL; + + if (*outstr == NULL) + *outstr = svn_stringbuf_create_empty(pool); + + if (svn_xml_is_xml_safe(propval->data, propval->len)) + { + svn_stringbuf_t *xml_esc = NULL; + svn_xml_escape_cdata_string(&xml_esc, propval, pool); + xml_safe = xml_esc->data; + } + else + { + const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE, + pool); + encoding = "base64"; + xml_safe = base64ed->data; + } + + if (encoding) + svn_xml_make_open_tag( + outstr, pool, svn_xml_protect_pcdata, + inherited_prop ? "inherited_property" : "property", + "name", propname, + "encoding", encoding, NULL); + else + svn_xml_make_open_tag( + outstr, pool, svn_xml_protect_pcdata, + inherited_prop ? "inherited_property" : "property", + "name", propname, NULL); + + svn_stringbuf_appendcstr(*outstr, xml_safe); + + svn_xml_make_close_tag( + outstr, pool, + inherited_prop ? "inherited_property" : "property"); + + return; +} + +svn_error_t * +svn_cmdline__parse_config_option(apr_array_header_t *config_options, + const char *opt_arg, + apr_pool_t *pool) +{ + svn_cmdline__config_argument_t *config_option; + const char *first_colon, *second_colon, *equals_sign; + apr_size_t len = strlen(opt_arg); + if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg)) + { + if ((second_colon = strchr(first_colon + 1, ':')) && + (second_colon != first_colon + 1)) + { + if ((equals_sign = strchr(second_colon + 1, '=')) && + (equals_sign != second_colon + 1)) + { + config_option = apr_pcalloc(pool, sizeof(*config_option)); + config_option->file = apr_pstrndup(pool, opt_arg, + first_colon - opt_arg); + config_option->section = apr_pstrndup(pool, first_colon + 1, + second_colon - first_colon - 1); + config_option->option = apr_pstrndup(pool, second_colon + 1, + equals_sign - second_colon - 1); + + if (! (strchr(config_option->option, ':'))) + { + config_option->value = apr_pstrndup(pool, equals_sign + 1, + opt_arg + len - equals_sign - 1); + APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *) + = config_option; + return SVN_NO_ERROR; + } + } + } + } + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Invalid syntax of argument of --config-option")); +} + +svn_error_t * +svn_cmdline__apply_config_options(apr_hash_t *config, + const apr_array_header_t *config_options, + const char *prefix, + const char *argument_name) +{ + int i; + + for (i = 0; i < config_options->nelts; i++) + { + svn_config_t *cfg; + svn_cmdline__config_argument_t *arg = + APR_ARRAY_IDX(config_options, i, + svn_cmdline__config_argument_t *); + + cfg = svn_hash_gets(config, arg->file); + + if (cfg) + { + svn_config_set(cfg, arg->section, arg->option, arg->value); + } + else + { + svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Unrecognized file in argument of %s"), argument_name); + + svn_handle_warning2(stderr, err, prefix); + svn_error_clear(err); + } + } + + return SVN_NO_ERROR; +} + +/* Return a copy, allocated in POOL, of the next line of text from *STR + * up to and including a CR and/or an LF. Change *STR to point to the + * remainder of the string after the returned part. If there are no + * characters to be returned, return NULL; never return an empty string. + */ +static const char * +next_line(const char **str, apr_pool_t *pool) +{ + const char *start = *str; + const char *p = *str; + + /* n.b. Throughout this fn, we never read any character after a '\0'. */ + /* Skip over all non-EOL characters, if any. */ + while (*p != '\r' && *p != '\n' && *p != '\0') + p++; + /* Skip over \r\n or \n\r or \r or \n, if any. */ + if (*p == '\r' || *p == '\n') + { + char c = *p++; + + if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r')) + p++; + } + + /* Now p points after at most one '\n' and/or '\r'. */ + *str = p; + + if (p == start) + return NULL; + + return svn_string_ncreate(start, p - start, pool)->data; +} + +const char * +svn_cmdline__indent_string(const char *str, + const char *indent, + apr_pool_t *pool) +{ + svn_stringbuf_t *out = svn_stringbuf_create_empty(pool); + const char *line; + + while ((line = next_line(&str, pool))) + { + svn_stringbuf_appendcstr(out, indent); + svn_stringbuf_appendcstr(out, line); + } + return out->data; +} + +svn_error_t * +svn_cmdline__print_prop_hash(svn_stream_t *out, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + + sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, + pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *pname = item.key; + svn_string_t *propval = item.value; + const char *pname_stdout; + + if (svn_prop_needs_translation(pname)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, pool)); + + SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool)); + + if (out) + { + pname_stdout = apr_psprintf(pool, " %s\n", pname_stdout); + SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout, + APR_EOL_STR, /* 'native' eol */ + FALSE, /* no repair */ + NULL, /* no keywords */ + FALSE, /* no expansion */ + pool)); + + SVN_ERR(svn_stream_puts(out, pname_stdout)); + } + else + { + /* ### We leave these printfs for now, since if propval wasn't + translated above, we don't know anything about its encoding. + In fact, it might be binary data... */ + printf(" %s\n", pname_stdout); + } + + if (!names_only) + { + /* Add an extra newline to the value before indenting, so that + * every line of output has the indentation whether the value + * already ended in a newline or not. */ + const char *newval = apr_psprintf(pool, "%s\n", propval->data); + const char *indented_newval = svn_cmdline__indent_string(newval, + " ", + pool); + if (out) + { + SVN_ERR(svn_stream_puts(out, indented_newval)); + } + else + { + printf("%s", indented_newval); + } + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + svn_boolean_t inherited_props, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + + if (*outstr == NULL) + *outstr = svn_stringbuf_create_empty(pool); + + sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, + pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *pname = item.key; + svn_string_t *propval = item.value; + + if (names_only) + { + svn_xml_make_open_tag( + outstr, pool, svn_xml_self_closing, + inherited_props ? "inherited_property" : "property", + "name", pname, NULL); + } + else + { + const char *pname_out; + + if (svn_prop_needs_translation(pname)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, pool)); + + SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool)); + + svn_cmdline__print_xml_prop(outstr, pname_out, propval, + inherited_props, pool); + } + } + + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_cmdline__be_interactive(svn_boolean_t non_interactive, + svn_boolean_t force_interactive) +{ + /* If neither --non-interactive nor --force-interactive was passed, + * be interactive if stdin is a terminal. + * If --force-interactive was passed, always be interactive. */ + if (!force_interactive && !non_interactive) + { +#ifdef WIN32 + return (_isatty(STDIN_FILENO) != 0); +#else + return (isatty(STDIN_FILENO) != 0); +#endif + } + else if (force_interactive) + return TRUE; + + return !non_interactive; +} + + +/* Helper for the next two functions. Set *EDITOR to some path to an + editor binary. Sources to search include: the EDITOR_CMD argument + (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG + is not NULL), $VISUAL, $EDITOR. Return + SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */ +static svn_error_t * +find_editor_binary(const char **editor, + const char *editor_cmd, + apr_hash_t *config) +{ + const char *e; + struct svn_config_t *cfg; + + /* Use the editor specified on the command line via --editor-cmd, if any. */ + e = editor_cmd; + + /* Otherwise look for the Subversion-specific environment variable. */ + if (! e) + e = getenv("SVN_EDITOR"); + + /* If not found then fall back on the config file. */ + if (! e) + { + cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; + svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_EDITOR_CMD, NULL); + } + + /* If not found yet then try general purpose environment variables. */ + if (! e) + e = getenv("VISUAL"); + if (! e) + e = getenv("EDITOR"); + +#ifdef SVN_CLIENT_EDITOR + /* If still not found then fall back on the hard-coded default. */ + if (! e) + e = SVN_CLIENT_EDITOR; +#endif + + /* Error if there is no editor specified */ + if (e) + { + const char *c; + + for (c = e; *c; c++) + if (!svn_ctype_isspace(*c)) + break; + + if (! *c) + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, + _("The EDITOR, SVN_EDITOR or VISUAL environment variable or " + "'editor-cmd' run-time configuration option is empty or " + "consists solely of whitespace. Expected a shell command.")); + } + else + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, + _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are " + "set, and no 'editor-cmd' run-time configuration option was found")); + + *editor = e; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cmdline__edit_file_externally(const char *path, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *pool) +{ + const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr; + char *old_cwd; + int sys_err; + apr_status_t apr_err; + + svn_dirent_split(&base_dir, &file_name, path, pool); + + SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + + apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get working directory")); + + /* APR doesn't like "" directories */ + if (base_dir[0] == '\0') + base_dir_apr = "."; + else + SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); + + apr_err = apr_filepath_set(base_dir_apr, pool); + if (apr_err) + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + + cmd = apr_psprintf(pool, "%s %s", editor, file_name); + sys_err = system(cmd); + + apr_err = apr_filepath_set(old_cwd, pool); + if (apr_err) + svn_handle_error2(svn_error_wrap_apr + (apr_err, _("Can't restore working directory")), + stderr, TRUE /* fatal */, "svn: "); + + if (sys_err) + /* Extracting any meaning from sys_err is platform specific, so just + use the raw value. */ + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("system('%s') returned %d"), cmd, sys_err); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, + const char **tmpfile_left /* UTF-8! */, + const char *editor_cmd, + const char *base_dir /* UTF-8! */, + const svn_string_t *contents /* UTF-8! */, + const char *filename, + apr_hash_t *config, + svn_boolean_t as_text, + const char *encoding, + apr_pool_t *pool) +{ + const char *editor; + const char *cmd; + apr_file_t *tmp_file; + const char *tmpfile_name; + const char *tmpfile_native; + const char *tmpfile_apr, *base_dir_apr; + svn_string_t *translated_contents; + apr_status_t apr_err, apr_err2; + apr_size_t written; + apr_finfo_t finfo_before, finfo_after; + svn_error_t *err = SVN_NO_ERROR, *err2; + char *old_cwd; + int sys_err; + svn_boolean_t remove_file = TRUE; + + SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + + /* Convert file contents from UTF-8/LF if desired. */ + if (as_text) + { + const char *translated; + SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated, + APR_EOL_STR, FALSE, + NULL, FALSE, pool)); + translated_contents = svn_string_create_empty(pool); + if (encoding) + SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data, + translated, encoding, pool)); + else + SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data, + translated, pool)); + translated_contents->len = strlen(translated_contents->data); + } + else + translated_contents = svn_string_dup(contents, pool); + + /* Move to BASE_DIR to avoid getting characters that need quoting + into tmpfile_name */ + apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get working directory")); + + /* APR doesn't like "" directories */ + if (base_dir[0] == '\0') + base_dir_apr = "."; + else + SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); + apr_err = apr_filepath_set(base_dir_apr, pool); + if (apr_err) + { + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + } + + /*** From here on, any problems that occur require us to cd back!! ***/ + + /* Ask the working copy for a temporary file named FILENAME-something. */ + err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, + "" /* dirpath */, + filename, + ".tmp", + svn_io_file_del_none, pool, pool); + + if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS)) + { + const char *temp_dir_apr; + + svn_error_clear(err); + + SVN_ERR(svn_io_temp_dir(&base_dir, pool)); + + SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool)); + apr_err = apr_filepath_set(temp_dir_apr, pool); + if (apr_err) + { + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + } + + err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, + "" /* dirpath */, + filename, + ".tmp", + svn_io_file_del_none, pool, pool); + } + + if (err) + goto cleanup2; + + /*** From here on, any problems that occur require us to cleanup + the file we just created!! ***/ + + /* Dump initial CONTENTS to TMP_FILE. */ + apr_err = apr_file_write_full(tmp_file, translated_contents->data, + translated_contents->len, &written); + + apr_err2 = apr_file_close(tmp_file); + if (! apr_err) + apr_err = apr_err2; + + /* Make sure the whole CONTENTS were written, else return an error. */ + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"), + tmpfile_name); + goto cleanup; + } + + err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool); + if (err) + goto cleanup; + + /* Get information about the temporary file before the user has + been allowed to edit its contents. */ + apr_err = apr_stat(&finfo_before, tmpfile_apr, + APR_FINFO_MTIME, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* Backdate the file a little bit in case the editor is very fast + and doesn't change the size. (Use two seconds, since some + filesystems have coarse granularity.) It's OK if this call + fails, so we don't check its return value.*/ + apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool); + + /* Stat it again to get the mtime we actually set. */ + apr_err = apr_stat(&finfo_before, tmpfile_apr, + APR_FINFO_MTIME | APR_FINFO_SIZE, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* Prepare the editor command line. */ + err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool); + if (err) + goto cleanup; + cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native); + + /* If the caller wants us to leave the file around, return the path + of the file we'll use, and make a note not to destroy it. */ + if (tmpfile_left) + { + *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool); + remove_file = FALSE; + } + + /* Now, run the editor command line. */ + sys_err = system(cmd); + if (sys_err != 0) + { + /* Extracting any meaning from sys_err is platform specific, so just + use the raw value. */ + err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("system('%s') returned %d"), cmd, sys_err); + goto cleanup; + } + + /* Get information about the temporary file after the assumed editing. */ + apr_err = apr_stat(&finfo_after, tmpfile_apr, + APR_FINFO_MTIME | APR_FINFO_SIZE, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* If the file looks changed... */ + if ((finfo_before.mtime != finfo_after.mtime) || + (finfo_before.size != finfo_after.size)) + { + svn_stringbuf_t *edited_contents_s; + err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool); + if (err) + goto cleanup; + + *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s); + + /* Translate back to UTF8/LF if desired. */ + if (as_text) + { + err = svn_subst_translate_string2(edited_contents, FALSE, FALSE, + *edited_contents, encoding, FALSE, + pool, pool); + if (err) + { + err = svn_error_quick_wrap + (err, + _("Error normalizing edited contents to internal format")); + goto cleanup; + } + } + } + else + { + /* No edits seem to have been made */ + *edited_contents = NULL; + } + + cleanup: + if (remove_file) + { + /* Remove the file from disk. */ + err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool); + + /* Only report remove error if there was no previous error. */ + if (! err && err2) + err = err2; + else + svn_error_clear(err2); + } + + cleanup2: + /* If we against all probability can't cd back, all further relative + file references would be screwed up, so we have to abort. */ + apr_err = apr_filepath_set(old_cwd, pool); + if (apr_err) + { + svn_handle_error2(svn_error_wrap_apr + (apr_err, _("Can't restore working directory")), + stderr, TRUE /* fatal */, "svn: "); + } + + return svn_error_trace(err); +} |