summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_subr/io.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_subr/io.c')
-rw-r--r--subversion/libsvn_subr/io.c4768
1 files changed, 4768 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c
new file mode 100644
index 0000000..58bc540
--- /dev/null
+++ b/subversion/libsvn_subr/io.c
@@ -0,0 +1,4768 @@
+/*
+ * io.c: shared file reading, writing, and probing code.
+ *
+ * ====================================================================
+ * 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 <stdio.h>
+
+#ifndef WIN32
+#include <unistd.h>
+#endif
+
+#ifndef APR_STATUS_IS_EPERM
+#include <errno.h>
+#ifdef EPERM
+#define APR_STATUS_IS_EPERM(s) ((s) == EPERM)
+#else
+#define APR_STATUS_IS_EPERM(s) (0)
+#endif
+#endif
+
+#include <apr_lib.h>
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+#include <apr_portable.h>
+#include <apr_md5.h>
+
+#ifdef WIN32
+#include <arch/win32/apr_arch_file_io.h>
+#endif
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+#include "svn_config.h"
+#include "svn_private_config.h"
+#include "svn_ctype.h"
+
+#include "private/svn_atomic.h"
+#include "private/svn_io_private.h"
+
+#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
+
+/*
+ Windows is 'aided' by a number of types of applications that
+ follow other applications around and open up files they have
+ changed for various reasons (the most intrusive are virus
+ scanners). So, if one of these other apps has glommed onto
+ our file we may get an 'access denied' error.
+
+ This retry loop does not completely solve the problem (who
+ knows how long the other app is going to hold onto it for), but
+ goes a long way towards minimizing it. It is not an infinite
+ loop because there might really be an error.
+
+ Another reason for retrying delete operations on Windows
+ is that they are asynchronous -- the file or directory is not
+ actually deleted until the last handle to it is closed. The
+ retry loop cannot completely solve this problem either, but can
+ help mitigate it.
+*/
+#define RETRY_MAX_ATTEMPTS 100
+#define RETRY_INITIAL_SLEEP 1000
+#define RETRY_MAX_SLEEP 128000
+
+#define RETRY_LOOP(err, expr, retry_test, sleep_test) \
+ do \
+ { \
+ apr_status_t os_err = APR_TO_OS_ERROR(err); \
+ int sleep_count = RETRY_INITIAL_SLEEP; \
+ int retries; \
+ for (retries = 0; \
+ retries < RETRY_MAX_ATTEMPTS && (retry_test); \
+ os_err = APR_TO_OS_ERROR(err)) \
+ { \
+ if (sleep_test) \
+ { \
+ ++retries; \
+ apr_sleep(sleep_count); \
+ if (sleep_count < RETRY_MAX_SLEEP) \
+ sleep_count *= 2; \
+ } \
+ (err) = (expr); \
+ } \
+ } \
+ while (0)
+
+#if defined(EDEADLK) && APR_HAS_THREADS
+#define FILE_LOCK_RETRY_LOOP(err, expr) \
+ RETRY_LOOP(err, \
+ expr, \
+ (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK), \
+ (!APR_STATUS_IS_EINTR(err)))
+#else
+#define FILE_LOCK_RETRY_LOOP(err, expr) \
+ RETRY_LOOP(err, \
+ expr, \
+ (APR_STATUS_IS_EINTR(err)), \
+ 0)
+#endif
+
+#ifndef WIN32_RETRY_LOOP
+#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP)
+#define WIN32_RETRY_LOOP(err, expr) \
+ RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED \
+ || os_err == ERROR_SHARING_VIOLATION \
+ || os_err == ERROR_DIR_NOT_EMPTY), \
+ 1)
+#else
+#define WIN32_RETRY_LOOP(err, expr) ((void)0)
+#endif
+#endif
+
+/* Forward declaration */
+static apr_status_t
+dir_is_empty(const char *dir, apr_pool_t *pool);
+static APR_INLINE svn_error_t *
+do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
+ const char *msg, const char *msg_no_name,
+ apr_pool_t *pool);
+
+/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
+ * operating systems where APR always uses utf-8 as native path format */
+static svn_error_t *
+cstring_to_utf8(const char **path_utf8,
+ const char *path_apr,
+ apr_pool_t *pool)
+{
+#if defined(WIN32) || defined(DARWIN)
+ *path_utf8 = path_apr;
+ return SVN_NO_ERROR;
+#else
+ return svn_path_cstring_to_utf8(path_utf8, path_apr, pool);
+#endif
+}
+
+/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on
+ * operating systems where APR always uses utf-8 as native path format */
+static svn_error_t *
+cstring_from_utf8(const char **path_apr,
+ const char *path_utf8,
+ apr_pool_t *pool)
+{
+#if defined(WIN32) || defined(DARWIN)
+ *path_apr = path_utf8;
+ return SVN_NO_ERROR;
+#else
+ return svn_path_cstring_from_utf8(path_apr, path_utf8, pool);
+#endif
+}
+
+/* Helper function that allows to convert an APR-level PATH to something
+ * that we can pass the svn_error_wrap_apr. Since we use it in context
+ * of error reporting, having *some* path info may be more useful than
+ * having none. Therefore, we use a best effort approach here.
+ *
+ * This is different from svn_io_file_name_get in that it uses a different
+ * signature style and will never fail.
+ */
+static const char *
+try_utf8_from_internal_style(const char *path, apr_pool_t *pool)
+{
+ svn_error_t *error;
+ const char *path_utf8;
+
+ /* Special case. */
+ if (path == NULL)
+ return "(NULL)";
+
+ /* (try to) convert PATH to UTF-8. If that fails, continue with the plain
+ * PATH because it is the best we have. It may actually be UTF-8 already.
+ */
+ error = cstring_to_utf8(&path_utf8, path, pool);
+ if (error)
+ {
+ /* fallback to best representation we have */
+
+ svn_error_clear(error);
+ path_utf8 = path;
+ }
+
+ /* Toggle (back-)slashes etc. as necessary.
+ */
+ return svn_dirent_local_style(path_utf8, pool);
+}
+
+
+/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
+ * NAME is in the internal encoding used by APR; PARENT is in
+ * UTF-8 and in internal (not local) style.
+ *
+ * Use PARENT only for generating an error string if the conversion
+ * fails because NAME could not be represented in UTF-8. In that
+ * case, return a two-level error in which the outer error's message
+ * mentions PARENT, but the inner error's message does not mention
+ * NAME (except possibly in hex) since NAME may not be printable.
+ * Such a compound error at least allows the user to go looking in the
+ * right directory for the problem.
+ *
+ * If there is any other error, just return that error directly.
+ *
+ * If there is any error, the effect on *NAME_P is undefined.
+ *
+ * *NAME_P and NAME may refer to the same storage.
+ */
+static svn_error_t *
+entry_name_to_utf8(const char **name_p,
+ const char *name,
+ const char *parent,
+ apr_pool_t *pool)
+{
+#if defined(WIN32) || defined(DARWIN)
+ *name_p = apr_pstrdup(pool, name);
+ return SVN_NO_ERROR;
+#else
+ svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
+ if (err && err->apr_err == APR_EINVAL)
+ {
+ return svn_error_createf(err->apr_err, err,
+ _("Error converting entry "
+ "in directory '%s' to UTF-8"),
+ svn_dirent_local_style(parent, pool));
+ }
+ return err;
+#endif
+}
+
+
+
+static void
+map_apr_finfo_to_node_kind(svn_node_kind_t *kind,
+ svn_boolean_t *is_special,
+ apr_finfo_t *finfo)
+{
+ *is_special = FALSE;
+
+ if (finfo->filetype == APR_REG)
+ *kind = svn_node_file;
+ else if (finfo->filetype == APR_DIR)
+ *kind = svn_node_dir;
+ else if (finfo->filetype == APR_LNK)
+ {
+ *is_special = TRUE;
+ *kind = svn_node_file;
+ }
+ else
+ *kind = svn_node_unknown;
+}
+
+/* Helper for svn_io_check_path() and svn_io_check_resolved_path();
+ essentially the same semantics as those two, with the obvious
+ interpretation for RESOLVE_SYMLINKS. */
+static svn_error_t *
+io_check_path(const char *path,
+ svn_boolean_t resolve_symlinks,
+ svn_boolean_t *is_special_p,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ apr_int32_t flags;
+ apr_finfo_t finfo;
+ apr_status_t apr_err;
+ const char *path_apr;
+ svn_boolean_t is_special = FALSE;
+
+ if (path[0] == '\0')
+ path = ".";
+
+ /* Not using svn_io_stat() here because we want to check the
+ apr_err return explicitly. */
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
+ apr_err = apr_stat(&finfo, path_apr, flags, pool);
+
+ if (APR_STATUS_IS_ENOENT(apr_err))
+ *kind = svn_node_none;
+ else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err))
+ *kind = svn_node_none;
+ else if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
+ svn_dirent_local_style(path, pool));
+ else
+ map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
+
+ *is_special_p = is_special;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Wrapper for apr_file_open(), taking an APR-encoded filename. */
+static apr_status_t
+file_open(apr_file_t **f,
+ const char *fname_apr,
+ apr_int32_t flag,
+ apr_fileperms_t perm,
+ svn_boolean_t retry_on_failure,
+ apr_pool_t *pool)
+{
+ apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool);
+
+ if (retry_on_failure)
+ {
+ WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool));
+ }
+ return status;
+}
+
+
+svn_error_t *
+svn_io_check_resolved_path(const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_boolean_t ignored;
+ return io_check_path(path, TRUE, &ignored, kind, pool);
+}
+
+svn_error_t *
+svn_io_check_path(const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_boolean_t ignored;
+ return io_check_path(path, FALSE, &ignored, kind, pool);
+}
+
+svn_error_t *
+svn_io_check_special_path(const char *path,
+ svn_node_kind_t *kind,
+ svn_boolean_t *is_special,
+ apr_pool_t *pool)
+{
+ return io_check_path(path, FALSE, is_special, kind, pool);
+}
+
+struct temp_file_cleanup_s
+{
+ apr_pool_t *pool;
+ /* The (APR-encoded) full path of the file to be removed, or NULL if
+ * nothing to do. */
+ const char *fname_apr;
+};
+
+
+static apr_status_t
+temp_file_plain_cleanup_handler(void *baton)
+{
+ struct temp_file_cleanup_s *b = baton;
+ apr_status_t apr_err = APR_SUCCESS;
+
+ if (b->fname_apr)
+ {
+ apr_err = apr_file_remove(b->fname_apr, b->pool);
+ WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool));
+ }
+
+ return apr_err;
+}
+
+
+static apr_status_t
+temp_file_child_cleanup_handler(void *baton)
+{
+ struct temp_file_cleanup_s *b = baton;
+
+ apr_pool_cleanup_kill(b->pool, b,
+ temp_file_plain_cleanup_handler);
+
+ return APR_SUCCESS;
+}
+
+
+svn_error_t *
+svn_io_open_uniquely_named(apr_file_t **file,
+ const char **unique_path,
+ const char *dirpath,
+ const char *filename,
+ const char *suffix,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *path;
+ unsigned int i;
+ struct temp_file_cleanup_s *baton = NULL;
+
+ /* At the beginning, we don't know whether unique_path will need
+ UTF8 conversion */
+ svn_boolean_t needs_utf8_conversion = TRUE;
+
+ SVN_ERR_ASSERT(file || unique_path);
+
+ if (dirpath == NULL)
+ SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
+ if (filename == NULL)
+ filename = "tempfile";
+ if (suffix == NULL)
+ suffix = ".tmp";
+
+ path = svn_dirent_join(dirpath, filename, scratch_pool);
+
+ if (delete_when == svn_io_file_del_on_pool_cleanup)
+ {
+ baton = apr_palloc(result_pool, sizeof(*baton));
+
+ baton->pool = result_pool;
+ baton->fname_apr = NULL;
+
+ /* Because cleanups are run LIFO, we need to make sure to register
+ our cleanup before the apr_file_close cleanup:
+
+ On Windows, you can't remove an open file.
+ */
+ apr_pool_cleanup_register(result_pool, baton,
+ temp_file_plain_cleanup_handler,
+ temp_file_child_cleanup_handler);
+ }
+
+ for (i = 1; i <= 99999; i++)
+ {
+ const char *unique_name;
+ const char *unique_name_apr;
+ apr_file_t *try_file;
+ apr_status_t apr_err;
+ apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
+ | APR_BUFFERED | APR_BINARY);
+
+ if (delete_when == svn_io_file_del_on_close)
+ flag |= APR_DELONCLOSE;
+
+ /* Special case the first attempt -- if we can avoid having a
+ generated numeric portion at all, that's best. So first we
+ try with just the suffix; then future tries add a number
+ before the suffix. (A do-while loop could avoid the repeated
+ conditional, but it's not worth the clarity loss.)
+
+ If the first attempt fails, the first number will be "2".
+ This is good, since "1" would misleadingly imply that
+ the second attempt was actually the first... and if someone's
+ got conflicts on their conflicts, we probably don't want to
+ add to their confusion :-). */
+ if (i == 1)
+ unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix);
+ else
+ unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix);
+
+ /* Hmmm. Ideally, we would append to a native-encoding buf
+ before starting iteration, then convert back to UTF-8 for
+ return. But I suppose that would make the appending code
+ sensitive to i18n in a way it shouldn't be... Oh well. */
+ if (needs_utf8_conversion)
+ {
+ SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name,
+ scratch_pool));
+ if (i == 1)
+ {
+ /* The variable parts of unique_name will not require UTF8
+ conversion. Therefore, if UTF8 conversion had no effect
+ on it in the first iteration, it won't require conversion
+ in any future iteration. */
+ needs_utf8_conversion = strcmp(unique_name_apr, unique_name);
+ }
+ }
+ else
+ unique_name_apr = unique_name;
+
+ apr_err = file_open(&try_file, unique_name_apr, flag,
+ APR_OS_DEFAULT, FALSE, result_pool);
+
+ if (APR_STATUS_IS_EEXIST(apr_err))
+ continue;
+ else if (apr_err)
+ {
+ /* On Win32, CreateFile fails with an "Access Denied" error
+ code, rather than "File Already Exists", if the colliding
+ name belongs to a directory. */
+ if (APR_STATUS_IS_EACCES(apr_err))
+ {
+ apr_finfo_t finfo;
+ apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
+ APR_FINFO_TYPE, scratch_pool);
+
+ if (!apr_err_2 && finfo.filetype == APR_DIR)
+ continue;
+
+#ifdef WIN32
+ apr_err_2 = APR_TO_OS_ERROR(apr_err);
+
+ if (apr_err_2 == ERROR_ACCESS_DENIED ||
+ apr_err_2 == ERROR_SHARING_VIOLATION)
+ {
+ /* The file is in use by another process or is hidden;
+ create a new name, but don't do this 99999 times in
+ case the folder is not writable */
+ i += 797;
+ continue;
+ }
+#endif
+
+ /* Else fall through and return the original error. */
+ }
+
+ if (file)
+ *file = NULL;
+ if (unique_path)
+ *unique_path = NULL;
+ return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
+ svn_dirent_local_style(unique_name,
+ scratch_pool));
+ }
+ else
+ {
+ if (delete_when == svn_io_file_del_on_pool_cleanup)
+ baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr);
+
+ if (file)
+ *file = try_file;
+ else
+ apr_file_close(try_file);
+ if (unique_path)
+ *unique_path = apr_pstrdup(result_pool, unique_name);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (file)
+ *file = NULL;
+ if (unique_path)
+ *unique_path = NULL;
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to make name for '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+}
+
+svn_error_t *
+svn_io_create_unique_link(const char **unique_name_p,
+ const char *path,
+ const char *dest,
+ const char *suffix,
+ apr_pool_t *pool)
+{
+#ifdef HAVE_SYMLINK
+ unsigned int i;
+ const char *unique_name;
+ const char *unique_name_apr;
+ const char *dest_apr;
+ int rv;
+
+ SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool));
+ for (i = 1; i <= 99999; i++)
+ {
+ apr_status_t apr_err;
+
+ /* Special case the first attempt -- if we can avoid having a
+ generated numeric portion at all, that's best. So first we
+ try with just the suffix; then future tries add a number
+ before the suffix. (A do-while loop could avoid the repeated
+ conditional, but it's not worth the clarity loss.)
+
+ If the first attempt fails, the first number will be "2".
+ This is good, since "1" would misleadingly imply that
+ the second attempt was actually the first... and if someone's
+ got conflicts on their conflicts, we probably don't want to
+ add to their confusion :-). */
+ if (i == 1)
+ unique_name = apr_psprintf(pool, "%s%s", path, suffix);
+ else
+ unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
+
+ /* Hmmm. Ideally, we would append to a native-encoding buf
+ before starting iteration, then convert back to UTF-8 for
+ return. But I suppose that would make the appending code
+ sensitive to i18n in a way it shouldn't be... Oh well. */
+ SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool));
+ do {
+ rv = symlink(dest_apr, unique_name_apr);
+ } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
+
+ apr_err = apr_get_os_error();
+
+ if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
+ continue;
+ else if (rv == -1 && apr_err)
+ {
+ /* On Win32, CreateFile fails with an "Access Denied" error
+ code, rather than "File Already Exists", if the colliding
+ name belongs to a directory. */
+ if (APR_STATUS_IS_EACCES(apr_err))
+ {
+ apr_finfo_t finfo;
+ apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
+ APR_FINFO_TYPE, pool);
+
+ if (!apr_err_2
+ && (finfo.filetype == APR_DIR))
+ continue;
+
+ /* Else ignore apr_err_2; better to fall through and
+ return the original error. */
+ }
+
+ *unique_name_p = NULL;
+ return svn_error_wrap_apr(apr_err,
+ _("Can't create symbolic link '%s'"),
+ svn_dirent_local_style(unique_name, pool));
+ }
+ else
+ {
+ *unique_name_p = unique_name;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *unique_name_p = NULL;
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to make name for '%s'"),
+ svn_dirent_local_style(path, pool));
+#else
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Symbolic links are not supported on this "
+ "platform"));
+#endif
+}
+
+svn_error_t *
+svn_io_read_link(svn_string_t **dest,
+ const char *path,
+ apr_pool_t *pool)
+{
+#ifdef HAVE_READLINK
+ svn_string_t dest_apr;
+ const char *path_apr;
+ char buf[1025];
+ ssize_t rv;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+ do {
+ rv = readlink(path_apr, buf, sizeof(buf) - 1);
+ } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
+
+ if (rv == -1)
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't read contents of link"));
+
+ buf[rv] = '\0';
+ dest_apr.data = buf;
+ dest_apr.len = rv;
+
+ /* ### Cast needed, one of these interfaces is wrong */
+ return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
+#else
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Symbolic links are not supported on this "
+ "platform"));
+#endif
+}
+
+
+svn_error_t *
+svn_io_copy_link(const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+
+{
+#ifdef HAVE_READLINK
+ svn_string_t *link_dest;
+ const char *dst_tmp;
+
+ /* Notice what the link is pointing at... */
+ SVN_ERR(svn_io_read_link(&link_dest, src, pool));
+
+ /* Make a tmp-link pointing at the same thing. */
+ SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
+ ".tmp", pool));
+
+ /* Move the tmp-link to link. */
+ return svn_io_file_rename(dst_tmp, dst, pool);
+
+#else
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Symbolic links are not supported on this "
+ "platform"));
+#endif
+}
+
+/* Temporary directory name cache for svn_io_temp_dir() */
+static volatile svn_atomic_t temp_dir_init_state = 0;
+static const char *temp_dir;
+
+/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */
+static svn_error_t *
+init_temp_dir(void *baton, apr_pool_t *scratch_pool)
+{
+ /* Global pool for the temp path */
+ apr_pool_t *global_pool = svn_pool_create(NULL);
+ const char *dir;
+
+ apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
+
+ SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool));
+
+ dir = svn_dirent_internal_style(dir, scratch_pool);
+
+ SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_temp_dir(const char **dir,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_atomic__init_once(&temp_dir_init_state,
+ init_temp_dir, NULL, pool));
+
+ *dir = apr_pstrdup(pool, temp_dir);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/*** Creating, copying and appending files. ***/
+
+/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
+ * allocations.
+ *
+ * NOTE: We don't use apr_copy_file() for this, since it takes filenames
+ * as parameters. Since we want to copy to a temporary file
+ * and rename for atomicity (see below), this would require an extra
+ * close/open pair, which can be expensive, especially on
+ * remote file systems.
+ */
+static apr_status_t
+copy_contents(apr_file_t *from_file,
+ apr_file_t *to_file,
+ apr_pool_t *pool)
+{
+ /* Copy bytes till the cows come home. */
+ while (1)
+ {
+ char buf[SVN__STREAM_CHUNK_SIZE];
+ apr_size_t bytes_this_time = sizeof(buf);
+ apr_status_t read_err;
+ apr_status_t write_err;
+
+ /* Read 'em. */
+ read_err = apr_file_read(from_file, buf, &bytes_this_time);
+ if (read_err && !APR_STATUS_IS_EOF(read_err))
+ {
+ return read_err;
+ }
+
+ /* Write 'em. */
+ write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
+ if (write_err)
+ {
+ return write_err;
+ }
+
+ if (read_err && APR_STATUS_IS_EOF(read_err))
+ {
+ /* Return the results of this close: an error, or success. */
+ return APR_SUCCESS;
+ }
+ }
+ /* NOTREACHED */
+}
+
+
+svn_error_t *
+svn_io_copy_file(const char *src,
+ const char *dst,
+ svn_boolean_t copy_perms,
+ apr_pool_t *pool)
+{
+ apr_file_t *from_file, *to_file;
+ apr_status_t apr_err;
+ const char *dst_tmp;
+ svn_error_t *err;
+
+ /* ### NOTE: sometimes src == dst. In this case, because we copy to a
+ ### temporary file, and then rename over the top of the destination,
+ ### the net result is resetting the permissions on src/dst.
+ ###
+ ### Note: specifically, this can happen during a switch when the desired
+ ### permissions for a file change from one branch to another. See
+ ### switch_tests 17.
+ ###
+ ### ... yes, we should avoid copying to the same file, and we should
+ ### make the "reset perms" explicit. The switch *happens* to work
+ ### because of this copy-to-temp-then-rename implementation. If it
+ ### weren't for that, the switch would break.
+ */
+#ifdef CHECK_FOR_SAME_FILE
+ if (strcmp(src, dst) == 0)
+ return SVN_NO_ERROR;
+#endif
+
+ SVN_ERR(svn_io_file_open(&from_file, src, APR_READ,
+ APR_OS_DEFAULT, pool));
+
+ /* For atomicity, we copy to a tmp file and then rename the tmp
+ file over the real destination. */
+
+ SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp,
+ svn_dirent_dirname(dst, pool),
+ svn_io_file_del_none, pool, pool));
+
+ apr_err = copy_contents(from_file, to_file, pool);
+
+ if (apr_err)
+ {
+ err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"),
+ svn_dirent_local_style(src, pool),
+ svn_dirent_local_style(dst_tmp, pool));
+ }
+ else
+ err = NULL;
+
+ err = svn_error_compose_create(err,
+ svn_io_file_close(from_file, pool));
+
+ err = svn_error_compose_create(err,
+ svn_io_file_close(to_file, pool));
+
+ if (err)
+ {
+ return svn_error_compose_create(
+ err,
+ svn_io_remove_file2(dst_tmp, TRUE, pool));
+ }
+
+ /* If copying perms, set the perms on dst_tmp now, so they will be
+ atomically inherited in the upcoming rename. But note that we
+ had to wait until now to set perms, because if they say
+ read-only, then we'd have failed filling dst_tmp's contents. */
+ if (copy_perms)
+ SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool));
+
+ return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool));
+}
+
+#if !defined(WIN32) && !defined(__OS2__)
+/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */
+static svn_error_t *
+file_perms_set(const char *fname, apr_fileperms_t perms,
+ apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
+
+ status = apr_file_perms_set(fname_apr, perms);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
+ fname);
+ else
+ return SVN_NO_ERROR;
+}
+
+/* Set permissions PERMS on the FILE. This is a cheaper variant of the
+ * file_perms_set wrapper() function because no locale-dependent string
+ * conversion is required. POOL will be used for allocations.
+ */
+static svn_error_t *
+file_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ status = apr_file_name_get(&fname_apr, file);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't get file name"));
+
+ status = apr_file_perms_set(fname_apr, perms);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
+ try_utf8_from_internal_style(fname_apr, pool));
+ else
+ return SVN_NO_ERROR;
+}
+
+#endif /* !WIN32 && !__OS2__ */
+
+svn_error_t *
+svn_io_copy_perms(const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+{
+ /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL,
+ and the path passed to apr_file_perms_set must be encoded
+ in the platform-specific path encoding; not necessary UTF-8.
+ We need a platform-specific implementation to get the
+ permissions right. */
+
+#if !defined(WIN32) && !defined(__OS2__)
+ {
+ apr_finfo_t finfo;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+ svn_error_t *err;
+
+ /* If DST is a symlink, don't bother copying permissions. */
+ SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool));
+ if (is_special)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool));
+ err = file_perms_set(dst, finfo.protection, pool);
+ if (err)
+ {
+ /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL
+ here under normal circumstances, because the perms themselves
+ came from a call to apr_file_info_get(), and we already know
+ this is the non-Win32 case. But if it does happen, it's not
+ an error. */
+ if (APR_STATUS_IS_INCOMPLETE(err->apr_err) ||
+ APR_STATUS_IS_ENOTIMPL(err->apr_err))
+ svn_error_clear(err);
+ else
+ {
+ const char *message;
+ message = apr_psprintf(pool, _("Can't set permissions on '%s'"),
+ svn_dirent_local_style(dst, pool));
+ return svn_error_quick_wrap(err, message);
+ }
+ }
+ }
+#endif /* !WIN32 && !__OS2__ */
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_append_file(const char *src, const char *dst, apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ const char *src_apr, *dst_apr;
+
+ SVN_ERR(cstring_from_utf8(&src_apr, src, pool));
+ SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool));
+
+ apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"),
+ svn_dirent_local_style(src, pool),
+ svn_dirent_local_style(dst, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *svn_io_copy_dir_recursively(const char *src,
+ const char *dst_parent,
+ const char *dst_basename,
+ svn_boolean_t copy_perms,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ apr_status_t status;
+ const char *dst_path;
+ apr_dir_t *this_dir;
+ apr_finfo_t this_entry;
+ apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ /* Make a subpool for recursion */
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* The 'dst_path' is simply dst_parent/dst_basename */
+ dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
+
+ /* Sanity checks: SRC and DST_PARENT are directories, and
+ DST_BASENAME doesn't already exist in DST_PARENT. */
+ SVN_ERR(svn_io_check_path(src, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Source '%s' is not a directory"),
+ svn_dirent_local_style(src, pool));
+
+ SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Destination '%s' is not a directory"),
+ svn_dirent_local_style(dst_parent, pool));
+
+ SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
+ if (kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Destination '%s' already exists"),
+ svn_dirent_local_style(dst_path, pool));
+
+ /* Create the new directory. */
+ /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
+ SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
+
+ /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
+ SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
+
+ for (status = apr_dir_read(&this_entry, flags, this_dir);
+ status == APR_SUCCESS;
+ status = apr_dir_read(&this_entry, flags, this_dir))
+ {
+ if ((this_entry.name[0] == '.')
+ && ((this_entry.name[1] == '\0')
+ || ((this_entry.name[1] == '.')
+ && (this_entry.name[2] == '\0'))))
+ {
+ continue;
+ }
+ else
+ {
+ const char *src_target, *entryname_utf8;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
+ src, subpool));
+ src_target = svn_dirent_join(src, entryname_utf8, subpool);
+
+ if (this_entry.filetype == APR_REG) /* regular file */
+ {
+ const char *dst_target = svn_dirent_join(dst_path,
+ entryname_utf8,
+ subpool);
+ SVN_ERR(svn_io_copy_file(src_target, dst_target,
+ copy_perms, subpool));
+ }
+ else if (this_entry.filetype == APR_LNK) /* symlink */
+ {
+ const char *dst_target = svn_dirent_join(dst_path,
+ entryname_utf8,
+ subpool);
+ SVN_ERR(svn_io_copy_link(src_target, dst_target,
+ subpool));
+ }
+ else if (this_entry.filetype == APR_DIR) /* recurse */
+ {
+ /* Prevent infinite recursion by filtering off our
+ newly created destination path. */
+ if (strcmp(src, dst_parent) == 0
+ && strcmp(entryname_utf8, dst_basename) == 0)
+ continue;
+
+ SVN_ERR(svn_io_copy_dir_recursively
+ (src_target,
+ dst_path,
+ entryname_utf8,
+ copy_perms,
+ cancel_func,
+ cancel_baton,
+ subpool));
+ }
+ /* ### support other APR node types someday?? */
+
+ }
+ }
+
+ if (! (APR_STATUS_IS_ENOENT(status)))
+ return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ status = apr_dir_close(this_dir);
+ if (status)
+ return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ /* Free any memory used by recursion */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
+{
+ const char *path_apr;
+ apr_status_t apr_err;
+
+ if (svn_path_is_empty(path))
+ /* Empty path (current dir) is assumed to always exist,
+ so we do nothing, per docs. */
+ return SVN_NO_ERROR;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool);
+ WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
+ APR_OS_DEFAULT, pool));
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_io_file_create(const char *file,
+ const char *contents,
+ apr_pool_t *pool)
+{
+ apr_file_t *f;
+ apr_size_t written;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_io_file_open(&f, file,
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT,
+ pool));
+ if (contents && *contents)
+ err = svn_io_file_write_full(f, contents, strlen(contents),
+ &written, pool);
+
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(f, pool)));
+}
+
+svn_error_t *svn_io_dir_file_copy(const char *src_path,
+ const char *dest_path,
+ const char *file,
+ apr_pool_t *pool)
+{
+ const char *file_dest_path = svn_dirent_join(dest_path, file, pool);
+ const char *file_src_path = svn_dirent_join(src_path, file, pool);
+
+ return svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool);
+}
+
+
+/*** Modtime checking. ***/
+
+svn_error_t *
+svn_io_file_affected_time(apr_time_t *apr_time,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool));
+
+ *apr_time = finfo.mtime;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_set_file_affected_time(apr_time_t apr_time,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *native_path;
+
+ SVN_ERR(cstring_from_utf8(&native_path, path, pool));
+ status = apr_file_mtime_set(native_path, apr_time, pool);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't set access time of '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+void
+svn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool)
+{
+ apr_time_t now, then;
+ svn_error_t *err;
+ char *sleep_env_var;
+
+ sleep_env_var = getenv(SVN_SLEEP_ENV_VAR);
+
+ if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0)
+ return; /* Allow skipping for testing */
+
+ now = apr_time_now();
+
+ /* Calculate 0.02 seconds after the next second wallclock tick. */
+ then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50);
+
+ /* Worst case is waiting one second, so we can use that time to determine
+ if we can sleep shorter than that */
+ if (path)
+ {
+ apr_finfo_t finfo;
+
+ err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool);
+
+ if (err)
+ {
+ svn_error_clear(err); /* Fall back on original behavior */
+ }
+ else if (finfo.mtime % APR_USEC_PER_SEC)
+ {
+ /* Very simplistic but safe approach:
+ If the filesystem has < sec mtime we can be reasonably sure
+ that the filesystem has <= millisecond precision.
+
+ ## Perhaps find a better algorithm here. This will fail once
+ in every 1000 cases on a millisecond precision filesystem.
+
+ But better to fail once in every thousand cases than every
+ time, like we did before.
+ (All tested filesystems I know have at least microsecond precision.)
+
+ Note for further research on algorithm:
+ FAT32 has < 1 sec precision on ctime, but 2 sec on mtime */
+
+ /* Sleep for at least 1 millisecond.
+ (t < 1000 will be round to 0 in apr) */
+ apr_sleep(1000);
+
+ return;
+ }
+
+ now = apr_time_now(); /* Extract the time used for the path stat */
+
+ if (now >= then)
+ return; /* Passing negative values may suspend indefinitely (Windows) */
+ }
+
+ apr_sleep(then - now);
+}
+
+
+svn_error_t *
+svn_io_filesizes_different_p(svn_boolean_t *different_p,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool)
+{
+ apr_finfo_t finfo1;
+ apr_finfo_t finfo2;
+ apr_status_t status;
+ const char *file1_apr, *file2_apr;
+
+ /* Not using svn_io_stat() because don't want to generate
+ svn_error_t objects for non-error conditions. */
+
+ SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool));
+ SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool));
+
+ /* Stat both files */
+ status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool);
+ if (status)
+ {
+ /* If we got an error stat'ing a file, it could be because the
+ file was removed... or who knows. Whatever the case, we
+ don't know if the filesizes are definitely different, so
+ assume that they're not. */
+ *different_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool);
+ if (status)
+ {
+ /* See previous comment. */
+ *different_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Examine file sizes */
+ if (finfo1.size == finfo2.size)
+ *different_p = FALSE;
+ else
+ *different_p = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_filesizes_three_different_p(svn_boolean_t *different_p12,
+ svn_boolean_t *different_p23,
+ svn_boolean_t *different_p13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo1, finfo2, finfo3;
+ apr_status_t status1, status2, status3;
+ const char *file1_apr, *file2_apr, *file3_apr;
+
+ /* Not using svn_io_stat() because don't want to generate
+ svn_error_t objects for non-error conditions. */
+
+ SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool));
+ SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool));
+ SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool));
+
+ /* Stat all three files */
+ status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool);
+ status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool);
+ status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool);
+
+ /* If we got an error stat'ing a file, it could be because the
+ file was removed... or who knows. Whatever the case, we
+ don't know if the filesizes are definitely different, so
+ assume that they're not. */
+ *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size;
+ *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size;
+ *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_file_checksum2(svn_checksum_t **checksum,
+ const char *file,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool)
+{
+ svn_stream_t *file_stream;
+ svn_stream_t *checksum_stream;
+ apr_file_t* f;
+
+ SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool));
+ file_stream = svn_stream_from_aprfile2(f, FALSE, pool);
+ checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind,
+ TRUE, pool);
+
+ /* Because the checksummed stream will force the reading (and
+ checksumming) of all the file's bytes, we can just close the stream
+ and let its magic work. */
+ return svn_stream_close(checksum_stream);
+}
+
+
+svn_error_t *
+svn_io_file_checksum(unsigned char digest[],
+ const char *file,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool));
+ memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Permissions and modes. ***/
+
+#if !defined(WIN32) && !defined(__OS2__)
+/* Given the file specified by PATH, attempt to create an
+ identical version of it owned by the current user. This is done by
+ moving it to a temporary location, copying the file back to its old
+ path, then deleting the temporarily moved version. All temporary
+ allocations are done in POOL. */
+static svn_error_t *
+reown_file(const char *path,
+ apr_pool_t *pool)
+{
+ const char *unique_name;
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name,
+ svn_dirent_dirname(path, pool),
+ svn_io_file_del_none, pool, pool));
+ SVN_ERR(svn_io_file_rename(path, unique_name, pool));
+ SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool));
+ return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool));
+}
+
+/* Determine what the PERMS for a new file should be by looking at the
+ permissions of a temporary file that we create.
+ Unfortunately, umask() as defined in POSIX provides no thread-safe way
+ to get at the current value of the umask, so what we're doing here is
+ the only way we have to determine which combination of write bits
+ (User/Group/World) should be set by default.
+ Make temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+get_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool)
+{
+ /* the default permissions as read from the temp folder */
+ static apr_fileperms_t default_perms = 0;
+
+ /* Technically, this "racy": Multiple threads may use enter here and
+ try to figure out the default permission concurrently. That's fine
+ since they will end up with the same results. Even more technical,
+ apr_fileperms_t is an atomic type on 32+ bit machines.
+ */
+ if (default_perms == 0)
+ {
+ apr_finfo_t finfo;
+ apr_file_t *fd;
+ const char *fname_base, *fname;
+ apr_uint32_t randomish;
+ svn_error_t *err;
+
+ /* Get the perms for a newly created file to find out what bits
+ should be set.
+
+ Explictly delete the file because we want this file to be as
+ short-lived as possible since its presence means other
+ processes may have to try multiple names.
+
+ Using svn_io_open_uniquely_named() here because other tempfile
+ creation functions tweak the permission bits of files they create.
+ */
+ randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool
+ + (apr_uint32_t)apr_time_now());
+ fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish);
+
+ SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base,
+ NULL, svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool);
+ err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool));
+ err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE,
+ scratch_pool));
+ SVN_ERR(err);
+ *perms = finfo.protection;
+ default_perms = finfo.protection;
+ }
+ else
+ *perms = default_perms;
+
+ return SVN_NO_ERROR;
+}
+
+/* OR together permission bits of the file FD and the default permissions
+ of a file as determined by get_default_file_perms(). Do temporary
+ allocations in SCRATCH_POOL. */
+static svn_error_t *
+merge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+ apr_fileperms_t default_perms;
+
+ SVN_ERR(get_default_file_perms(&default_perms, scratch_pool));
+ SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool));
+
+ /* Glom the perms together. */
+ *perms = default_perms | finfo.protection;
+ return SVN_NO_ERROR;
+}
+
+/* This is a helper function for the svn_io_set_file_read* functions
+ that attempts to honor the users umask when dealing with
+ permission changes. It is a no-op when invoked on a symlink. */
+static svn_error_t *
+io_set_file_perms(const char *path,
+ svn_boolean_t change_readwrite,
+ svn_boolean_t enable_write,
+ svn_boolean_t change_executable,
+ svn_boolean_t executable,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *path_apr;
+ apr_finfo_t finfo;
+ apr_fileperms_t perms_to_set;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ /* Try to change only a minimal amount of the perms first
+ by getting the current perms and adding bits
+ only on where read perms are granted. If this fails
+ fall through to just setting file attributes. */
+ status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
+ if (status)
+ {
+ if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
+ return SVN_NO_ERROR;
+ else if (status != APR_ENOTIMPL)
+ return svn_error_wrap_apr(status,
+ _("Can't change perms of file '%s'"),
+ svn_dirent_local_style(path, pool));
+ return SVN_NO_ERROR;
+ }
+
+ if (finfo.filetype == APR_LNK)
+ return SVN_NO_ERROR;
+
+ perms_to_set = finfo.protection;
+ if (change_readwrite)
+ {
+ if (enable_write) /* Make read-write. */
+ {
+ apr_file_t *fd;
+
+ /* Get the perms for the original file so we'll have any other bits
+ * that were already set (like the execute bits, for example). */
+ SVN_ERR(svn_io_file_open(&fd, path, APR_READ,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(merge_default_file_perms(fd, &perms_to_set, pool));
+ SVN_ERR(svn_io_file_close(fd, pool));
+ }
+ else
+ {
+ if (finfo.protection & APR_UREAD)
+ perms_to_set &= ~APR_UWRITE;
+ if (finfo.protection & APR_GREAD)
+ perms_to_set &= ~APR_GWRITE;
+ if (finfo.protection & APR_WREAD)
+ perms_to_set &= ~APR_WWRITE;
+ }
+ }
+
+ if (change_executable)
+ {
+ if (executable)
+ {
+ if (finfo.protection & APR_UREAD)
+ perms_to_set |= APR_UEXECUTE;
+ if (finfo.protection & APR_GREAD)
+ perms_to_set |= APR_GEXECUTE;
+ if (finfo.protection & APR_WREAD)
+ perms_to_set |= APR_WEXECUTE;
+ }
+ else
+ {
+ if (finfo.protection & APR_UREAD)
+ perms_to_set &= ~APR_UEXECUTE;
+ if (finfo.protection & APR_GREAD)
+ perms_to_set &= ~APR_GEXECUTE;
+ if (finfo.protection & APR_WREAD)
+ perms_to_set &= ~APR_WEXECUTE;
+ }
+ }
+
+ /* If we aren't changing anything then just return, this saves
+ some system calls and helps with shared working copies */
+ if (perms_to_set == finfo.protection)
+ return SVN_NO_ERROR;
+
+ status = apr_file_perms_set(path_apr, perms_to_set);
+ if (!status)
+ return SVN_NO_ERROR;
+
+ if (APR_STATUS_IS_EPERM(status))
+ {
+ /* We don't have permissions to change the
+ permissions! Try a move, copy, and delete
+ workaround to see if we can get the file owned by
+ us. If these succeed, try the permissions set
+ again.
+
+ Note that we only attempt this in the
+ stat-available path. This assumes that the
+ move-copy workaround will only be helpful on
+ platforms that implement apr_stat. */
+ SVN_ERR(reown_file(path, pool));
+ status = apr_file_perms_set(path_apr, perms_to_set);
+ }
+
+ if (!status)
+ return SVN_NO_ERROR;
+
+ if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
+ return SVN_NO_ERROR;
+ else if (status == APR_ENOTIMPL)
+ {
+ /* At least try to set the attributes. */
+ apr_fileattrs_t attrs = 0;
+ apr_fileattrs_t attrs_values = 0;
+
+ if (change_readwrite)
+ {
+ attrs = APR_FILE_ATTR_READONLY;
+ if (!enable_write)
+ attrs_values = APR_FILE_ATTR_READONLY;
+ }
+ if (change_executable)
+ {
+ attrs = APR_FILE_ATTR_EXECUTABLE;
+ if (executable)
+ attrs_values = APR_FILE_ATTR_EXECUTABLE;
+ }
+ status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
+ }
+
+ return svn_error_wrap_apr(status,
+ _("Can't change perms of file '%s'"),
+ svn_dirent_local_style(path, pool));
+}
+#endif /* !WIN32 && !__OS2__ */
+
+#ifdef WIN32
+#if APR_HAS_UNICODE_FS
+/* copy of the apr function utf8_to_unicode_path since apr doesn't export this one */
+static apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retlen,
+ const char* srcstr)
+{
+ /* TODO: The computations could preconvert the string to determine
+ * the true size of the retstr, but that's a memory over speed
+ * tradeoff that isn't appropriate this early in development.
+ *
+ * Allocate the maximum string length based on leading 4
+ * characters of \\?\ (allowing nearly unlimited path lengths)
+ * plus the trailing null, then transform /'s into \\'s since
+ * the \\?\ form doesn't allow '/' path separators.
+ *
+ * Note that the \\?\ form only works for local drive paths, and
+ * \\?\UNC\ is needed UNC paths.
+ */
+ apr_size_t srcremains = strlen(srcstr) + 1;
+ apr_wchar_t *t = retstr;
+ apr_status_t rv;
+
+ /* This is correct, we don't twist the filename if it will
+ * definitely be shorter than 248 characters. It merits some
+ * performance testing to see if this has any effect, but there
+ * seem to be applications that get confused by the resulting
+ * Unicode \\?\ style file names, especially if they use argv[0]
+ * or call the Win32 API functions such as GetModuleName, etc.
+ * Not every application is prepared to handle such names.
+ *
+ * Note also this is shorter than MAX_PATH, as directory paths
+ * are actually limited to 248 characters.
+ *
+ * Note that a utf-8 name can never result in more wide chars
+ * than the original number of utf-8 narrow chars.
+ */
+ if (srcremains > 248) {
+ if (srcstr[1] == ':' && (srcstr[2] == '/' || srcstr[2] == '\\')) {
+ wcscpy (retstr, L"\\\\?\\");
+ retlen -= 4;
+ t += 4;
+ }
+ else if ((srcstr[0] == '/' || srcstr[0] == '\\')
+ && (srcstr[1] == '/' || srcstr[1] == '\\')
+ && (srcstr[2] != '?')) {
+ /* Skip the slashes */
+ srcstr += 2;
+ srcremains -= 2;
+ wcscpy (retstr, L"\\\\?\\UNC\\");
+ retlen -= 8;
+ t += 8;
+ }
+ }
+
+ if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) {
+ return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv;
+ }
+ if (srcremains) {
+ return APR_ENAMETOOLONG;
+ }
+ for (; *t; ++t)
+ if (*t == L'/')
+ *t = L'\\';
+ return APR_SUCCESS;
+}
+#endif
+
+static apr_status_t io_win_file_attrs_set(const char *fname,
+ DWORD attributes,
+ DWORD attr_mask,
+ apr_pool_t *pool)
+{
+ /* this is an implementation of apr_file_attrs_set() but one
+ that uses the proper Windows attributes instead of the apr
+ attributes. This way, we can apply any Windows file and
+ folder attributes even if apr doesn't implement them */
+ DWORD flags;
+ apr_status_t rv;
+#if APR_HAS_UNICODE_FS
+ apr_wchar_t wfname[APR_PATH_MAX];
+#endif
+
+#if APR_HAS_UNICODE_FS
+ IF_WIN_OS_IS_UNICODE
+ {
+ if (rv = io_utf8_to_unicode_path(wfname,
+ sizeof(wfname) / sizeof(wfname[0]),
+ fname))
+ return rv;
+ flags = GetFileAttributesW(wfname);
+ }
+#endif
+#if APR_HAS_ANSI_FS
+ ELSE_WIN_OS_IS_ANSI
+ {
+ flags = GetFileAttributesA(fname);
+ }
+#endif
+
+ if (flags == 0xFFFFFFFF)
+ return apr_get_os_error();
+
+ flags &= ~attr_mask;
+ flags |= (attributes & attr_mask);
+
+#if APR_HAS_UNICODE_FS
+ IF_WIN_OS_IS_UNICODE
+ {
+ rv = SetFileAttributesW(wfname, flags);
+ }
+#endif
+#if APR_HAS_ANSI_FS
+ ELSE_WIN_OS_IS_ANSI
+ {
+ rv = SetFileAttributesA(fname, flags);
+ }
+#endif
+
+ if (rv == 0)
+ return apr_get_os_error();
+
+ return APR_SUCCESS;
+}
+
+#endif
+
+svn_error_t *
+svn_io_set_file_read_write_carefully(const char *path,
+ svn_boolean_t enable_write,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ if (enable_write)
+ return svn_io_set_file_read_write(path, ignore_enoent, pool);
+ return svn_io_set_file_read_only(path, ignore_enoent, pool);
+}
+
+svn_error_t *
+svn_io_set_file_read_only(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ /* On Windows and OS/2, just set the file attributes -- on unix call
+ our internal function which attempts to honor the umask. */
+#if !defined(WIN32) && !defined(__OS2__)
+ return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
+ ignore_enoent, pool);
+#else
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ status = apr_file_attrs_set(path_apr,
+ APR_FILE_ATTR_READONLY,
+ APR_FILE_ATTR_READONLY,
+ pool);
+
+ if (status && status != APR_ENOTIMPL)
+ if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
+ return svn_error_wrap_apr(status,
+ _("Can't set file '%s' read-only"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+
+svn_error_t *
+svn_io_set_file_read_write(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ /* On Windows and OS/2, just set the file attributes -- on unix call
+ our internal function which attempts to honor the umask. */
+#if !defined(WIN32) && !defined(__OS2__)
+ return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
+ ignore_enoent, pool);
+#else
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ status = apr_file_attrs_set(path_apr,
+ 0,
+ APR_FILE_ATTR_READONLY,
+ pool);
+
+ if (status && status != APR_ENOTIMPL)
+ if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
+ return svn_error_wrap_apr(status,
+ _("Can't set file '%s' read-write"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+svn_error_t *
+svn_io_set_file_executable(const char *path,
+ svn_boolean_t executable,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ /* On Windows and OS/2, just exit -- on unix call our internal function
+ which attempts to honor the umask. */
+#if (!defined(WIN32) && !defined(__OS2__))
+ return io_set_file_perms(path, FALSE, FALSE, TRUE, executable,
+ ignore_enoent, pool);
+#else
+ return SVN_NO_ERROR;
+#endif
+}
+
+
+svn_error_t *
+svn_io__is_finfo_read_only(svn_boolean_t *read_only,
+ apr_finfo_t *file_info,
+ apr_pool_t *pool)
+{
+#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
+ apr_status_t apr_err;
+ apr_uid_t uid;
+ apr_gid_t gid;
+
+ *read_only = FALSE;
+
+ apr_err = apr_uid_current(&uid, &gid, pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
+
+ /* Check write bit for current user. */
+ if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
+ *read_only = !(file_info->protection & APR_UWRITE);
+
+ else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
+ *read_only = !(file_info->protection & APR_GWRITE);
+
+ else
+ *read_only = !(file_info->protection & APR_WWRITE);
+
+#else /* WIN32 || __OS2__ || !APR_HAS_USER */
+ *read_only = (file_info->protection & APR_FREADONLY);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io__is_finfo_executable(svn_boolean_t *executable,
+ apr_finfo_t *file_info,
+ apr_pool_t *pool)
+{
+#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
+ apr_status_t apr_err;
+ apr_uid_t uid;
+ apr_gid_t gid;
+
+ *executable = FALSE;
+
+ apr_err = apr_uid_current(&uid, &gid, pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
+
+ /* Check executable bit for current user. */
+ if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
+ *executable = (file_info->protection & APR_UEXECUTE);
+
+ else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
+ *executable = (file_info->protection & APR_GEXECUTE);
+
+ else
+ *executable = (file_info->protection & APR_WEXECUTE);
+
+#else /* WIN32 || __OS2__ || !APR_HAS_USER */
+ *executable = FALSE;
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_is_file_executable(svn_boolean_t *executable,
+ const char *path,
+ apr_pool_t *pool)
+{
+#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
+ apr_finfo_t file_info;
+
+ SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER,
+ pool));
+ SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool));
+
+#else /* WIN32 || __OS2__ || !APR_HAS_USER */
+ *executable = FALSE;
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** File locking. ***/
+#if !defined(WIN32) && !defined(__OS2__)
+/* Clear all outstanding locks on ARG, an open apr_file_t *. */
+static apr_status_t
+file_clear_locks(void *arg)
+{
+ apr_status_t apr_err;
+ apr_file_t *f = arg;
+
+ /* Remove locks. */
+ apr_err = apr_file_unlock(f);
+ if (apr_err)
+ return apr_err;
+
+ return 0;
+}
+#endif
+
+svn_error_t *
+svn_io_lock_open_file(apr_file_t *lockfile_handle,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool)
+{
+ int locktype = APR_FLOCK_SHARED;
+ apr_status_t apr_err;
+ const char *fname;
+
+ if (exclusive)
+ locktype = APR_FLOCK_EXCLUSIVE;
+ if (nonblocking)
+ locktype |= APR_FLOCK_NONBLOCK;
+
+ /* We need this only in case of an error but this is cheap to get -
+ * so we do it here for clarity. */
+ apr_err = apr_file_name_get(&fname, lockfile_handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't get file name"));
+
+ /* Get lock on the filehandle. */
+ apr_err = apr_file_lock(lockfile_handle, locktype);
+
+ /* In deployments with two or more multithreaded servers running on
+ the same system serving two or more fsfs repositories it is
+ possible for a deadlock to occur when getting a write lock on
+ db/txn-current-lock:
+
+ Process 1 Process 2
+ --------- ---------
+ thread 1: get lock in repos A
+ thread 1: get lock in repos B
+ thread 2: block getting lock in repos A
+ thread 2: try to get lock in B *** deadlock ***
+
+ Retry for a while for the deadlock to clear. */
+ FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype));
+
+ if (apr_err)
+ {
+ switch (locktype & APR_FLOCK_TYPEMASK)
+ {
+ case APR_FLOCK_SHARED:
+ return svn_error_wrap_apr(apr_err,
+ _("Can't get shared lock on file '%s'"),
+ try_utf8_from_internal_style(fname, pool));
+ case APR_FLOCK_EXCLUSIVE:
+ return svn_error_wrap_apr(apr_err,
+ _("Can't get exclusive lock on file '%s'"),
+ try_utf8_from_internal_style(fname, pool));
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+/* On Windows and OS/2 file locks are automatically released when
+ the file handle closes */
+#if !defined(WIN32) && !defined(__OS2__)
+ apr_pool_cleanup_register(pool, lockfile_handle,
+ file_clear_locks,
+ apr_pool_cleanup_null);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_unlock_open_file(apr_file_t *lockfile_handle,
+ apr_pool_t *pool)
+{
+ const char *fname;
+ apr_status_t apr_err;
+
+ /* We need this only in case of an error but this is cheap to get -
+ * so we do it here for clarity. */
+ apr_err = apr_file_name_get(&fname, lockfile_handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't get file name"));
+
+ /* The actual unlock attempt. */
+ apr_err = apr_file_unlock(lockfile_handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"),
+ try_utf8_from_internal_style(fname, pool));
+
+/* On Windows and OS/2 file locks are automatically released when
+ the file handle closes */
+#if !defined(WIN32) && !defined(__OS2__)
+ apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_file_lock2(const char *lock_file,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool)
+{
+ int locktype = APR_FLOCK_SHARED;
+ apr_file_t *lockfile_handle;
+ apr_int32_t flags;
+
+ if (exclusive)
+ locktype = APR_FLOCK_EXCLUSIVE;
+
+ flags = APR_READ;
+ if (locktype == APR_FLOCK_EXCLUSIVE)
+ flags |= APR_WRITE;
+
+ /* locktype is never read after this block, so we don't need to bother
+ setting it. If that were to ever change, uncomment the following
+ block.
+ if (nonblocking)
+ locktype |= APR_FLOCK_NONBLOCK;
+ */
+
+ SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
+ APR_OS_DEFAULT,
+ pool));
+
+ /* Get lock on the filehandle. */
+ return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool);
+}
+
+
+
+/* Data consistency/coherency operations. */
+
+svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
+ apr_pool_t *pool)
+{
+ apr_os_file_t filehand;
+
+ /* First make sure that any user-space buffered data is flushed. */
+ SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
+ N_("Can't flush file '%s'"),
+ N_("Can't flush stream"),
+ pool));
+
+ apr_os_file_get(&filehand, file);
+
+ /* Call the operating system specific function to actually force the
+ data to disk. */
+ {
+#ifdef WIN32
+
+ if (! FlushFileBuffers(filehand))
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't flush file to disk"));
+
+#else
+ int rv;
+
+ do {
+ rv = fsync(filehand);
+ } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
+
+ /* If the file is in a memory filesystem, fsync() may return
+ EINVAL. Presumably the user knows the risks, and we can just
+ ignore the error. */
+ if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
+ return SVN_NO_ERROR;
+
+ if (rv == -1)
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't flush file to disk"));
+
+#endif
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/* TODO write test for these two functions, then refactor. */
+
+/* Set RESULT to an svn_stringbuf_t containing the contents of FILE.
+ FILENAME is the FILE's on-disk APR-safe name, or NULL if that name
+ isn't known. If CHECK_SIZE is TRUE, the function will attempt to
+ first stat() the file to determine it's size before sucking its
+ contents into the stringbuf. (Doing so can prevent unnecessary
+ memory usage, an unwanted side effect of the stringbuf growth and
+ reallocation mechanism.) */
+static svn_error_t *
+stringbuf_from_aprfile(svn_stringbuf_t **result,
+ const char *filename,
+ apr_file_t *file,
+ svn_boolean_t check_size,
+ apr_pool_t *pool)
+{
+ apr_size_t len;
+ svn_error_t *err;
+ svn_stringbuf_t *res = NULL;
+ apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE;
+ char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+
+ /* If our caller wants us to check the size of the file for
+ efficient memory handling, we'll try to do so. */
+ if (check_size)
+ {
+ apr_status_t status;
+
+ /* If our caller didn't tell us the file's name, we'll ask APR
+ if it knows the name. No problem if we can't figure it out. */
+ if (! filename)
+ {
+ const char *filename_apr;
+ if (! (status = apr_file_name_get(&filename_apr, file)))
+ filename = filename_apr;
+ }
+
+ /* If we now know the filename, try to stat(). If we succeed,
+ we know how to allocate our stringbuf. */
+ if (filename)
+ {
+ apr_finfo_t finfo;
+ if (! (status = apr_stat(&finfo, filename, APR_FINFO_MIN, pool)))
+ res_initial_len = (apr_size_t)finfo.size;
+ }
+ }
+
+
+ /* XXX: We should check the incoming data for being of type binary. */
+
+ res = svn_stringbuf_create_ensure(res_initial_len, pool);
+
+ /* apr_file_read will not return data and eof in the same call. So this loop
+ * is safe from missing read data. */
+ len = SVN__STREAM_CHUNK_SIZE;
+ err = svn_io_file_read(file, buf, &len, pool);
+ while (! err)
+ {
+ svn_stringbuf_appendbytes(res, buf, len);
+ len = SVN__STREAM_CHUNK_SIZE;
+ err = svn_io_file_read(file, buf, &len, pool);
+ }
+
+ /* Having read all the data we *expect* EOF */
+ if (err && !APR_STATUS_IS_EOF(err->apr_err))
+ return err;
+ svn_error_clear(err);
+
+ *result = res;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_stringbuf_from_file2(svn_stringbuf_t **result,
+ const char *filename,
+ apr_pool_t *pool)
+{
+ apr_file_t *f;
+
+ if (filename[0] == '-' && filename[1] == '\0')
+ {
+ apr_status_t apr_err;
+ if ((apr_err = apr_file_open_stdin(&f, pool)))
+ return svn_error_wrap_apr(apr_err, _("Can't open stdin"));
+ SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool));
+ SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool));
+ }
+ return svn_io_file_close(f, pool);
+}
+
+
+svn_error_t *
+svn_stringbuf_from_file(svn_stringbuf_t **result,
+ const char *filename,
+ apr_pool_t *pool)
+{
+ if (filename[0] == '-' && filename[1] == '\0')
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Reading from stdin is disallowed"));
+ return svn_stringbuf_from_file2(result, filename, pool);
+}
+
+svn_error_t *
+svn_stringbuf_from_aprfile(svn_stringbuf_t **result,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ return stringbuf_from_aprfile(result, NULL, file, TRUE, pool);
+}
+
+
+
+/* Deletion. */
+
+svn_error_t *
+svn_io_remove_file2(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t apr_err;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool));
+
+ apr_err = apr_file_remove(path_apr, scratch_pool);
+ if (!apr_err
+ || (ignore_enoent
+ && (APR_STATUS_IS_ENOENT(apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(apr_err))))
+ return SVN_NO_ERROR;
+
+#ifdef WIN32
+ /* If the target is read only NTFS reports EACCESS and FAT/FAT32
+ reports EEXIST */
+ if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err))
+ {
+ /* Set the destination file writable because Windows will not
+ allow us to delete when path is read-only */
+ SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool));
+ apr_err = apr_file_remove(path_apr, scratch_pool);
+
+ if (!apr_err)
+ return SVN_NO_ERROR;
+ }
+
+ {
+ apr_status_t os_err = APR_TO_OS_ERROR(apr_err);
+ /* Check to make sure we aren't trying to delete a directory */
+ if (os_err == ERROR_ACCESS_DENIED || os_err == ERROR_SHARING_VIOLATION)
+ {
+ apr_finfo_t finfo;
+
+ if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool)
+ && finfo.filetype == APR_REG)
+ {
+ WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr,
+ scratch_pool));
+ }
+ }
+
+ /* Just return the delete error */
+ }
+#endif
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_remove_dir(const char *path, apr_pool_t *pool)
+{
+ return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+}
+
+/*
+ Mac OS X has a bug where if you're reading the contents of a
+ directory via readdir in a loop, and you remove one of the entries in
+ the directory and the directory has 338 or more files in it you will
+ skip over some of the entries in the directory. Needless to say,
+ this causes problems if you are using this kind of loop inside a
+ function that is recursively deleting a directory, because when you
+ get around to removing the directory it will still have something in
+ it. A similar problem has been observed in other BSDs. This bug has
+ since been fixed. See http://www.vnode.ch/fixing_seekdir for details.
+
+ The workaround is to delete the files only _after_ the initial
+ directory scan. A previous workaround involving rewinddir is
+ problematic on Win32 and some NFS clients, notably NetBSD.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3501.
+*/
+
+/* Neither windows nor unix allows us to delete a non-empty
+ directory.
+
+ This is a function to perform the equivalent of 'rm -rf'. */
+svn_error_t *
+svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_pool_t *subpool;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+
+ /* Check for pending cancellation request.
+ If we need to bail out, do so early. */
+
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ subpool = svn_pool_create(pool);
+
+ err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool);
+ if (err)
+ {
+ /* if the directory doesn't exist, our mission is accomplished */
+ if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+
+ for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *fullpath;
+
+ fullpath = svn_dirent_join(path, name, subpool);
+ if (dirent->kind == svn_node_dir)
+ {
+ /* Don't check for cancellation, the callee will immediately do so */
+ SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func,
+ cancel_baton, subpool));
+ }
+ else
+ {
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ err = svn_io_remove_file2(fullpath, FALSE, subpool);
+ if (err)
+ return svn_error_createf
+ (err->apr_err, err, _("Can't remove '%s'"),
+ svn_dirent_local_style(fullpath, subpool));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return svn_io_dir_remove_nonrecursive(path, pool);
+}
+
+svn_error_t *
+svn_io_get_dir_filenames(apr_hash_t **dirents,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE,
+ pool, pool));
+}
+
+svn_io_dirent2_t *
+svn_io_dirent2_create(apr_pool_t *result_pool)
+{
+ svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent));
+
+ /*dirent->kind = svn_node_none;
+ dirent->special = FALSE;*/
+ dirent->filesize = SVN_INVALID_FILESIZE;
+ /*dirent->mtime = 0;*/
+
+ return dirent;
+}
+
+svn_io_dirent2_t *
+svn_io_dirent2_dup(const svn_io_dirent2_t *item,
+ apr_pool_t *result_pool)
+{
+ return apr_pmemdup(result_pool,
+ item,
+ sizeof(*item));
+}
+
+svn_error_t *
+svn_io_get_dirents3(apr_hash_t **dirents,
+ const char *path,
+ svn_boolean_t only_check_type,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t status;
+ apr_dir_t *this_dir;
+ apr_finfo_t this_entry;
+ apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ if (!only_check_type)
+ flags |= APR_FINFO_SIZE | APR_FINFO_MTIME;
+
+ *dirents = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool));
+
+ for (status = apr_dir_read(&this_entry, flags, this_dir);
+ status == APR_SUCCESS;
+ status = apr_dir_read(&this_entry, flags, this_dir))
+ {
+ if ((this_entry.name[0] == '.')
+ && ((this_entry.name[1] == '\0')
+ || ((this_entry.name[1] == '.')
+ && (this_entry.name[2] == '\0'))))
+ {
+ continue;
+ }
+ else
+ {
+ const char *name;
+ svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool);
+
+ SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool));
+
+ map_apr_finfo_to_node_kind(&(dirent->kind),
+ &(dirent->special),
+ &this_entry);
+
+ if (!only_check_type)
+ {
+ dirent->filesize = this_entry.size;
+ dirent->mtime = this_entry.mtime;
+ }
+
+ svn_hash_sets(*dirents, name, dirent);
+ }
+ }
+
+ if (! (APR_STATUS_IS_ENOENT(status)))
+ return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+
+ status = apr_dir_close(this_dir);
+ if (status)
+ return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p,
+ const char *path,
+ svn_boolean_t verify_truename,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+ svn_io_dirent2_t *dirent;
+ svn_error_t *err;
+ apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK
+ | APR_FINFO_SIZE | APR_FINFO_MTIME;
+
+#if defined(WIN32) || defined(__OS2__)
+ if (verify_truename)
+ wanted |= APR_FINFO_NAME;
+#endif
+
+ err = svn_io_stat(&finfo, path, wanted, scratch_pool);
+
+ if (err && ignore_enoent &&
+ (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+ dirent = svn_io_dirent2_create(result_pool);
+ SVN_ERR_ASSERT(dirent->kind == svn_node_none);
+
+ *dirent_p = dirent;
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+#if defined(WIN32) || defined(__OS2__) || defined(DARWIN)
+ if (verify_truename)
+ {
+ const char *requested_name = svn_dirent_basename(path, NULL);
+
+ if (requested_name[0] == '\0')
+ {
+ /* No parent directory. No need to stat/verify */
+ }
+#if defined(WIN32) || defined(__OS2__)
+ else if (finfo.name)
+ {
+ const char *name_on_disk;
+ SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path,
+ scratch_pool));
+
+ if (strcmp(name_on_disk, requested_name) /* != 0 */)
+ {
+ if (ignore_enoent)
+ {
+ *dirent_p = svn_io_dirent2_create(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_createf(APR_ENOENT, NULL,
+ _("Path '%s' not found, case obstructed by '%s'"),
+ svn_dirent_local_style(path, scratch_pool),
+ name_on_disk);
+ }
+ }
+#elif defined(DARWIN)
+ /* Currently apr doesn't set finfo.name on DARWIN, returning
+ APR_INCOMPLETE.
+ ### Can we optimize this in another way? */
+ else
+ {
+ apr_hash_t *dirents;
+
+ err = svn_io_get_dirents3(&dirents,
+ svn_dirent_dirname(path, scratch_pool),
+ TRUE /* only_check_type */,
+ scratch_pool, scratch_pool);
+
+ if (err && ignore_enoent
+ && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+
+ *dirent_p = svn_io_dirent2_create(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ if (! svn_hash_gets(dirents, requested_name))
+ {
+ if (ignore_enoent)
+ {
+ *dirent_p = svn_io_dirent2_create(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_createf(APR_ENOENT, NULL,
+ _("Path '%s' not found"),
+ svn_dirent_local_style(path, scratch_pool));
+ }
+ }
+#endif
+ }
+#endif
+
+ dirent = svn_io_dirent2_create(result_pool);
+ map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo);
+
+ dirent->filesize = finfo.size;
+ dirent->mtime = finfo.mtime;
+
+ *dirent_p = dirent;
+
+ return SVN_NO_ERROR;
+}
+
+/* Pool userdata key for the error file passed to svn_io_start_cmd(). */
+#define ERRFILE_KEY "svn-io-start-cmd-errfile"
+
+/* Handle an error from the child process (before command execution) by
+ printing DESC and the error string corresponding to STATUS to stderr. */
+static void
+handle_child_process_error(apr_pool_t *pool, apr_status_t status,
+ const char *desc)
+{
+ char errbuf[256];
+ apr_file_t *errfile;
+ void *p;
+
+ /* We can't do anything if we get an error here, so just return. */
+ if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool))
+ return;
+ errfile = p;
+
+ if (errfile)
+ /* What we get from APR is in native encoding. */
+ apr_file_printf(errfile, "%s: %s",
+ desc, apr_strerror(status, errbuf,
+ sizeof(errbuf)));
+}
+
+
+svn_error_t *
+svn_io_start_cmd3(apr_proc_t *cmd_proc,
+ const char *path,
+ const char *cmd,
+ const char *const *args,
+ const char *const *env,
+ svn_boolean_t inherit,
+ svn_boolean_t infile_pipe,
+ apr_file_t *infile,
+ svn_boolean_t outfile_pipe,
+ apr_file_t *outfile,
+ svn_boolean_t errfile_pipe,
+ apr_file_t *errfile,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_procattr_t *cmdproc_attr;
+ int num_args;
+ const char **args_native;
+ const char *cmd_apr;
+
+ SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe));
+ SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe));
+ SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe));
+
+ /* Create the process attributes. */
+ apr_err = apr_procattr_create(&cmdproc_attr, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't create process '%s' attributes"),
+ cmd);
+
+ /* Make sure we invoke cmd directly, not through a shell. */
+ apr_err = apr_procattr_cmdtype_set(cmdproc_attr,
+ inherit ? APR_PROGRAM_PATH : APR_PROGRAM);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"),
+ cmd);
+
+ /* Set the process's working directory. */
+ if (path)
+ {
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+ apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' directory"),
+ cmd);
+ }
+
+ /* Use requested inputs and outputs.
+
+ ### Unfortunately each of these apr functions creates a pipe and then
+ overwrites the pipe file descriptor with the descriptor we pass
+ in. The pipes can then never be closed. This is an APR bug. */
+ if (infile)
+ {
+ apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child input"),
+ cmd);
+ }
+ if (outfile)
+ {
+ apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child outfile"),
+ cmd);
+ }
+ if (errfile)
+ {
+ apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child errfile"),
+ cmd);
+ }
+
+ /* Forward request for pipes to APR. */
+ if (infile_pipe || outfile_pipe || errfile_pipe)
+ {
+ apr_err = apr_procattr_io_set(cmdproc_attr,
+ infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
+ outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
+ errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' stdio pipes"),
+ cmd);
+ }
+
+ /* Have the child print any problems executing its program to errfile. */
+ apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child errfile for "
+ "error handler"),
+ cmd);
+ apr_err = apr_procattr_child_errfn_set(cmdproc_attr,
+ handle_child_process_error);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' error handler"),
+ cmd);
+
+ /* Convert cmd and args from UTF-8 */
+ SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool));
+ for (num_args = 0; args[num_args]; num_args++)
+ ;
+ args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *));
+ args_native[num_args] = NULL;
+ while (num_args--)
+ {
+ /* ### Well, it turns out that on APR on Windows expects all
+ program args to be in UTF-8. Callers of svn_io_run_cmd
+ should be aware of that. */
+ SVN_ERR(cstring_from_utf8(&args_native[num_args],
+ args[num_args], pool));
+ }
+
+
+ /* Start the cmd command. */
+ apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native,
+ inherit ? NULL : env, cmdproc_attr, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd);
+
+ return SVN_NO_ERROR;
+}
+
+#undef ERRFILE_KEY
+
+svn_error_t *
+svn_io_wait_for_cmd(apr_proc_t *cmd_proc,
+ const char *cmd,
+ int *exitcode,
+ apr_exit_why_e *exitwhy,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_exit_why_e exitwhy_val;
+ int exitcode_val;
+
+ /* The Win32 apr_proc_wait doesn't set this... */
+ exitwhy_val = APR_PROC_EXIT;
+
+ /* Wait for the cmd command to finish. */
+ apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT);
+ if (!APR_STATUS_IS_CHILD_DONE(apr_err))
+ return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"),
+ cmd);
+
+ if (exitwhy)
+ *exitwhy = exitwhy_val;
+ else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)
+ && APR_PROC_CHECK_CORE_DUMP(exitwhy_val))
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' failed (signal %d, core dumped)"),
+ cmd, exitcode_val);
+ else if (APR_PROC_CHECK_SIGNALED(exitwhy_val))
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' failed (signal %d)"),
+ cmd, exitcode_val);
+ else if (! APR_PROC_CHECK_EXIT(exitwhy_val))
+ /* Don't really know what happened here. */
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' failed (exitwhy %d, exitcode %d)"),
+ cmd, exitwhy_val, exitcode_val);
+
+ if (exitcode)
+ *exitcode = exitcode_val;
+ else if (exitcode_val != 0)
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' returned error exitcode %d"), cmd, exitcode_val);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_run_cmd(const char *path,
+ const char *cmd,
+ const char *const *args,
+ int *exitcode,
+ apr_exit_why_e *exitwhy,
+ svn_boolean_t inherit,
+ apr_file_t *infile,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ apr_pool_t *pool)
+{
+ apr_proc_t cmd_proc;
+
+ SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit,
+ FALSE, infile, FALSE, outfile, FALSE, errfile,
+ pool));
+
+ return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool);
+}
+
+
+svn_error_t *
+svn_io_run_diff2(const char *dir,
+ const char *const *user_args,
+ int num_user_args,
+ const char *label1,
+ const char *label2,
+ const char *from,
+ const char *to,
+ int *pexitcode,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const char *diff_cmd,
+ apr_pool_t *pool)
+{
+ const char **args;
+ int i;
+ int exitcode;
+ int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ if (pexitcode == NULL)
+ pexitcode = &exitcode;
+
+ if (user_args != NULL)
+ nargs += num_user_args;
+ else
+ nargs += 1; /* -u */
+
+ if (label1 != NULL)
+ nargs += 2; /* the -L and the label itself */
+ if (label2 != NULL)
+ nargs += 2; /* the -L and the label itself */
+
+ args = apr_palloc(subpool, nargs * sizeof(char *));
+
+ i = 0;
+ args[i++] = diff_cmd;
+
+ if (user_args != NULL)
+ {
+ int j;
+ for (j = 0; j < num_user_args; ++j)
+ args[i++] = user_args[j];
+ }
+ else
+ args[i++] = "-u"; /* assume -u if the user didn't give us any args */
+
+ if (label1 != NULL)
+ {
+ args[i++] = "-L";
+ args[i++] = label1;
+ }
+ if (label2 != NULL)
+ {
+ args[i++] = "-L";
+ args[i++] = label2;
+ }
+
+ args[i++] = svn_dirent_local_style(from, subpool);
+ args[i++] = svn_dirent_local_style(to, subpool);
+ args[i++] = NULL;
+
+ SVN_ERR_ASSERT(i == nargs);
+
+ SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE,
+ NULL, outfile, errfile, subpool));
+
+ /* The man page for (GNU) diff describes the return value as:
+
+ "An exit status of 0 means no differences were found, 1 means
+ some differences were found, and 2 means trouble."
+
+ A return value of 2 typically occurs when diff cannot read its input
+ or write to its output, but in any case we probably ought to return an
+ error for anything other than 0 or 1 as the output is likely to be
+ corrupt.
+ */
+ if (*pexitcode != 0 && *pexitcode != 1)
+ return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("'%s' returned %d"),
+ svn_dirent_local_style(diff_cmd, pool),
+ *pexitcode);
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_run_diff3_3(int *exitcode,
+ const char *dir,
+ const char *mine,
+ const char *older,
+ const char *yours,
+ const char *mine_label,
+ const char *older_label,
+ const char *yours_label,
+ apr_file_t *merged,
+ const char *diff3_cmd,
+ const apr_array_header_t *user_args,
+ apr_pool_t *pool)
+{
+ const char **args = apr_palloc(pool,
+ sizeof(char*) * (13
+ + (user_args
+ ? user_args->nelts
+ : 1)));
+#ifndef NDEBUG
+ int nargs = 12;
+#endif
+ int i = 0;
+
+ /* Labels fall back to sensible defaults if not specified. */
+ if (mine_label == NULL)
+ mine_label = ".working";
+ if (older_label == NULL)
+ older_label = ".old";
+ if (yours_label == NULL)
+ yours_label = ".new";
+
+ /* Set up diff3 command line. */
+ args[i++] = diff3_cmd;
+ if (user_args)
+ {
+ int j;
+ for (j = 0; j < user_args->nelts; ++j)
+ args[i++] = APR_ARRAY_IDX(user_args, j, const char *);
+#ifndef NDEBUG
+ nargs += user_args->nelts;
+#endif
+ }
+ else
+ {
+ args[i++] = "-E"; /* We tried "-A" here, but that caused
+ overlapping identical changes to
+ conflict. See issue #682. */
+#ifndef NDEBUG
+ ++nargs;
+#endif
+ }
+ args[i++] = "-m";
+ args[i++] = "-L";
+ args[i++] = mine_label;
+ args[i++] = "-L";
+ args[i++] = older_label; /* note: this label is ignored if
+ using 2-part markers, which is the
+ case with "-E". */
+ args[i++] = "-L";
+ args[i++] = yours_label;
+#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG
+ {
+ svn_boolean_t has_arg;
+
+ /* ### FIXME: we really shouldn't be reading the config here;
+ instead, the necessary bits should be passed in by the caller.
+ But should we add another parameter to this function, when the
+ whole external diff3 thing might eventually go away? */
+ apr_hash_t *config;
+ svn_config_t *cfg;
+
+ SVN_ERR(svn_config_get_config(&config, pool));
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
+ SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG,
+ TRUE));
+ if (has_arg)
+ {
+ const char *diff_cmd, *diff_utf8;
+ svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF);
+ SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool));
+ args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL);
+#ifndef NDEBUG
+ ++nargs;
+#endif
+ }
+ }
+#endif
+ args[i++] = svn_dirent_local_style(mine, pool);
+ args[i++] = svn_dirent_local_style(older, pool);
+ args[i++] = svn_dirent_local_style(yours, pool);
+ args[i++] = NULL;
+#ifndef NDEBUG
+ SVN_ERR_ASSERT(i == nargs);
+#endif
+
+ /* Run diff3, output the merged text into the scratch file. */
+ SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args,
+ exitcode, NULL,
+ TRUE, /* keep environment */
+ NULL, merged, NULL,
+ pool));
+
+ /* According to the diff3 docs, a '0' means the merge was clean, and
+ '1' means conflict markers were found. Anything else is real
+ error. */
+ if ((*exitcode != 0) && (*exitcode != 1))
+ return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Error running '%s': exitcode was %d, "
+ "args were:"
+ "\nin directory '%s', basenames:\n%s\n%s\n%s"),
+ svn_dirent_local_style(diff3_cmd, pool),
+ *exitcode,
+ svn_dirent_local_style(dir, pool),
+ /* Don't call svn_path_local_style() on
+ the basenames. We don't want them to
+ be absolute, and we don't need the
+ separator conversion. */
+ mine, older, yours);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Canonicalize a string for hashing. Modifies KEY in place. */
+static APR_INLINE char *
+fileext_tolower(char *key)
+{
+ register char *p;
+ for (p = key; *p != 0; ++p)
+ *p = (char)apr_tolower(*p);
+ return key;
+}
+
+
+svn_error_t *
+svn_io_parse_mimetypes_file(apr_hash_t **type_map,
+ const char *mimetypes_file,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_hash_t *types = apr_hash_make(pool);
+ svn_boolean_t eof = FALSE;
+ svn_stringbuf_t *buf;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_file_t *types_file;
+ svn_stream_t *mimetypes_stream;
+
+ SVN_ERR(svn_io_file_open(&types_file, mimetypes_file,
+ APR_READ, APR_OS_DEFAULT, pool));
+ mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool);
+
+ while (1)
+ {
+ apr_array_header_t *tokens;
+ const char *type;
+
+ svn_pool_clear(subpool);
+
+ /* Read a line. */
+ if ((err = svn_stream_readline(mimetypes_stream, &buf,
+ APR_EOL_STR, &eof, subpool)))
+ break;
+
+ /* Only pay attention to non-empty, non-comment lines. */
+ if (buf->len)
+ {
+ int i;
+
+ if (buf->data[0] == '#')
+ continue;
+
+ /* Tokenize (into our return pool). */
+ tokens = svn_cstring_split(buf->data, " \t", TRUE, pool);
+ if (tokens->nelts < 2)
+ continue;
+
+ /* The first token in a multi-token line is the media type.
+ Subsequent tokens are filename extensions associated with
+ that media type. */
+ type = APR_ARRAY_IDX(tokens, 0, const char *);
+ for (i = 1; i < tokens->nelts; i++)
+ {
+ /* We can safely address 'ext' as a non-const string because
+ * we know svn_cstring_split() allocated it in 'pool' for us. */
+ char *ext = APR_ARRAY_IDX(tokens, i, char *);
+ fileext_tolower(ext);
+ svn_hash_sets(types, ext, type);
+ }
+ }
+ if (eof)
+ break;
+ }
+ svn_pool_destroy(subpool);
+
+ /* If there was an error above, close the file (ignoring any error
+ from *that*) and return the originally error. */
+ if (err)
+ {
+ svn_error_clear(svn_stream_close(mimetypes_stream));
+ return err;
+ }
+
+ /* Close the stream (which closes the underlying file, too). */
+ SVN_ERR(svn_stream_close(mimetypes_stream));
+
+ *type_map = types;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_detect_mimetype2(const char **mimetype,
+ const char *file,
+ apr_hash_t *mimetype_map,
+ apr_pool_t *pool)
+{
+ static const char * const generic_binary = "application/octet-stream";
+
+ svn_node_kind_t kind;
+ apr_file_t *fh;
+ svn_error_t *err;
+ unsigned char block[1024];
+ apr_size_t amt_read = sizeof(block);
+
+ /* Default return value is NULL. */
+ *mimetype = NULL;
+
+ /* If there is a mimetype_map provided, we'll first try to look up
+ our file's extension in the map. Failing that, we'll run the
+ heuristic. */
+ if (mimetype_map)
+ {
+ const char *type_from_map;
+ char *path_ext; /* Can point to physical const memory but only when
+ svn_path_splitext sets it to "". */
+ svn_path_splitext(NULL, (const char **)&path_ext, file, pool);
+ fileext_tolower(path_ext);
+ if ((type_from_map = svn_hash_gets(mimetype_map, path_ext)))
+ {
+ *mimetype = type_from_map;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* See if this file even exists, and make sure it really is a file. */
+ SVN_ERR(svn_io_check_path(file, &kind, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ _("Can't detect MIME type of non-file '%s'"),
+ svn_dirent_local_style(file, pool));
+
+ SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool));
+
+ /* Read a block of data from FILE. */
+ err = svn_io_file_read(fh, block, &amt_read, pool);
+ if (err && ! APR_STATUS_IS_EOF(err->apr_err))
+ return err;
+ svn_error_clear(err);
+
+ /* Now close the file. No use keeping it open any more. */
+ SVN_ERR(svn_io_file_close(fh, pool));
+
+ if (svn_io_is_binary_data(block, amt_read))
+ *mimetype = generic_binary;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_io_is_binary_data(const void *data, apr_size_t len)
+{
+ const unsigned char *buf = data;
+
+ if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
+ {
+ /* This is an empty UTF-8 file which only contains the UTF-8 BOM.
+ * Treat it as plain text. */
+ return FALSE;
+ }
+
+ /* Right now, this function is going to be really stupid. It's
+ going to examine the block of data, and make sure that 15%
+ of the bytes are such that their value is in the ranges 0x07-0x0D
+ or 0x20-0x7F, and that none of those bytes is 0x00. If those
+ criteria are not met, we're calling it binary.
+
+ NOTE: Originally, I intended to target 85% of the bytes being in
+ the specified ranges, but I flubbed the condition. At any rate,
+ folks aren't complaining, so I'm not sure that it's worth
+ adjusting this retroactively now. --cmpilato */
+ if (len > 0)
+ {
+ apr_size_t i;
+ apr_size_t binary_count = 0;
+
+ /* Run through the data we've read, counting the 'binary-ish'
+ bytes. HINT: If we see a 0x00 byte, we'll set our count to its
+ max and stop reading the file. */
+ for (i = 0; i < len; i++)
+ {
+ if (buf[i] == 0)
+ {
+ binary_count = len;
+ break;
+ }
+ if ((buf[i] < 0x07)
+ || ((buf[i] > 0x0D) && (buf[i] < 0x20))
+ || (buf[i] > 0x7F))
+ {
+ binary_count++;
+ }
+ }
+
+ return (((binary_count * 1000) / len) > 850);
+ }
+
+ return FALSE;
+}
+
+
+svn_error_t *
+svn_io_detect_mimetype(const char **mimetype,
+ const char *file,
+ apr_pool_t *pool)
+{
+ return svn_io_detect_mimetype2(mimetype, file, NULL, pool);
+}
+
+
+svn_error_t *
+svn_io_file_open(apr_file_t **new_file, const char *fname,
+ apr_int32_t flag, apr_fileperms_t perm,
+ apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
+ status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE,
+ pool);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't open file '%s'"),
+ svn_dirent_local_style(fname, pool));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+static APR_INLINE svn_error_t *
+do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
+ const char *msg, const char *msg_no_name,
+ apr_pool_t *pool)
+{
+ const char *name;
+ svn_error_t *err;
+
+ if (! status)
+ return SVN_NO_ERROR;
+
+ err = svn_io_file_name_get(&name, file, pool);
+ if (err)
+ name = NULL;
+ svn_error_clear(err);
+
+ /* ### Issue #3014: Return a specific error for broken pipes,
+ * ### with a single element in the error chain. */
+ if (APR_STATUS_IS_EPIPE(status))
+ return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
+
+ if (name)
+ return svn_error_wrap_apr(status, _(msg),
+ try_utf8_from_internal_style(name, pool));
+ else
+ return svn_error_wrap_apr(status, "%s", _(msg_no_name));
+}
+
+
+svn_error_t *
+svn_io_file_close(apr_file_t *file, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_close(file),
+ N_("Can't close file '%s'"),
+ N_("Can't close stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file),
+ N_("Can't read file '%s'"),
+ N_("Can't read stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file),
+ N_("Can't write file '%s'"),
+ N_("Can't write stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
+ apr_file_t *file, apr_pool_t *pool)
+{
+ /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
+ wanted &= ~SVN__APR_FINFO_MASK_OUT;
+
+ return do_io_file_wrapper_cleanup(
+ file, apr_file_info_get(finfo, wanted, file),
+ N_("Can't get attribute information from file '%s'"),
+ N_("Can't get attribute information from stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_read(apr_file_t *file, void *buf,
+ apr_size_t *nbytes, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes),
+ N_("Can't read file '%s'"),
+ N_("Can't read stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_read_full2(apr_file_t *file, void *buf,
+ apr_size_t nbytes, apr_size_t *bytes_read,
+ svn_boolean_t *hit_eof,
+ apr_pool_t *pool)
+{
+ apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read);
+ if (hit_eof)
+ {
+ if (APR_STATUS_IS_EOF(status))
+ {
+ *hit_eof = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else
+ *hit_eof = FALSE;
+ }
+
+ return do_io_file_wrapper_cleanup(file, status,
+ N_("Can't read file '%s'"),
+ N_("Can't read stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
+ apr_off_t *offset, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(
+ file, apr_file_seek(file, where, offset),
+ N_("Can't set position pointer in file '%s'"),
+ N_("Can't set position pointer in stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_write(apr_file_t *file, const void *buf,
+ apr_size_t *nbytes, apr_pool_t *pool)
+{
+ return svn_error_trace(do_io_file_wrapper_cleanup(
+ file, apr_file_write(file, buf, nbytes),
+ N_("Can't write to file '%s'"),
+ N_("Can't write to stream"),
+ pool));
+}
+
+
+svn_error_t *
+svn_io_file_write_full(apr_file_t *file, const void *buf,
+ apr_size_t nbytes, apr_size_t *bytes_written,
+ apr_pool_t *pool)
+{
+ /* We cannot simply call apr_file_write_full on Win32 as it may fail
+ for larger values of NBYTES. In that case, we have to emulate the
+ "_full" part here. Thus, always call apr_file_write directly on
+ Win32 as this minimizes overhead for small data buffers. */
+#ifdef WIN32
+#define MAXBUFSIZE 30*1024
+ apr_size_t bw = nbytes;
+ apr_size_t to_write = nbytes;
+
+ /* try a simple "write everything at once" first */
+ apr_status_t rv = apr_file_write(file, buf, &bw);
+ buf = (char *)buf + bw;
+ to_write -= bw;
+
+ /* if the OS cannot handle that, use smaller chunks */
+ if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
+ && nbytes > MAXBUFSIZE)
+ {
+ do {
+ bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write;
+ rv = apr_file_write(file, buf, &bw);
+ buf = (char *)buf + bw;
+ to_write -= bw;
+ } while (rv == APR_SUCCESS && to_write > 0);
+ }
+
+ /* bytes_written may actually be NULL */
+ if (bytes_written)
+ *bytes_written = nbytes - to_write;
+#undef MAXBUFSIZE
+#else
+ apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
+#endif
+
+ return svn_error_trace(do_io_file_wrapper_cleanup(
+ file, rv,
+ N_("Can't write to file '%s'"),
+ N_("Can't write to stream"),
+ pool));
+}
+
+
+svn_error_t *
+svn_io_write_unique(const char **tmp_path,
+ const char *dirpath,
+ const void *buf,
+ apr_size_t nbytes,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *pool)
+{
+ apr_file_t *new_file;
+ svn_error_t *err;
+
+ SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath,
+ delete_when, pool, pool));
+
+ err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool);
+
+ if (!err)
+ err = svn_io_file_flush_to_disk(new_file, pool);
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(new_file, pool)));
+}
+
+
+svn_error_t *
+svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
+{
+ /* This is a work-around. APR would flush the write buffer
+ _after_ truncating the file causing now invalid buffered
+ data to be written behind OFFSET. */
+ SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
+ N_("Can't flush file '%s'"),
+ N_("Can't flush stream"),
+ pool));
+
+ return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset),
+ N_("Can't truncate file '%s'"),
+ N_("Can't truncate stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
+ apr_pool_t *pool)
+{
+ /* variables */
+ apr_size_t total_read = 0;
+ svn_boolean_t eof = FALSE;
+ const char *name;
+ svn_error_t *err;
+ apr_size_t buf_size = *limit;
+
+ while (buf_size > 0)
+ {
+ /* read a fair chunk of data at once. But don't get too ambitious
+ * as that would result in too much waste. Also make sure we can
+ * put a NUL after the last byte read.
+ */
+ apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128;
+ apr_size_t bytes_read = 0;
+ char *eol;
+
+ /* read data block (or just a part of it) */
+ SVN_ERR(svn_io_file_read_full2(file, buf, to_read,
+ &bytes_read, &eof, pool));
+
+ /* look or a newline char */
+ buf[bytes_read] = 0;
+ eol = strchr(buf, '\n');
+ if (eol)
+ {
+ apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read;
+
+ *eol = 0;
+ *limit = total_read + (eol - buf);
+
+ /* correct the file pointer:
+ * appear as though we just had read the newline char
+ */
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
+
+ return SVN_NO_ERROR;
+ }
+ else if (eof)
+ {
+ /* no EOL found but we hit the end of the file.
+ * Generate a nice EOF error object and return it.
+ */
+ char dummy;
+ SVN_ERR(svn_io_file_getc(&dummy, file, pool));
+ }
+
+ /* next data chunk */
+ buf_size -= bytes_read;
+ buf += bytes_read;
+ total_read += bytes_read;
+ }
+
+ /* buffer limit has been exceeded without finding the EOL */
+ err = svn_io_file_name_get(&name, file, pool);
+ if (err)
+ name = NULL;
+ svn_error_clear(err);
+
+ if (name)
+ return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Can't read length line in file '%s'"),
+ svn_dirent_local_style(name, pool));
+ else
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Can't read length line in stream"));
+}
+
+
+svn_error_t *
+svn_io_stat(apr_finfo_t *finfo, const char *fname,
+ apr_int32_t wanted, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *fname_apr;
+
+ /* APR doesn't like "" directories */
+ if (fname[0] == '\0')
+ fname = ".";
+
+ SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
+
+ /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
+ wanted &= ~SVN__APR_FINFO_MASK_OUT;
+
+ status = apr_stat(finfo, fname_apr, wanted, pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+ svn_dirent_local_style(fname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_file_rename(const char *from_path, const char *to_path,
+ apr_pool_t *pool)
+{
+ apr_status_t status = APR_SUCCESS;
+ const char *from_path_apr, *to_path_apr;
+
+ SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool));
+ SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool));
+
+ status = apr_file_rename(from_path_apr, to_path_apr, pool);
+
+#if defined(WIN32) || defined(__OS2__)
+ /* If the target file is read only NTFS reports EACCESS and
+ FAT/FAT32 reports EEXIST */
+ if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
+ {
+ /* Set the destination file writable because Windows will not
+ allow us to rename when to_path is read-only, but will
+ allow renaming when from_path is read only. */
+ SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
+
+ status = apr_file_rename(from_path_apr, to_path_apr, pool);
+ }
+ WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool));
+#endif /* WIN32 || __OS2__ */
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
+ svn_dirent_local_style(from_path, pool),
+ svn_dirent_local_style(to_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_file_move(const char *from_path, const char *to_path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_io_file_rename(from_path, to_path, pool);
+
+ if (err && APR_STATUS_IS_EXDEV(err->apr_err))
+ {
+ const char *tmp_to_path;
+
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path,
+ svn_dirent_dirname(to_path, pool),
+ svn_io_file_del_none,
+ pool, pool));
+
+ err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool);
+ if (err)
+ goto failed_tmp;
+
+ err = svn_io_file_rename(tmp_to_path, to_path, pool);
+ if (err)
+ goto failed_tmp;
+
+ err = svn_io_remove_file2(from_path, FALSE, pool);
+ if (! err)
+ return SVN_NO_ERROR;
+
+ svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool));
+
+ return err;
+
+ failed_tmp:
+ svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool));
+ }
+
+ return err;
+}
+
+/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden.
+ HIDDEN determines if the hidden attribute
+ should be set on the newly created directory. */
+static svn_error_t *
+dir_make(const char *path, apr_fileperms_t perm,
+ svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ /* APR doesn't like "" directories */
+ if (path_apr[0] == '\0')
+ path_apr = ".";
+
+#if (APR_OS_DEFAULT & APR_WSTICKY)
+ /* The APR shipped with httpd 2.0.50 contains a bug where
+ APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits.
+ There is a special case for file creation, but not directory
+ creation, so directories wind up getting created with the sticky
+ bit set. (There is no such thing as a setuid directory, and the
+ setgid bit is apparently ignored at mkdir() time.) If we detect
+ this problem, work around it by unsetting those bits if we are
+ passed APR_OS_DEFAULT. */
+ if (perm == APR_OS_DEFAULT)
+ perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY);
+#endif
+
+ status = apr_dir_make(path_apr, perm, pool);
+ WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+#ifdef APR_FILE_ATTR_HIDDEN
+ if (hidden)
+ {
+#ifndef WIN32
+ status = apr_file_attrs_set(path_apr,
+ APR_FILE_ATTR_HIDDEN,
+ APR_FILE_ATTR_HIDDEN,
+ pool);
+#else
+ /* on Windows, use our wrapper so we can also set the
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */
+ status = io_win_file_attrs_set(path_apr,
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ pool);
+
+#endif
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
+ svn_dirent_local_style(path, pool));
+ }
+#endif
+
+/* Windows does not implement sgid. Skip here because retrieving
+ the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented
+ to be 'incredibly expensive'. */
+#ifndef WIN32
+ if (sgid)
+ {
+ apr_finfo_t finfo;
+
+ /* Per our contract, don't do error-checking. Some filesystems
+ * don't support the sgid bit, and that's okay. */
+ status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool);
+
+ if (!status)
+ apr_file_perms_set(path_apr, finfo.protection | APR_GSETID);
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool)
+{
+ return dir_make(path, perm, FALSE, FALSE, pool);
+}
+
+svn_error_t *
+svn_io_dir_make_hidden(const char *path, apr_fileperms_t perm,
+ apr_pool_t *pool)
+{
+ return dir_make(path, perm, TRUE, FALSE, pool);
+}
+
+svn_error_t *
+svn_io_dir_make_sgid(const char *path, apr_fileperms_t perm,
+ apr_pool_t *pool)
+{
+ return dir_make(path, perm, FALSE, TRUE, pool);
+}
+
+
+svn_error_t *
+svn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *dirname_apr;
+
+ /* APR doesn't like "" directories */
+ if (dirname[0] == '\0')
+ dirname = ".";
+
+ SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
+
+ status = apr_dir_open(new_dir, dirname_apr, pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't open directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *dirname_apr;
+
+ SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
+
+ status = apr_dir_remove(dirname_apr, pool);
+
+#ifdef WIN32
+ {
+ svn_boolean_t retry = TRUE;
+
+ if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY)
+ {
+ apr_status_t empty_status = dir_is_empty(dirname_apr, pool);
+
+ if (APR_STATUS_IS_ENOTEMPTY(empty_status))
+ retry = FALSE;
+ }
+
+ if (retry)
+ {
+ WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool));
+ }
+ }
+#endif
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't remove directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_dir_read(apr_finfo_t *finfo,
+ apr_int32_t wanted,
+ apr_dir_t *thedir,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+
+ status = apr_dir_read(finfo, wanted, thedir);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't read directory"));
+
+ /* It would be nice to use entry_name_to_utf8() below, but can we
+ get the dir's path out of an apr_dir_t? I don't see a reliable
+ way to do it. */
+
+ if (finfo->fname)
+ SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool));
+
+ if (finfo->name)
+ SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_close(apr_dir_t *thedir)
+{
+ apr_status_t apr_err = apr_dir_close(thedir);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error closing directory"));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_walk2(const char *dirname,
+ apr_int32_t wanted,
+ svn_io_walk_func_t walk_func,
+ void *walk_baton,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_dir_t *handle;
+ apr_pool_t *subpool;
+ const char *dirname_apr;
+ apr_finfo_t finfo;
+
+ wanted |= APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
+ wanted &= ~SVN__APR_FINFO_MASK_OUT;
+
+ /* The documentation for apr_dir_read used to state that "." and ".."
+ will be returned as the first two files, but it doesn't
+ work that way in practice, in particular ext3 on Linux-2.6 doesn't
+ follow the rules. For details see
+ http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666
+
+ If APR ever does implement "dot-first" then it would be possible to
+ remove the svn_io_stat and walk_func calls and use the walk_func
+ inside the loop.
+
+ Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is
+ documented to provide it, so we have to do a bit extra. */
+ SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool));
+ SVN_ERR(cstring_from_utf8(&finfo.name,
+ svn_dirent_basename(dirname, pool),
+ pool));
+ finfo.valid |= APR_FINFO_NAME;
+ SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool));
+
+ SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
+
+ /* APR doesn't like "" directories */
+ if (dirname_apr[0] == '\0')
+ dirname_apr = ".";
+
+ apr_err = apr_dir_open(&handle, dirname_apr, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ /* iteration subpool */
+ subpool = svn_pool_create(pool);
+
+ while (1)
+ {
+ const char *name_utf8;
+ const char *full_path;
+
+ svn_pool_clear(subpool);
+
+ apr_err = apr_dir_read(&finfo, wanted, handle);
+ if (APR_STATUS_IS_ENOENT(apr_err))
+ break;
+ else if (apr_err)
+ {
+ return svn_error_wrap_apr(apr_err,
+ _("Can't read directory entry in '%s'"),
+ svn_dirent_local_style(dirname, pool));
+ }
+
+ if (finfo.filetype == APR_DIR)
+ {
+ if (finfo.name[0] == '.'
+ && (finfo.name[1] == '\0'
+ || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
+ /* skip "." and ".." */
+ continue;
+
+ /* some other directory. recurse. it will be passed to the
+ callback inside the recursion. */
+ SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
+ subpool));
+ full_path = svn_dirent_join(dirname, name_utf8, subpool);
+ SVN_ERR(svn_io_dir_walk2(full_path,
+ wanted,
+ walk_func,
+ walk_baton,
+ subpool));
+ }
+ else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
+ {
+ /* some other directory. pass it to the callback. */
+ SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
+ subpool));
+ full_path = svn_dirent_join(dirname, name_utf8, subpool);
+ SVN_ERR((*walk_func)(walk_baton,
+ full_path,
+ &finfo,
+ subpool));
+ }
+ /* else:
+ Some other type of file; skip it for now. We've reserved the
+ right to expand our coverage here in the future, though,
+ without revving this API.
+ */
+ }
+
+ svn_pool_destroy(subpool);
+
+ apr_err = apr_dir_close(handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/**
+ * Determine if a directory is empty or not.
+ * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not.
+ * @param path The directory.
+ * @param pool Used for temporary allocation.
+ * @remark If path is not a directory, or some other error occurs,
+ * then return the appropriate apr status code.
+ *
+ * (This function is written in APR style, in anticipation of
+ * perhaps someday being moved to APR as 'apr_dir_is_empty'.)
+ */
+static apr_status_t
+dir_is_empty(const char *dir, apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_dir_t *dir_handle;
+ apr_finfo_t finfo;
+ apr_status_t retval = APR_SUCCESS;
+
+ /* APR doesn't like "" directories */
+ if (dir[0] == '\0')
+ dir = ".";
+
+ apr_err = apr_dir_open(&dir_handle, dir, pool);
+ if (apr_err != APR_SUCCESS)
+ return apr_err;
+
+ for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle);
+ apr_err == APR_SUCCESS;
+ apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle))
+ {
+ /* Ignore entries for this dir and its parent, robustly.
+ (APR promises that they'll come first, so technically
+ this guard could be moved outside the loop. But Ryan Bloom
+ says he doesn't believe it, and I believe him. */
+ if (! (finfo.name[0] == '.'
+ && (finfo.name[1] == '\0'
+ || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
+ {
+ retval = APR_ENOTEMPTY;
+ break;
+ }
+ }
+
+ /* Make sure we broke out of the loop for the right reason. */
+ if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err))
+ return apr_err;
+
+ apr_err = apr_dir_close(dir_handle);
+ if (apr_err != APR_SUCCESS)
+ return apr_err;
+
+ return retval;
+}
+
+
+svn_error_t *
+svn_io_dir_empty(svn_boolean_t *is_empty_p,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ status = dir_is_empty(path_apr, pool);
+
+ if (!status)
+ *is_empty_p = TRUE;
+ else if (APR_STATUS_IS_ENOTEMPTY(status))
+ *is_empty_p = FALSE;
+ else
+ return svn_error_wrap_apr(status, _("Can't check directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Version/format files ***/
+
+svn_error_t *
+svn_io_write_version_file(const char *path,
+ int version,
+ apr_pool_t *pool)
+{
+ const char *path_tmp;
+ const char *format_contents = apr_psprintf(pool, "%d\n", version);
+
+ SVN_ERR_ASSERT(version >= 0);
+
+ SVN_ERR(svn_io_write_unique(&path_tmp,
+ svn_dirent_dirname(path, pool),
+ format_contents, strlen(format_contents),
+ svn_io_file_del_none, pool));
+
+#if defined(WIN32) || defined(__OS2__)
+ /* make the destination writable, but only on Windows, because
+ Windows does not let us replace read-only files. */
+ SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
+#endif /* WIN32 || __OS2__ */
+
+ /* rename the temp file as the real destination */
+ SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
+
+ /* And finally remove the perms to make it read only */
+ return svn_io_set_file_read_only(path, FALSE, pool);
+}
+
+
+svn_error_t *
+svn_io_read_version_file(int *version,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_file_t *format_file;
+ char buf[80];
+ apr_size_t len;
+ svn_error_t *err;
+
+ /* Read a chunk of data from PATH */
+ SVN_ERR(svn_io_file_open(&format_file, path, APR_READ,
+ APR_OS_DEFAULT, pool));
+ len = sizeof(buf);
+ err = svn_io_file_read(format_file, buf, &len, pool);
+
+ /* Close the file. */
+ SVN_ERR(svn_error_compose_create(err,
+ svn_io_file_close(format_file, pool)));
+
+ /* If there was no data in PATH, return an error. */
+ if (len == 0)
+ return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Reading '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ /* Check that the first line contains only digits. */
+ {
+ apr_size_t i;
+
+ for (i = 0; i < len; ++i)
+ {
+ char c = buf[i];
+
+ if (i > 0 && (c == '\r' || c == '\n'))
+ {
+ buf[i] = '\0';
+ break;
+ }
+ if (! svn_ctype_isdigit(c))
+ return svn_error_createf
+ (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("First line of '%s' contains non-digit"),
+ svn_dirent_local_style(path, pool));
+ }
+ }
+
+ /* Convert to integer. */
+ SVN_ERR(svn_cstring_atoi(version, buf));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Do a byte-for-byte comparison of FILE1 and FILE2. */
+static svn_error_t *
+contents_identical_p(svn_boolean_t *identical_p,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_size_t bytes_read1, bytes_read2;
+ char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+ char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+ apr_file_t *file1_h;
+ apr_file_t *file2_h;
+ svn_boolean_t eof1 = FALSE;
+ svn_boolean_t eof2 = FALSE;
+
+ SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
+ pool));
+
+ err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
+ pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(file1_h, pool)));
+
+ *identical_p = TRUE; /* assume TRUE, until disproved below */
+ while (!err && !eof1 && !eof2)
+ {
+ err = svn_io_file_read_full2(file1_h, buf1,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read1,
+ &eof1, pool);
+ if (err)
+ break;
+
+ err = svn_io_file_read_full2(file2_h, buf2,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read2,
+ &eof2, pool);
+ if (err)
+ break;
+
+ if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1))
+ {
+ *identical_p = FALSE;
+ break;
+ }
+ }
+
+ /* Special case: one file being a prefix of the other and the shorter
+ * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */
+ if (!err && (eof1 != eof2))
+ *identical_p = FALSE;
+
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_error_compose_create(svn_io_file_close(file1_h, pool),
+ svn_io_file_close(file2_h, pool))));
+}
+
+
+
+/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */
+static svn_error_t *
+contents_three_identical_p(svn_boolean_t *identical_p12,
+ svn_boolean_t *identical_p23,
+ svn_boolean_t *identical_p13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ apr_size_t bytes_read1, bytes_read2, bytes_read3;
+ char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+ char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+ char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+ apr_file_t *file1_h;
+ apr_file_t *file2_h;
+ apr_file_t *file3_h;
+ svn_boolean_t eof1 = FALSE;
+ svn_boolean_t eof2 = FALSE;
+ svn_boolean_t eof3 = FALSE;
+ svn_boolean_t read_1, read_2, read_3;
+
+ SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
+ scratch_pool));
+
+ err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
+ scratch_pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(file1_h, scratch_pool)));
+
+ err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT,
+ scratch_pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_error_compose_create(svn_io_file_close(file1_h,
+ scratch_pool),
+ svn_io_file_close(file2_h,
+ scratch_pool))));
+
+ /* assume TRUE, until disproved below */
+ *identical_p12 = *identical_p23 = *identical_p13 = TRUE;
+ /* We need to read as long as no error occurs, and as long as one of the
+ * flags could still change due to a read operation */
+ while (!err
+ && ((*identical_p12 && !eof1 && !eof2)
+ || (*identical_p23 && !eof2 && !eof3)
+ || (*identical_p13 && !eof1 && !eof3)))
+ {
+ read_1 = read_2 = read_3 = FALSE;
+
+ /* As long as a file is not at the end yet, and it is still
+ * potentially identical to another file, we read the next chunk.*/
+ if (!eof1 && (identical_p12 || identical_p13))
+ {
+ err = svn_io_file_read_full2(file1_h, buf1,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read1,
+ &eof1, scratch_pool);
+ if (err)
+ break;
+ read_1 = TRUE;
+ }
+
+ if (!eof2 && (identical_p12 || identical_p23))
+ {
+ err = svn_io_file_read_full2(file2_h, buf2,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read2,
+ &eof2, scratch_pool);
+ if (err)
+ break;
+ read_2 = TRUE;
+ }
+
+ if (!eof3 && (identical_p13 || identical_p23))
+ {
+ err = svn_io_file_read_full2(file3_h, buf3,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read3,
+ &eof3, scratch_pool);
+ if (err)
+ break;
+ read_3 = TRUE;
+ }
+
+ /* If the files are still marked identical, and at least one of them
+ * is not at the end of file, we check whether they differ, and set
+ * their flag to false then. */
+ if (*identical_p12
+ && (read_1 || read_2)
+ && ((eof1 != eof2)
+ || (bytes_read1 != bytes_read2)
+ || memcmp(buf1, buf2, bytes_read1)))
+ {
+ *identical_p12 = FALSE;
+ }
+
+ if (*identical_p23
+ && (read_2 || read_3)
+ && ((eof2 != eof3)
+ || (bytes_read2 != bytes_read3)
+ || memcmp(buf2, buf3, bytes_read2)))
+ {
+ *identical_p23 = FALSE;
+ }
+
+ if (*identical_p13
+ && (read_1 || read_3)
+ && ((eof1 != eof3)
+ || (bytes_read1 != bytes_read3)
+ || memcmp(buf1, buf3, bytes_read3)))
+ {
+ *identical_p13 = FALSE;
+ }
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_error_compose_create(
+ svn_io_file_close(file1_h, scratch_pool),
+ svn_error_compose_create(
+ svn_io_file_close(file2_h, scratch_pool),
+ svn_io_file_close(file3_h, scratch_pool)))));
+}
+
+
+
+svn_error_t *
+svn_io_files_contents_same_p(svn_boolean_t *same,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool)
+{
+ svn_boolean_t q;
+
+ SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool));
+
+ if (q)
+ {
+ *same = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(contents_identical_p(&q, file1, file2, pool));
+
+ if (q)
+ *same = TRUE;
+ else
+ *same = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_files_contents_three_same_p(svn_boolean_t *same12,
+ svn_boolean_t *same23,
+ svn_boolean_t *same13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t diff_size12, diff_size23, diff_size13;
+
+ SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12,
+ &diff_size23,
+ &diff_size13,
+ file1,
+ file2,
+ file3,
+ scratch_pool));
+
+ if (diff_size12 && diff_size23 && diff_size13)
+ {
+ *same12 = *same23 = *same13 = FALSE;
+ }
+ else if (diff_size12 && diff_size23)
+ {
+ *same12 = *same23 = FALSE;
+ SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool));
+ }
+ else if (diff_size23 && diff_size13)
+ {
+ *same23 = *same13 = FALSE;
+ SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool));
+ }
+ else if (diff_size12 && diff_size13)
+ {
+ *same12 = *same13 = FALSE;
+ SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13);
+ SVN_ERR(contents_three_identical_p(same12, same23, same13,
+ file1, file2, file3,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#ifdef WIN32
+/* Counter value of file_mktemp request (used in a threadsafe way), to make
+ sure that a single process normally never generates the same tempname
+ twice */
+static volatile apr_uint32_t tempname_counter = 0;
+#endif
+
+/* Creates a new temporary file in DIRECTORY with apr flags FLAGS.
+ Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name.
+ Perform temporary allocations in SCRATCH_POOL and the result in
+ RESULT_POOL. */
+static svn_error_t *
+temp_file_create(apr_file_t **new_file,
+ const char **new_file_name,
+ const char *directory,
+ apr_int32_t flags,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#ifndef WIN32
+ const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool);
+ const char *templ_apr;
+ apr_status_t status;
+
+ SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool));
+
+ /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the
+ data available in POOL and we need a non-const pointer here,
+ as apr changes the template to return the new filename. */
+ status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't create temporary file from "
+ "template '%s'"), templ);
+
+ /* Translate the returned path back to utf-8 before returning it */
+ return svn_error_trace(svn_path_cstring_to_utf8(new_file_name,
+ templ_apr,
+ result_pool));
+#else
+ /* The Windows implementation of apr_file_mktemp doesn't handle access
+ denied errors correctly. Therefore we implement our own temp file
+ creation function here. */
+
+ /* ### Most of this is borrowed from the svn_io_open_uniquely_named(),
+ ### the function we used before. But we try to guess a more unique
+ ### name before trying if it exists. */
+
+ /* Offset by some time value and a unique request nr to make the number
+ +- unique for both this process and on the computer */
+ int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter)
+ + GetCurrentProcessId();
+ int i;
+
+ /* ### Maybe use an iterpool? */
+ for (i = 0; i <= 99999; i++)
+ {
+ apr_uint32_t unique_nr;
+ const char *unique_name;
+ const char *unique_name_apr;
+ apr_file_t *try_file;
+ apr_status_t apr_err;
+
+ /* Generate a number that should be unique for this application and
+ usually for the entire computer to reduce the number of cycles
+ through this loop. (A bit of calculation is much cheaper then
+ disk io) */
+ unique_nr = baseNr + 3 * i;
+
+ unique_name = svn_dirent_join(directory,
+ apr_psprintf(scratch_pool, "svn-%X",
+ unique_nr),
+ scratch_pool);
+
+ SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool));
+
+ apr_err = file_open(&try_file, unique_name_apr, flags,
+ APR_OS_DEFAULT, FALSE, scratch_pool);
+
+ if (APR_STATUS_IS_EEXIST(apr_err))
+ continue;
+ else if (apr_err)
+ {
+ /* On Win32, CreateFile fails with an "Access Denied" error
+ code, rather than "File Already Exists", if the colliding
+ name belongs to a directory. */
+
+ if (APR_STATUS_IS_EACCES(apr_err))
+ {
+ apr_finfo_t finfo;
+ apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
+ APR_FINFO_TYPE, scratch_pool);
+
+ if (!apr_err_2 && finfo.filetype == APR_DIR)
+ continue;
+
+ apr_err_2 = APR_TO_OS_ERROR(apr_err);
+
+ if (apr_err_2 == ERROR_ACCESS_DENIED ||
+ apr_err_2 == ERROR_SHARING_VIOLATION)
+ {
+ /* The file is in use by another process or is hidden;
+ create a new name, but don't do this 99999 times in
+ case the folder is not writable */
+ i += 797;
+ continue;
+ }
+
+ /* Else fall through and return the original error. */
+ }
+
+ return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
+ svn_dirent_local_style(unique_name,
+ scratch_pool));
+ }
+ else
+ {
+ /* Move file to the right pool */
+ apr_err = apr_file_setaside(new_file, try_file, result_pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"),
+ svn_dirent_local_style(unique_name,
+ scratch_pool));
+
+ *new_file_name = apr_pstrdup(result_pool, unique_name);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to make name in '%s'"),
+ svn_dirent_local_style(directory, scratch_pool));
+#endif
+}
+
+/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */
+svn_error_t *
+svn_io_file_name_get(const char **filename,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ status = apr_file_name_get(&fname_apr, file);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't get file name"));
+
+ if (fname_apr)
+ SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool));
+ else
+ *filename = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_open_unique_file3(apr_file_t **file,
+ const char **unique_path,
+ const char *dirpath,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *tempfile;
+ const char *tempname;
+ struct temp_file_cleanup_s *baton = NULL;
+ apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL |
+ APR_BUFFERED | APR_BINARY);
+#if !defined(WIN32) && !defined(__OS2__)
+ apr_fileperms_t perms;
+ svn_boolean_t using_system_temp_dir = FALSE;
+#endif
+
+ SVN_ERR_ASSERT(file || unique_path);
+ if (file)
+ *file = NULL;
+ if (unique_path)
+ *unique_path = NULL;
+
+ if (dirpath == NULL)
+ {
+#if !defined(WIN32) && !defined(__OS2__)
+ using_system_temp_dir = TRUE;
+#endif
+ SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
+ }
+
+ switch (delete_when)
+ {
+ case svn_io_file_del_on_pool_cleanup:
+ baton = apr_palloc(result_pool, sizeof(*baton));
+ baton->pool = result_pool;
+ baton->fname_apr = NULL;
+
+ /* Because cleanups are run LIFO, we need to make sure to register
+ our cleanup before the apr_file_close cleanup:
+
+ On Windows, you can't remove an open file.
+ */
+ apr_pool_cleanup_register(result_pool, baton,
+ temp_file_plain_cleanup_handler,
+ temp_file_child_cleanup_handler);
+
+ break;
+ case svn_io_file_del_on_close:
+ flags |= APR_DELONCLOSE;
+ break;
+ default:
+ break;
+ }
+
+ SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags,
+ result_pool, scratch_pool));
+
+#if !defined(WIN32) && !defined(__OS2__)
+ /* apr_file_mktemp() creates files with mode 0600.
+ * This is appropriate if we're using a system temp dir since we don't
+ * want to leak sensitive data into temp files other users can read.
+ * If we're not using a system temp dir we're probably using the
+ * .svn/tmp area and it's likely that the tempfile will end up being
+ * copied or renamed into the working copy.
+ * This would cause working files having mode 0600 while users might
+ * expect to see 0644 or 0664. So we tweak perms of the tempfile in this
+ * case, but only if the umask allows it. */
+ if (!using_system_temp_dir)
+ {
+ SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool));
+ SVN_ERR(file_perms_set2(tempfile, perms, scratch_pool));
+ }
+#endif
+
+ if (file)
+ *file = tempfile;
+ else
+ SVN_ERR(svn_io_file_close(tempfile, scratch_pool));
+
+ if (unique_path)
+ *unique_path = tempname; /* Was allocated in result_pool */
+
+ if (baton)
+ SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_file_readline(apr_file_t *file,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_size_t max_len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *str;
+ const char *eol_str;
+ apr_size_t numbytes;
+ char c;
+ apr_size_t len;
+ svn_boolean_t found_eof;
+
+ str = svn_stringbuf_create_ensure(80, result_pool);
+
+ /* Read bytes into STR up to and including, but not storing,
+ * the next EOL sequence. */
+ eol_str = NULL;
+ numbytes = 1;
+ len = 0;
+ found_eof = FALSE;
+ while (!found_eof)
+ {
+ if (len < max_len)
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ len++;
+ if (numbytes != 1 || len > max_len)
+ {
+ found_eof = TRUE;
+ break;
+ }
+
+ if (c == '\n')
+ {
+ eol_str = "\n";
+ }
+ else if (c == '\r')
+ {
+ eol_str = "\r";
+
+ if (!found_eof && len < max_len)
+ {
+ apr_off_t pos;
+
+ /* Check for "\r\n" by peeking at the next byte. */
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ if (numbytes == 1 && c == '\n')
+ {
+ eol_str = "\r\n";
+ len++;
+ }
+ else
+ {
+ /* Pretend we never peeked. */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
+ found_eof = FALSE;
+ numbytes = 1;
+ }
+ }
+ }
+ else
+ svn_stringbuf_appendbyte(str, c);
+
+ if (eol_str)
+ break;
+ }
+
+ if (eol)
+ *eol = eol_str;
+ if (eof)
+ *eof = found_eof;
+ *stringbuf = str;
+
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud