summaryrefslogtreecommitdiff
path: root/subversion/libsvn_subr/cmdline.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_subr/cmdline.c')
-rw-r--r--subversion/libsvn_subr/cmdline.c645
1 files changed, 615 insertions, 30 deletions
diff --git a/subversion/libsvn_subr/cmdline.c b/subversion/libsvn_subr/cmdline.c
index 7dbcbc0..89d1ff3 100644
--- a/subversion/libsvn_subr/cmdline.c
+++ b/subversion/libsvn_subr/cmdline.c
@@ -33,17 +33,20 @@
#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_atomic.h> /* for apr_atomic_init */
#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"
@@ -53,9 +56,13 @@
#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"
@@ -74,6 +81,7 @@ 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
{
@@ -137,7 +145,9 @@ svn_cmdline_init(const char *progname, FILE *error_stream)
_set_error_mode(_OUT_TO_STDERR);
/* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr.
- (Ignored in releas builds) */
+ (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);
@@ -198,11 +208,17 @@ svn_cmdline_init(const char *progname, FILE *error_stream)
return EXIT_FAILURE;
}
- /* This has to happen before any pools are created. */
+ 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 && err->message)
- fprintf(error_stream, "%s", err->message);
+ if (error_stream)
+ svn_handle_error2(err, error_stream, TRUE, prefix_buf);
svn_error_clear(err);
return EXIT_FAILURE;
@@ -220,12 +236,12 @@ svn_cmdline_init(const char *progname, FILE *error_stream)
/* 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_initialize(pool);
+ svn_utf_initialize2(FALSE, pool);
if ((err = svn_nls_init()))
{
- if (error_stream && err->message)
- fprintf(error_stream, "%s", err->message);
+ if (error_stream)
+ svn_handle_error2(err, error_stream, TRUE, prefix_buf);
svn_error_clear(err);
return EXIT_FAILURE;
@@ -340,7 +356,7 @@ svn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool)
{
/* ### 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()))
+ if (SVN__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"));
@@ -363,7 +379,7 @@ svn_cmdline_fflush(FILE *stream)
{
/* ### 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()))
+ if (SVN__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"));
@@ -459,8 +475,8 @@ svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
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));
+ 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. */
@@ -472,7 +488,7 @@ svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
pb->config_dir = config_dir;
}
- if (non_interactive == FALSE)
+ if (!non_interactive)
{
/* This provider doesn't prompt the user in order to get creds;
it prompts the user regarding the caching of creds. */
@@ -489,7 +505,7 @@ svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
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. */
+ /* The windows ssl server certificate CRYPTOAPI provider. */
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"windows",
"ssl_server_trust",
@@ -498,12 +514,21 @@ svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
if (provider)
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+ /* The windows ssl authority certificate CRYPTOAPI provider. */
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "windows",
+ "ssl_server_authority",
+ 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 == FALSE)
+ if (!non_interactive)
{
/* This provider doesn't prompt the user in order to get creds;
it prompts the user regarding the caching of creds. */
@@ -518,8 +543,15 @@ svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
}
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
- if (non_interactive == FALSE)
+ 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,
@@ -533,19 +565,23 @@ svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
2, /* retry limit */ pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
- /* Three ssl prompt providers, for server-certs, client-certs,
- and client-cert-passphrases. */
+ /* 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_prompt_provider
- (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, 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)
{
@@ -625,13 +661,14 @@ 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("", pool);
+ *outstr = svn_stringbuf_create_empty(pool);
if (svn_xml_is_xml_safe(propval->data, propval->len))
{
@@ -648,16 +685,22 @@ svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
}
if (encoding)
- svn_xml_make_open_tag(outstr, pool, svn_xml_protect_pcdata,
- "property", "name", propname,
- "encoding", encoding, NULL);
+ 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,
- "property", "name", propname, NULL);
+ 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, "property");
+ svn_xml_make_close_tag(
+ outstr, pool,
+ inherited_prop ? "inherited_property" : "property");
return;
}
@@ -716,7 +759,7 @@ svn_cmdline__apply_config_options(apr_hash_t *config,
APR_ARRAY_IDX(config_options, i,
svn_cmdline__config_argument_t *);
- cfg = apr_hash_get(config, arg->file, APR_HASH_KEY_STRING);
+ cfg = svn_hash_gets(config, arg->file);
if (cfg)
{
@@ -734,3 +777,545 @@ svn_cmdline__apply_config_options(apr_hash_t *config,
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);
+}