| /* |
| * 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> |
| |
| #if APR_HAVE_FCNTL_H |
| #include <fcntl.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" |
| #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" |
| |
| /* |
| 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 |
| |
| #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); |
| 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) |
| { |
| #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; |
| } |
| |
| |
| 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 |
| if (apr_err == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED) || |
| apr_err == APR_FROM_OS_ERROR(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) |
| { |
| #if defined(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); |
| #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 " |
| "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_rename2(dst_tmp, dst, FALSE, 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_rename2(dst_tmp, dst, FALSE, 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 |
| { |
| return svn_error_quick_wrapf( |
| err, _("Can't set permissions on '%s'"), |
| svn_dirent_local_style(dst, pool)); |
| } |
| } |
| } |
| #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); |
| #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'"), |
| svn_dirent_local_style(path, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| 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; |
| svn_error_t *err = SVN_NO_ERROR; |
| |
| SVN_ERR(svn_io_file_open(&f, file, |
| (APR_WRITE | APR_CREATE | APR_EXCL), |
| APR_OS_DEFAULT, |
| 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)); |
| |
| 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_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_error_trace( |
| 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 some sub-second resolution. On Windows |
| it is likely to be sub-millisecond; on Linux systems it depends |
| on the filesystem, ext4 is typically 1ms, 4ms or 10ms resolution. |
| |
| ## Perhaps find a better algorithm here. This will fail once |
| in every 1000 cases on a millisecond precision filesystem |
| if the mtime happens to be an exact second. |
| |
| But better to fail once in every thousand cases than every |
| time, like we did before. |
| |
| Note for further research on algorithm: |
| FAT32 has < 1 sec precision on ctime, but 2 sec on mtime. |
| |
| Linux/ext4 with CONFIG_HZ=250 has high resolution |
| apr_time_now and although the filesystem timestamps |
| have similar high precision they are only updated with |
| a coarser 4ms resolution. */ |
| |
| /* 10 milliseconds after now. */ |
| #ifndef SVN_HI_RES_SLEEP_MS |
| #define SVN_HI_RES_SLEEP_MS 10 |
| #endif |
| then = now + apr_time_from_msec(SVN_HI_RES_SLEEP_MS); |
| } |
| |
| /* Remove time taken to do stat() from sleep. */ |
| now = apr_time_now(); |
| } |
| |
| if (now >= then) |
| return; /* Passing negative values may suspend indefinitely (Windows) */ |
| |
| /* (t < 1000 will be round to 0 in apr) */ |
| if (then - now < 1000) |
| apr_sleep(1000); |
| else |
| 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_rename2(path, unique_name, FALSE, 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 in DIRECTORY. |
| DIRECTORY can be NULL in which case the system temporary dir is used. |
| 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, |
| const char *directory, |
| 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. |
| |
| 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. |
| |
| Using svn_io_open_uniquely_named() here because other tempfile |
| creation functions tweak the permission bits of files they create. |
| |
| Note that APR pool structures are allocated as the first item |
| in their first memory page (with e.g. 4kB granularity), i.e. the |
| lower bits tend to be identical between pool instances. That is |
| particularly true for the MMAPed allocator. |
| */ |
| randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool |
| + (apr_uint32_t)((apr_uintptr_t)scratch_pool / 4096) |
| + (apr_uint32_t)apr_time_now()); |
| fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish); |
| |
| SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, directory, 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(). DIRECTORY is used |
| to create temporary files, DIRECTORY can be NULL. Do temporary |
| allocations in SCRATCH_POOL. */ |
| static svn_error_t * |
| merge_default_file_perms(apr_file_t *fd, |
| apr_fileperms_t *perms, |
| const char *directory, |
| apr_pool_t *scratch_pool) |
| { |
| apr_finfo_t finfo; |
| apr_fileperms_t default_perms; |
| |
| SVN_ERR(get_default_file_perms(&default_perms, directory, 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) |
| || SVN__APR_STATUS_IS_ENOTDIR(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. */ |
| { |
| /* Tweak the owner bits only. The group/other bits aren't safe to |
| * touch because we may end up setting them in undesired ways. */ |
| perms_to_set |= (APR_UREAD|APR_UWRITE); |
| } |
| 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 |
| /* 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) |
| { |
| /* 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. |
| */ |
| 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 ((source[0] == '/' || source[0] == '\\') |
| && (source[1] == '/' || source[1] == '\\') |
| && source[2] != '?') |
| { |
| /* This is a UNC path */ |
| source += 2; /* Skip the leading slashes */ |
| prefix = L"\\\\?\\UNC\\"; |
| } |
| } |
| |
| 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 = '\\'; |
| } |
| 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 = '/'; |
| } |
| } |
| |
| *result = buffer; |
| for (; *buffer; ++buffer) |
| { |
| if (*buffer == '\\') |
| *buffer = '/'; |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| 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; |
| 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 (!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) |
| { |
| get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE) |
| GetProcAddress(kernel32, "GetFinalPathNameByHandleW"); |
| |
| set_file_information_by_handle_proc = (SetFileInformationByHandle_t) |
| GetProcAddress(kernel32, "SetFileInformationByHandle"); |
| } |
| |
| 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) |
| { |
| return SVN_ERR_UNSUPPORTED_FEATURE; |
| } |
| |
| if (!set_file_information_by_handle_proc(hFile, FileInformationClass, |
| lpFileInformation, |
| dwBufferSize)) |
| { |
| return apr_get_os_error(); |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| 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) |
| { |
| return svn_error_wrap_apr(status, _("Can't remove file '%s'"), |
| svn_dirent_local_style(path, pool)); |
| } |
| |
| 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)) |
| { |
| status = SVN_ERR_UNSUPPORTED_FEATURE; |
| } |
| |
| 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; |
| } |
| |
| #endif /* WIN32 */ |
| |
| 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) |
| || 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)); |
| |
| 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. ***/ |
| /* 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; |
| } |
| |
| 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, a process may not release file locks before closing the |
| handle, and in this case the outstanding locks are unlocked by the OS. |
| However, this is not recommended, because the actual unlocking may be |
| postponed depending on available system resources. We explicitly unlock |
| the file as a part of the pool cleanup handler. */ |
| apr_pool_cleanup_register(pool, lockfile_handle, |
| file_clear_locks, |
| apr_pool_cleanup_null); |
| |
| 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)); |
| |
| apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks); |
| |
| 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); |
| } |
| |
| 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. */ |
| |
| svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, |
| apr_pool_t *pool) |
| { |
| apr_os_file_t filehand; |
| 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, file); |
| if (apr_err) |
| return svn_error_wrap_apr(apr_err, _("Can't get file name")); |
| |
| /* ### 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(svn_io_file_flush(file, 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 '%s' to disk"), |
| try_utf8_from_internal_style(fname, pool)); |
| |
| #else |
| 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 |
| 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 '%s' to disk"), |
| try_utf8_from_internal_style(fname, pool)); |
| |
| #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; |
| |
| /* 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_finfo_t finfo = { 0 }; |
| |
| /* 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) |
| { |
| /* 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 |
| * 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 svn_error_trace(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); |
| |
| #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; |
| } |
| |
| /* 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_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_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)); |
| } |
| } |
| |
| |
| 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__APR_STATUS_IS_ENOTDIR(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 = 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); |
| 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; |
| |
| /* 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) |
| 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, |
| SVN_VA_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 (SVN__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_size_get(svn_filesize_t *filesize_p, apr_file_t *file, |
| apr_pool_t *pool) |
| { |
| apr_finfo_t finfo; |
| SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, file, pool)); |
| |
| *filesize_p = finfo.size; |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_io_file_get_offset(apr_off_t *offset_p, |
| apr_file_t *file, |
| apr_pool_t *pool) |
| { |
| apr_off_t offset; |
| |
| /* Note that, for buffered files, one (possibly surprising) side-effect |
| of this call is to flush any unwritten data to disk. */ |
| offset = 0; |
| SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); |
| *offset_p = offset; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| 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_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, ¤t, 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, |
| 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_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, |
| 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) |
| { |
| /* 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_atomic2(const char *final_path, |
| const void *buf, |
| apr_size_t nbytes, |
| const char *copy_perms_path, |
| svn_boolean_t flush_to_disk, |
| 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 && flush_to_disk) |
| 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_rename2(tmp_path, final_path, flush_to_disk, |
| 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)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| 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; |
| |
| if (to_read == 0) |
| break; |
| |
| /* 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; |
| } |
| |
| #if defined(WIN32) |
| /* Platform specific implementation of apr_file_rename() to workaround |
| APR problems on Windows. */ |
| static apr_status_t |
| win32_file_rename(const WCHAR *from_path_w, |
| const WCHAR *to_path_w, |
| svn_boolean_t flush_to_disk) |
| { |
| /* APR calls MoveFileExW() with MOVEFILE_COPY_ALLOWED, while we rely |
| * that rename is atomic operation. Call MoveFileEx directly on Windows |
| * without MOVEFILE_COPY_ALLOWED flag to workaround it. |
| */ |
| |
| DWORD flags = MOVEFILE_REPLACE_EXISTING; |
| |
| if (flush_to_disk) |
| { |
| /* Do not return until the file has actually been moved on the disk. */ |
| flags |= MOVEFILE_WRITE_THROUGH; |
| } |
| |
| if (!MoveFileExW(from_path_w, to_path_w, flags)) |
| return apr_get_os_error(); |
| |
| return APR_SUCCESS; |
| } |
| #endif |
| |
| svn_error_t * |
| svn_io_file_rename2(const char *from_path, const char *to_path, |
| svn_boolean_t flush_to_disk, apr_pool_t *pool) |
| { |
| apr_status_t status = APR_SUCCESS; |
| const char *from_path_apr, *to_path_apr; |
| #if defined(WIN32) |
| WCHAR *from_path_w; |
| WCHAR *to_path_w; |
| #endif |
| |
| SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool)); |
| SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool)); |
| |
| #if defined(WIN32) |
| SVN_ERR(svn_io__utf8_to_unicode_longpath(&from_path_w, from_path_apr, pool)); |
| SVN_ERR(svn_io__utf8_to_unicode_longpath(&to_path_w, to_path_apr, pool)); |
| status = win32_file_rename(from_path_w, to_path_w, flush_to_disk); |
| |
| /* 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 = win32_file_rename(from_path_w, to_path_w, flush_to_disk); |
| } |
| WIN32_RETRY_LOOP(status, win32_file_rename(from_path_w, to_path_w, |
| flush_to_disk)); |
| #elif defined(__OS2__) |
| status = apr_file_rename(from_path_apr, to_path_apr, pool); |
| /* 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 OS/2 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); |
| } |
| #else |
| 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)); |
| |
| #if defined(SVN_ON_POSIX) |
| if (flush_to_disk) |
| { |
| /* On POSIX, the file name is stored in the file's directory entry. |
| Hence, we need to fsync() that directory as well. |
| On other operating systems, we'd only be asking for trouble |
| by trying to open and fsync a directory. */ |
| const char *dirname; |
| apr_file_t *file; |
| |
| dirname = svn_dirent_dirname(to_path, pool); |
| SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT, |
| pool)); |
| SVN_ERR(svn_io_file_flush_to_disk(file, pool)); |
| SVN_ERR(svn_io_file_close(file, pool)); |
| } |
| #elif !defined(WIN32) |
| /* Flush the target of the rename to disk. */ |
| if (flush_to_disk) |
| { |
| apr_file_t *file; |
| SVN_ERR(svn_io_file_open(&file, to_path, APR_WRITE, |
| APR_OS_DEFAULT, pool)); |
| SVN_ERR(svn_io_file_flush_to_disk(file, pool)); |
| SVN_ERR(svn_io_file_close(file, pool)); |
| } |
| #endif |
| |
| 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_error_trace(svn_io_file_rename2(from_path, to_path, |
| FALSE, pool)); |
| |
| if (err && APR_STATUS_IS_EXDEV(err->apr_err)) |
| { |
| svn_error_clear(err); |
| |
| /* svn_io_copy_file() performs atomic copy via temporary file. */ |
| err = svn_error_trace(svn_io_copy_file(from_path, to_path, TRUE, |
| 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); |
| |
| #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'"), |
| 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); |
| 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 /* 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 |
| 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 (status == APR_FROM_OS_ERROR(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_rename2(path_tmp, path, FALSE, 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; |
| 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_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))) |
| { |
| 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 |
| * 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 than |
| 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; |
| |
| if (apr_err == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED) || |
| apr_err == APR_FROM_OS_ERROR(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_error_t *err; |
| |
| SVN_ERR(merge_default_file_perms(tempfile, &perms, dirpath, |
| scratch_pool)); |
| err = file_perms_set2(tempfile, perms, scratch_pool); |
| if (err) |
| { |
| if (APR_STATUS_IS_INCOMPLETE(err->apr_err) || |
| APR_STATUS_IS_ENOTIMPL(err->apr_err)) |
| svn_error_clear(err); |
| else |
| { |
| return svn_error_quick_wrapf( |
| err, _("Can't set permissions on '%s'"), |
| svn_dirent_local_style(tempname, 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. */ |
| SVN_ERR(svn_io_file_get_offset(&pos, file, 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; |
| } |