summaryrefslogtreecommitdiff
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.c942
1 files changed, 758 insertions, 184 deletions
diff --git a/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c
index e27411e..468dd17 100644
--- a/subversion/libsvn_subr/io.c
+++ b/subversion/libsvn_subr/io.c
@@ -47,8 +47,8 @@
#include <apr_portable.h>
#include <apr_md5.h>
-#ifdef WIN32
-#include <arch/win32/apr_arch_file_io.h>
+#if APR_HAVE_FCNTL_H
+#include <fcntl.h>
#endif
#include "svn_hash.h"
@@ -66,6 +66,8 @@
#include "private/svn_atomic.h"
#include "private/svn_io_private.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_dep_compat.h"
#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
@@ -139,6 +141,49 @@
#endif
#endif
+#ifdef WIN32
+
+#if _WIN32_WINNT < 0x600 /* Does the SDK assume Windows Vista+? */
+typedef struct _FILE_RENAME_INFO {
+ BOOL ReplaceIfExists;
+ HANDLE RootDirectory;
+ DWORD FileNameLength;
+ WCHAR FileName[1];
+} FILE_RENAME_INFO, *PFILE_RENAME_INFO;
+
+typedef struct _FILE_DISPOSITION_INFO {
+ BOOL DeleteFile;
+} FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;
+
+#define FileRenameInfo 3
+#define FileDispositionInfo 4
+#endif /* WIN32 < Vista */
+
+/* One-time initialization of the late bound Windows API functions. */
+static volatile svn_atomic_t win_dynamic_imports_state = 0;
+
+/* Pointer to GetFinalPathNameByHandleW function from kernel32.dll. */
+typedef DWORD (WINAPI *GETFINALPATHNAMEBYHANDLE)(
+ HANDLE hFile,
+ WCHAR *lpszFilePath,
+ DWORD cchFilePath,
+ DWORD dwFlags);
+
+typedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile,
+ int FileInformationClass,
+ LPVOID lpFileInformation,
+ DWORD dwBufferSize);
+
+static GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL;
+static SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL;
+
+/* Forward declaration. */
+static svn_error_t * io_win_read_link(svn_string_t **dest,
+ const char *path,
+ apr_pool_t *pool);
+
+#endif
+
/* Forward declaration */
static apr_status_t
dir_is_empty(const char *dir, apr_pool_t *pool);
@@ -330,6 +375,25 @@ file_open(apr_file_t **f,
if (retry_on_failure)
{
+#ifdef WIN32
+ if (status == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED))
+ {
+ if ((flag & (APR_CREATE | APR_EXCL)) == (APR_CREATE | APR_EXCL))
+ return status; /* Can't create if there is something */
+
+ if (flag & (APR_WRITE | APR_CREATE))
+ {
+ apr_finfo_t finfo;
+
+ if (!apr_stat(&finfo, fname_apr, SVN__APR_FINFO_READONLY, pool))
+ {
+ if (finfo.protection & APR_FREADONLY)
+ return status; /* Retrying won't fix this */
+ }
+ }
+ }
+#endif
+
WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool));
}
return status;
@@ -660,7 +724,7 @@ svn_io_read_link(svn_string_t **dest,
const char *path,
apr_pool_t *pool)
{
-#ifdef HAVE_READLINK
+#if defined(HAVE_READLINK)
svn_string_t dest_apr;
const char *path_apr;
char buf[1025];
@@ -681,6 +745,8 @@ svn_io_read_link(svn_string_t **dest,
/* ### Cast needed, one of these interfaces is wrong */
return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
+#elif defined(WIN32)
+ return io_win_read_link(dest, path, pool);
#else
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Symbolic links are not supported on this "
@@ -959,10 +1025,9 @@ svn_io_copy_perms(const char *src,
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);
+ return svn_error_quick_wrapf(
+ err, _("Can't set permissions on '%s'"),
+ svn_dirent_local_style(dst, pool));
}
}
}
@@ -1130,8 +1195,13 @@ svn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
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));
+#ifdef WIN32
+ /* Don't retry on ERROR_ACCESS_DENIED, as that typically signals a
+ permanent error */
+ if (apr_err == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION))
+ WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
+ APR_OS_DEFAULT, pool));
+#endif
if (apr_err)
return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
@@ -1140,9 +1210,11 @@ svn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
return SVN_NO_ERROR;
}
-svn_error_t *svn_io_file_create(const char *file,
- const char *contents,
- apr_pool_t *pool)
+svn_error_t *
+svn_io_file_create_bytes(const char *file,
+ const void *contents,
+ apr_size_t length,
+ apr_pool_t *scratch_pool)
{
apr_file_t *f;
apr_size_t written;
@@ -1151,26 +1223,59 @@ svn_error_t *svn_io_file_create(const char *file,
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);
+ scratch_pool));
+ if (length)
+ err = svn_io_file_write_full(f, contents, length, &written,
+ scratch_pool);
+ err = svn_error_compose_create(
+ err,
+ svn_io_file_close(f, scratch_pool));
- return svn_error_trace(
- svn_error_compose_create(err,
- svn_io_file_close(f, pool)));
+ if (err)
+ {
+ /* Our caller doesn't know if we left a file or not if we return
+ an error. Better to cleanup after ourselves if we created the
+ file. */
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_io_remove_file2(file, TRUE, scratch_pool)));
+ }
+
+ return SVN_NO_ERROR;
}
-svn_error_t *svn_io_dir_file_copy(const char *src_path,
- const char *dest_path,
- const char *file,
- apr_pool_t *pool)
+svn_error_t *
+svn_io_file_create(const char *file,
+ const char *contents,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_io_file_create_bytes(file, contents,
+ contents ? strlen(contents)
+ : 0,
+ pool));
+}
+
+svn_error_t *
+svn_io_file_create_empty(const char *file,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_io_file_create_bytes(file, NULL, 0,
+ scratch_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);
+ return svn_error_trace(
+ svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool));
}
@@ -1456,7 +1561,7 @@ get_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool)
/* 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
+ Explicitly 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.
@@ -1528,7 +1633,8 @@ io_set_file_perms(const char *path,
status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
if (status)
{
- if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
+ if (ignore_enoent && (APR_STATUS_IS_ENOENT(status)
+ || SVN__APR_STATUS_IS_ENOTDIR(status)))
return SVN_NO_ERROR;
else if (status != APR_ENOTIMPL)
return svn_error_wrap_apr(status,
@@ -1640,27 +1746,13 @@ io_set_file_perms(const char *path,
#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)
+/* This is semantically the same as the APR utf8_to_unicode_path
+ function, but reimplemented here because APR does not export it. */
+svn_error_t*
+svn_io__utf8_to_unicode_longpath(const WCHAR **result,
+ const char *source,
+ apr_pool_t *result_pool)
{
- /* 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
@@ -1675,95 +1767,331 @@ static apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retl
* 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;
+ const WCHAR *prefix = NULL;
+ const int srclen = strlen(source);
+ WCHAR *buffer;
+
+ if (srclen > 248)
+ {
+ if (svn_ctype_isalpha(source[0]) && source[1] == ':'
+ && (source[2] == '/' || source[2] == '\\'))
+ {
+ /* This is an ordinary absolute path. */
+ prefix = L"\\\\?\\";
}
- 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;
+ else if ((source[0] == '/' || source[0] == '\\')
+ && (source[1] == '/' || source[1] == '\\')
+ && source[2] != '?')
+ {
+ /* This is a UNC path */
+ source += 2; /* Skip the leading slashes */
+ prefix = L"\\\\?\\UNC\\";
}
}
- if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) {
- return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv;
+ SVN_ERR(svn_utf__win32_utf8_to_utf16(&(const WCHAR*)buffer, source,
+ prefix, result_pool));
+
+ /* Convert slashes to backslashes because the \\?\ path format
+ does not allow backslashes as path separators. */
+ *result = buffer;
+ for (; *buffer; ++buffer)
+ {
+ if (*buffer == '/')
+ *buffer = '\\';
}
- if (srcremains) {
- return APR_ENAMETOOLONG;
+ return SVN_NO_ERROR;
+}
+
+/* This is semantically the same as the APR unicode_to_utf8_path
+ function, but reimplemented here because APR does not export it. */
+static svn_error_t *
+io_unicode_to_utf8_path(const char **result,
+ const WCHAR *source,
+ apr_pool_t *result_pool)
+{
+ const char *utf8_buffer;
+ char *buffer;
+
+ SVN_ERR(svn_utf__win32_utf16_to_utf8(&utf8_buffer, source,
+ NULL, result_pool));
+ if (!*utf8_buffer)
+ {
+ *result = utf8_buffer;
+ return SVN_NO_ERROR;
+ }
+
+ /* We know that the non-empty buffer returned from the UTF-16 to
+ UTF-8 conversion function is in fact writable. */
+ buffer = (char*)utf8_buffer;
+
+ /* Skip the leading 4 characters if the path begins \\?\, or substitute
+ * // for the \\?\UNC\ path prefix, allocating the maximum string
+ * length based on the remaining string, plus the trailing null.
+ * then transform \\'s back into /'s since the \\?\ form never
+ * allows '/' path separators, and APR always uses '/'s.
+ */
+ if (0 == strncmp(buffer, "\\\\?\\", 4))
+ {
+ buffer += 4;
+ if (0 == strncmp(buffer, "UNC\\", 4))
+ {
+ buffer += 2;
+ *buffer = '/';
+ }
}
- for (; *t; ++t)
- if (*t == L'/')
- *t = L'\\';
- return APR_SUCCESS;
+
+ *result = buffer;
+ for (; *buffer; ++buffer)
+ {
+ if (*buffer == '\\')
+ *buffer = '/';
+ }
+ return SVN_NO_ERROR;
}
-#endif
-static apr_status_t io_win_file_attrs_set(const char *fname,
- DWORD attributes,
- DWORD attr_mask,
- apr_pool_t *pool)
+static svn_error_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
+ const WCHAR *wfname;
+
+ SVN_ERR(svn_io__utf8_to_unicode_longpath(&wfname, fname, pool));
+
+ flags = GetFileAttributesW(wfname);
+ if (flags == 0xFFFFFFFF)
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't get attributes of file '%s'"),
+ svn_dirent_local_style(fname, pool));
+
+ flags &= ~attr_mask;
+ flags |= (attributes & attr_mask);
-#if APR_HAS_UNICODE_FS
- IF_WIN_OS_IS_UNICODE
+ if (!SetFileAttributesW(wfname, flags))
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't set attributes of file '%s'"),
+ svn_dirent_local_style(fname, pool));
+
+ return SVN_NO_ERROR;;
+}
+
+static svn_error_t *win_init_dynamic_imports(void *baton, apr_pool_t *pool)
+{
+ HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
+
+ if (kernel32)
{
- if (rv = io_utf8_to_unicode_path(wfname,
- sizeof(wfname) / sizeof(wfname[0]),
- fname))
- return rv;
- flags = GetFileAttributesW(wfname);
+ get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE)
+ GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
+
+ set_file_information_by_handle_proc = (SetFileInformationByHandle_t)
+ GetProcAddress(kernel32, "SetFileInformationByHandle");
}
-#endif
-#if APR_HAS_ANSI_FS
- ELSE_WIN_OS_IS_ANSI
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t * io_win_read_link(svn_string_t **dest,
+ const char *path,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_atomic__init_once(&win_dynamic_imports_state,
+ win_init_dynamic_imports, NULL, pool));
+
+ if (get_final_path_name_by_handle_proc)
+ {
+ DWORD rv;
+ apr_status_t status;
+ apr_file_t *file;
+ apr_os_file_t filehand;
+ WCHAR wdest[APR_PATH_MAX];
+ const char *data;
+
+ /* reserve one char for terminating zero. */
+ DWORD wdest_len = sizeof(wdest)/sizeof(wdest[0]) - 1;
+
+ status = apr_file_open(&file, path, APR_OPENINFO, APR_OS_DEFAULT, pool);
+
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Can't read contents of link"));
+
+ apr_os_file_get(&filehand, file);
+
+ rv = get_final_path_name_by_handle_proc(
+ filehand, wdest, wdest_len,
+ FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+
+ /* Save error code. */
+ status = apr_get_os_error();
+
+ /* Close file/directory handle in any case. */
+ apr_file_close(file);
+
+ /* GetFinaPathNameByHandleW returns number of characters copied to
+ * output buffer. Returns zero on error. Returns required buffer size
+ * if supplied buffer is not enough. */
+ if (rv > wdest_len || rv == 0)
+ {
+ return svn_error_wrap_apr(status,
+ _("Can't read contents of link"));
+ }
+
+ /* GetFinaPathNameByHandleW doesn't add terminating NUL. */
+ wdest[rv] = 0;
+ SVN_ERR(io_unicode_to_utf8_path(&data, wdest, pool));
+
+ /* The result is already in the correct pool, so avoid copying
+ it to create the string. */
+ *dest = svn_string_create_empty(pool);
+ if (*data)
+ {
+ (*dest)->data = data;
+ (*dest)->len = strlen(data);
+ }
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Symbolic links are not supported on this "
+ "platform"));
+ }
+}
+
+/* Wrapper around Windows API function SetFileInformationByHandle() that
+ * returns APR status instead of boolean flag. */
+static apr_status_t
+win32_set_file_information_by_handle(HANDLE hFile,
+ int FileInformationClass,
+ LPVOID lpFileInformation,
+ DWORD dwBufferSize)
+{
+ svn_error_clear(svn_atomic__init_once(&win_dynamic_imports_state,
+ win_init_dynamic_imports,
+ NULL, NULL));
+
+ if (!set_file_information_by_handle_proc)
{
- flags = GetFileAttributesA(fname);
+ return SVN_ERR_UNSUPPORTED_FEATURE;
}
-#endif
- if (flags == 0xFFFFFFFF)
- return apr_get_os_error();
+ if (!set_file_information_by_handle_proc(hFile, FileInformationClass,
+ lpFileInformation,
+ dwBufferSize))
+ {
+ return apr_get_os_error();
+ }
- flags &= ~attr_mask;
- flags |= (attributes & attr_mask);
+ return APR_SUCCESS;
+}
-#if APR_HAS_UNICODE_FS
- IF_WIN_OS_IS_UNICODE
+svn_error_t *
+svn_io__win_delete_file_on_close(apr_file_t *file,
+ const char *path,
+ apr_pool_t *pool)
+{
+ FILE_DISPOSITION_INFO disposition_info;
+ HANDLE hFile;
+ apr_status_t status;
+
+ apr_os_file_get(&hFile, file);
+
+ disposition_info.DeleteFile = TRUE;
+
+ status = win32_set_file_information_by_handle(hFile, FileDispositionInfo,
+ &disposition_info,
+ sizeof(disposition_info));
+
+ if (status)
{
- rv = SetFileAttributesW(wfname, flags);
+ return svn_error_wrap_apr(status, _("Can't remove file '%s'"),
+ svn_dirent_local_style(path, pool));
}
-#endif
-#if APR_HAS_ANSI_FS
- ELSE_WIN_OS_IS_ANSI
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io__win_rename_open_file(apr_file_t *file,
+ const char *from_path,
+ const char *to_path,
+ apr_pool_t *pool)
+{
+ WCHAR *w_final_abspath;
+ size_t path_len;
+ size_t rename_size;
+ FILE_RENAME_INFO *rename_info;
+ HANDLE hFile;
+ apr_status_t status;
+
+ apr_os_file_get(&hFile, file);
+
+ SVN_ERR(svn_io__utf8_to_unicode_longpath(
+ &w_final_abspath, svn_dirent_local_style(to_path,pool),
+ pool));
+
+ path_len = wcslen(w_final_abspath);
+ rename_size = sizeof(*rename_info) + sizeof(WCHAR) * path_len;
+
+ /* The rename info struct doesn't need hacks for long paths,
+ so no ugly escaping calls here */
+ rename_info = apr_pcalloc(pool, rename_size);
+ rename_info->ReplaceIfExists = TRUE;
+ rename_info->FileNameLength = path_len;
+ memcpy(rename_info->FileName, w_final_abspath, path_len * sizeof(WCHAR));
+
+ status = win32_set_file_information_by_handle(hFile, FileRenameInfo,
+ rename_info,
+ rename_size);
+
+ 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 final_abspath is read-only. */
+ SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
+
+ status = win32_set_file_information_by_handle(hFile,
+ FileRenameInfo,
+ rename_info,
+ rename_size);
+ }
+
+ /* Windows returns Vista+ client accessing network share stored on Windows
+ Server 2003 returns ERROR_ACCESS_DENIED. The same happens when Vista+
+ client access Windows Server 2008 with disabled SMBv2 protocol.
+
+ So return SVN_ERR_UNSUPPORTED_FEATURE in this case like we do when
+ SetFileInformationByHandle() is not available and let caller to
+ handle it.
+
+ See "Access denied error on checkout-commit after updating to 1.9.X"
+ discussion on dev@s.a.o:
+ http://svn.haxx.se/dev/archive-2015-09/0054.shtml */
+ if (status == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED))
{
- rv = SetFileAttributesA(fname, flags);
+ status = SVN_ERR_UNSUPPORTED_FEATURE;
}
-#endif
- if (rv == 0)
- return apr_get_os_error();
+ 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 APR_SUCCESS;
+ return SVN_NO_ERROR;
}
-#endif
+#endif /* WIN32 */
svn_error_t *
svn_io_set_file_read_write_carefully(const char *path,
@@ -1798,7 +2126,8 @@ svn_io_set_file_read_only(const char *path,
pool);
if (status && status != APR_ENOTIMPL)
- if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
+ if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status)
+ || SVN__APR_STATUS_IS_ENOTDIR(status))))
return svn_error_wrap_apr(status,
_("Can't set file '%s' read-only"),
svn_dirent_local_style(path, pool));
@@ -2089,6 +2418,35 @@ svn_io_file_lock2(const char *lock_file,
return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool);
}
+svn_error_t *
+svn_io__file_lock_autocreate(const char *lock_file,
+ apr_pool_t *pool)
+{
+ svn_error_t *err
+ = svn_io_file_lock2(lock_file, TRUE, FALSE, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* No lock file? No big deal; these are just empty files anyway.
+ Create it and try again. */
+ svn_error_clear(err);
+
+ /* This file creation is racy.
+ We don't care as long as file gets created at all. */
+ err = svn_io_file_create_empty(lock_file, pool);
+ if (err && APR_STATUS_IS_EEXIST(err->apr_err))
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+
+ /* Finally, lock the file - if it exists */
+ if (!err)
+ err = svn_io_file_lock2(lock_file, TRUE, FALSE, pool);
+ }
+
+ return svn_error_trace(err);
+}
+
/* Data consistency/coherency operations. */
@@ -2098,11 +2456,12 @@ svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
{
apr_os_file_t filehand;
+ /* ### In apr 1.4+ we could delegate most of this function to
+ apr_file_sync(). The only major difference is that this doesn't
+ contain the retry loop for EINTR on linux. */
+
/* 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));
+ SVN_ERR(svn_io_file_flush(file, pool));
apr_os_file_get(&filehand, file);
@@ -2119,7 +2478,11 @@ svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
int rv;
do {
+#ifdef F_FULLFSYNC
+ rv = fcntl(filehand, F_FULLFSYNC, 0);
+#else
rv = fsync(filehand);
+#endif
} while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
/* If the file is in a memory filesystem, fsync() may return
@@ -2159,36 +2522,34 @@ stringbuf_from_aprfile(svn_stringbuf_t **result,
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);
+ char *buf;
/* 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;
+ apr_finfo_t finfo = { 0 };
- /* 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)
+ /* In some cases we get size 0 and no error for non files,
+ so we also check for the name. (= cached in apr_file_t) */
+ if (! apr_file_info_get(&finfo, APR_FINFO_SIZE, file) && finfo.fname)
{
- 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;
+ /* we've got the file length. Now, read it in one go. */
+ svn_boolean_t eof;
+ res_initial_len = (apr_size_t)finfo.size;
+ res = svn_stringbuf_create_ensure(res_initial_len, pool);
+ SVN_ERR(svn_io_file_read_full2(file, res->data,
+ res_initial_len, &res->len,
+ &eof, pool));
+ res->data[res->len] = 0;
+
+ *result = res;
+ return SVN_NO_ERROR;
}
}
-
/* XXX: We should check the incoming data for being of type binary. */
-
+ buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
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
@@ -2204,7 +2565,7 @@ stringbuf_from_aprfile(svn_stringbuf_t **result,
/* Having read all the data we *expect* EOF */
if (err && !APR_STATUS_IS_EOF(err->apr_err))
- return err;
+ return svn_error_trace(err);
svn_error_clear(err);
*result = res;
@@ -2269,11 +2630,6 @@ svn_io_remove_file2(const char *path,
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
@@ -2289,30 +2645,36 @@ svn_io_remove_file2(const char *path,
return SVN_NO_ERROR;
}
+ /* Check to make sure we aren't trying to delete a directory */
+ if (apr_err == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED)
+ || apr_err == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION))
{
- 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;
+ 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));
- }
+ 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));
+ /* Just return the delete error */
+#endif
- return SVN_NO_ERROR;
+ if (!apr_err)
+ {
+ return SVN_NO_ERROR;
+ }
+ else if (ignore_enoent && (APR_STATUS_IS_ENOENT(apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(apr_err)))
+ {
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+ }
}
@@ -2367,7 +2729,8 @@ svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
if (err)
{
/* if the directory doesn't exist, our mission is accomplished */
- if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err))
+ if (ignore_enoent && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
{
svn_error_clear(err);
return SVN_NO_ERROR;
@@ -2377,8 +2740,8 @@ svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
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 *name = apr_hash_this_key(hi);
+ const svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
const char *fullpath;
fullpath = svn_dirent_join(path, name, subpool);
@@ -2687,6 +3050,10 @@ svn_io_start_cmd3(apr_proc_t *cmd_proc,
{
const char *path_apr;
+ /* APR doesn't like our canonical path format for current directory */
+ if (path[0] == '\0')
+ path = ".";
+
SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
if (apr_err)
@@ -3025,7 +3392,8 @@ svn_io_run_diff3_3(int *exitcode,
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);
+ args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8,
+ SVN_VA_NULL);
#ifndef NDEBUG
++nargs;
#endif
@@ -3416,6 +3784,100 @@ svn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
pool);
}
+svn_error_t *
+svn_io_file_aligned_seek(apr_file_t *file,
+ apr_off_t block_size,
+ apr_off_t *buffer_start,
+ apr_off_t offset,
+ apr_pool_t *scratch_pool)
+{
+ const apr_size_t apr_default_buffer_size = 4096;
+ apr_size_t file_buffer_size = apr_default_buffer_size;
+ apr_off_t desired_offset = 0;
+ apr_off_t current = 0;
+ apr_off_t aligned_offset = 0;
+ svn_boolean_t fill_buffer = FALSE;
+
+ /* paranoia check: huge blocks on 32 bit machines may cause overflows */
+ SVN_ERR_ASSERT(block_size == (apr_size_t)block_size);
+
+ /* default for invalid block sizes */
+ if (block_size == 0)
+ block_size = apr_default_buffer_size;
+
+ file_buffer_size = apr_file_buffer_size_get(file);
+
+ /* don't try to set a buffer size for non-buffered files! */
+ if (file_buffer_size == 0)
+ {
+ aligned_offset = offset;
+ }
+ else if (file_buffer_size != (apr_size_t)block_size)
+ {
+ /* FILE has the wrong buffer size. correct it */
+ char *buffer;
+ file_buffer_size = (apr_size_t)block_size;
+ buffer = apr_palloc(apr_file_pool_get(file), file_buffer_size);
+ apr_file_buffer_set(file, buffer, file_buffer_size);
+
+ /* seek to the start of the block and cause APR to read 1 block */
+ aligned_offset = offset - (offset % block_size);
+ fill_buffer = TRUE;
+ }
+ else
+ {
+ aligned_offset = offset - (offset % file_buffer_size);
+
+ /* We have no way to determine the block start of an APR file.
+ Furthermore, we don't want to throw away the current buffer
+ contents. Thus, we re-align the buffer only if the CURRENT
+ offset definitely lies outside the desired, aligned buffer.
+ This covers the typical case of linear reads getting very
+ close to OFFSET but reading the previous / following block.
+
+ Note that ALIGNED_OFFSET may still be within the current
+ buffer and no I/O will actually happen in the FILL_BUFFER
+ section below.
+ */
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &current, scratch_pool));
+ fill_buffer = aligned_offset + file_buffer_size <= current
+ || current <= aligned_offset;
+ }
+
+ if (fill_buffer)
+ {
+ char dummy;
+ apr_status_t status;
+
+ /* seek to the start of the block and cause APR to read 1 block */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &aligned_offset,
+ scratch_pool));
+ status = apr_file_getc(&dummy, file);
+
+ /* read may fail if we seek to or behind EOF. That's ok then. */
+ if (status != APR_SUCCESS && !APR_STATUS_IS_EOF(status))
+ return do_io_file_wrapper_cleanup(file, status,
+ N_("Can't read file '%s'"),
+ N_("Can't read stream"),
+ scratch_pool);
+ }
+
+ /* finally, seek to the OFFSET the caller wants */
+ desired_offset = offset;
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
+ if (desired_offset != offset)
+ return do_io_file_wrapper_cleanup(file, APR_EOF,
+ N_("Can't seek in file '%s'"),
+ N_("Can't seek in stream"),
+ scratch_pool);
+
+ /* return the buffer start that we (probably) enforced */
+ if (buffer_start)
+ *buffer_start = aligned_offset;
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_io_file_write(apr_file_t *file, const void *buf,
@@ -3428,6 +3890,16 @@ svn_io_file_write(apr_file_t *file, const void *buf,
pool));
}
+svn_error_t *
+svn_io_file_flush(apr_file_t *file,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(do_io_file_wrapper_cleanup(
+ file, apr_file_flush(file),
+ N_("Can't flush file '%s'"),
+ N_("Can't flush stream"),
+ scratch_pool));
+}
svn_error_t *
svn_io_file_write_full(apr_file_t *file, const void *buf,
@@ -3493,17 +3965,105 @@ svn_io_write_unique(const char **tmp_path,
err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool);
if (!err)
- err = svn_io_file_flush_to_disk(new_file, pool);
+ {
+ /* svn_io_file_flush_to_disk() can be very expensive, so use the
+ cheaper standard flush if the file is created as temporary file
+ anyway */
+ if (delete_when == svn_io_file_del_none)
+ err = svn_io_file_flush_to_disk(new_file, pool);
+ else
+ err = svn_io_file_flush(new_file, pool);
+ }
return svn_error_trace(
svn_error_compose_create(err,
svn_io_file_close(new_file, pool)));
}
+svn_error_t *
+svn_io_write_atomic(const char *final_path,
+ const void *buf,
+ apr_size_t nbytes,
+ const char *copy_perms_path,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *tmp_file;
+ const char *tmp_path;
+ svn_error_t *err;
+ const char *dirname = svn_dirent_dirname(final_path, scratch_pool);
+
+ SVN_ERR(svn_io_open_unique_file3(&tmp_file, &tmp_path, dirname,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ err = svn_io_file_write_full(tmp_file, buf, nbytes, NULL, scratch_pool);
+
+ if (!err)
+ err = svn_io_file_flush_to_disk(tmp_file, scratch_pool);
+
+ err = svn_error_compose_create(err,
+ svn_io_file_close(tmp_file, scratch_pool));
+
+ if (!err && copy_perms_path)
+ err = svn_io_copy_perms(copy_perms_path, tmp_path, scratch_pool);
+
+ if (!err)
+ err = svn_io_file_rename(tmp_path, final_path, scratch_pool);
+
+ if (err)
+ {
+ err = svn_error_compose_create(err,
+ svn_io_remove_file2(tmp_path, TRUE,
+ scratch_pool));
+
+ return svn_error_createf(err->apr_err, err,
+ _("Can't write '%s' atomically"),
+ svn_dirent_local_style(final_path,
+ scratch_pool));
+ }
+
+#ifdef __linux__
+ {
+ /* Linux has the unusual feature that fsync() on a file is not
+ enough to ensure that a file's directory entries have been
+ flushed to disk; you have to fsync the directory as well.
+ On other operating systems, we'd only be asking for trouble
+ by trying to open and fsync a directory. */
+ apr_file_t *file;
+
+ SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
+ scratch_pool));
+ SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
+ SVN_ERR(svn_io_file_close(file, scratch_pool));
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
{
+ /* Workaround for yet another APR issue with trunc.
+
+ If the APR file internally is in read mode, the current buffer pointer
+ will not be clipped to the valid data range. get_file_offset may then
+ return an invalid position *after* new data was written to it.
+
+ To prevent this, write 1 dummy byte just after the OFFSET at which we
+ will trunc it. That will force the APR file into write mode
+ internally and the flush() work-around below becomes affective. */
+ apr_off_t position = 0;
+
+ /* A frequent usage is OFFSET==0, in which case we don't need to preserve
+ any file content or file pointer. */
+ if (offset)
+ {
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &position, pool));
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
+ }
+ SVN_ERR(svn_io_file_putc(0, file, 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. */
@@ -3512,10 +4072,17 @@ svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
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_ERR(do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset),
+ N_("Can't truncate file '%s'"),
+ N_("Can't truncate stream"),
+ pool));
+
+ /* Restore original file pointer, if necessary.
+ It's currently at OFFSET. */
+ if (position < offset)
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &position, pool));
+
+ return SVN_NO_ERROR;
}
@@ -3726,7 +4293,13 @@ dir_make(const char *path, apr_fileperms_t perm,
#endif
status = apr_dir_make(path_apr, perm, pool);
- WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
+
+#ifdef WIN32
+ /* Don't retry on ERROR_ACCESS_DENIED, as that typically signals a
+ permanent error */
+ if (status == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION))
+ WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
+#endif
if (status)
return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
@@ -3740,22 +4313,26 @@ dir_make(const char *path, apr_fileperms_t perm,
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));
+#else
+ /* on Windows, use our wrapper so we can also set the
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */
+ svn_error_t *err =
+ io_win_file_attrs_set(path_apr,
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ pool);
+ if (err)
+ return svn_error_createf(err->apr_err, err,
+ _("Can't hide directory '%s'"),
+ svn_dirent_local_style(path, pool));
+#endif /* WIN32 */
}
-#endif
+#endif /* APR_FILE_ATTR_HIDDEN */
/* Windows does not implement sgid. Skip here because retrieving
the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented
@@ -4173,7 +4750,6 @@ svn_io_read_version_file(int *version,
}
-
/* Do a byte-for-byte comparison of FILE1 and FILE2. */
static svn_error_t *
contents_identical_p(svn_boolean_t *identical_p,
@@ -4248,7 +4824,6 @@ contents_three_identical_p(svn_boolean_t *identical_p12,
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);
@@ -4258,7 +4833,6 @@ contents_three_identical_p(svn_boolean_t *identical_p12,
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));
@@ -4292,6 +4866,9 @@ contents_three_identical_p(svn_boolean_t *identical_p12,
|| (*identical_p23 && !eof2 && !eof3)
|| (*identical_p13 && !eof1 && !eof3)))
{
+ apr_size_t bytes_read1, bytes_read2, bytes_read3;
+ svn_boolean_t read_1, read_2, read_3;
+
read_1 = read_2 = read_3 = FALSE;
/* As long as a file is not at the end yet, and it is still
@@ -4686,12 +5263,9 @@ svn_io_open_unique_file3(apr_file_t **file,
svn_error_clear(err);
else
{
- const char *message;
- message = apr_psprintf(scratch_pool,
- _("Can't set permissions on '%s'"),
- svn_dirent_local_style(tempname,
- scratch_pool));
- return svn_error_quick_wrap(err, message);
+ return svn_error_quick_wrapf(
+ err, _("Can't set permissions on '%s'"),
+ svn_dirent_local_style(tempname, scratch_pool));
}
}
}