diff options
Diffstat (limited to 'subversion/libsvn_subr/io.c')
-rw-r--r-- | subversion/libsvn_subr/io.c | 442 |
1 files changed, 368 insertions, 74 deletions
diff --git a/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c index a1bc29c09b7f2..b351c00751add 100644 --- a/subversion/libsvn_subr/io.c +++ b/subversion/libsvn_subr/io.c @@ -155,8 +155,14 @@ typedef struct _FILE_DISPOSITION_INFO { BOOL DeleteFile; } FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO; +typedef struct _FILE_ATTRIBUTE_TAG_INFO { + DWORD FileAttributes; + DWORD ReparseTag; +} FILE_ATTRIBUTE_TAG_INFO, *PFILE_ATTRIBUTE_TAG_INFO; + #define FileRenameInfo 3 #define FileDispositionInfo 4 +#define FileAttributeTagInfo 9 #endif /* WIN32 < Vista */ /* One-time initialization of the late bound Windows API functions. */ @@ -169,19 +175,30 @@ typedef DWORD (WINAPI *GETFINALPATHNAMEBYHANDLE)( DWORD cchFilePath, DWORD dwFlags); +typedef BOOL (WINAPI *GetFileInformationByHandleEx_t)(HANDLE hFile, + int FileInformationClass, + LPVOID lpFileInformation, + DWORD dwBufferSize); + typedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile, int FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize); static GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL; +static GetFileInformationByHandleEx_t get_file_information_by_handle_ex_proc = NULL; static SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL; -/* Forward declaration. */ +/* Forward declarations. */ static svn_error_t * io_win_read_link(svn_string_t **dest, const char *path, apr_pool_t *pool); +static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p, + svn_boolean_t *is_symlink_p, + const char *path, + apr_pool_t *pool); + #endif /* Forward declaration */ @@ -342,13 +359,7 @@ io_check_path(const char *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)); -#ifdef WIN32 - /* on Windows, svn does not handle reparse points or hard links. - So ignore the 'resolve_symlinks' flag. */ - flags = APR_FINFO_MIN; -#else flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK); -#endif apr_err = apr_stat(&finfo, path_apr, flags, pool); if (APR_STATUS_IS_ENOENT(apr_err)) @@ -410,8 +421,12 @@ svn_io_check_resolved_path(const char *path, svn_node_kind_t *kind, apr_pool_t *pool) { +#if WIN32 + return io_win_check_path(kind, NULL, path, pool); +#else svn_boolean_t ignored; return io_check_path(path, TRUE, &ignored, kind, pool); +#endif } svn_error_t * @@ -419,8 +434,19 @@ svn_io_check_path(const char *path, svn_node_kind_t *kind, apr_pool_t *pool) { +#if WIN32 + svn_boolean_t is_symlink; + + SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool)); + + if (is_symlink) + *kind = svn_node_file; + + return SVN_NO_ERROR; +#else svn_boolean_t ignored; return io_check_path(path, FALSE, &ignored, kind, pool); +#endif } svn_error_t * @@ -429,7 +455,23 @@ svn_io_check_special_path(const char *path, svn_boolean_t *is_special, apr_pool_t *pool) { +#ifdef WIN32 + svn_boolean_t is_symlink; + + SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool)); + + if (is_symlink) + { + *is_special = TRUE; + *kind = svn_node_file; + } + else + *is_special = FALSE; + + return SVN_NO_ERROR; +#else return io_check_path(path, FALSE, is_special, kind, pool); +#endif } struct temp_file_cleanup_s @@ -1532,7 +1574,7 @@ reown_file(const char *path, } /* Determine what the PERMS for a new file should be by looking at the - permissions of a temporary file that we create in DIRECTORY. + 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 @@ -1622,13 +1664,14 @@ merge_default_file_perms(apr_file_t *fd, 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) +io_set_perms(const char *path, + svn_boolean_t is_file, + 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; @@ -1648,9 +1691,16 @@ io_set_file_perms(const char *path, || 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)); + { + if (is_file) + return svn_error_wrap_apr(status, + _("Can't change perms of file '%s'"), + svn_dirent_local_style(path, pool)); + else + return svn_error_wrap_apr(status, + _("Can't change perms of directory '%s'"), + svn_dirent_local_style(path, pool)); + } return SVN_NO_ERROR; } @@ -1750,10 +1800,50 @@ io_set_file_perms(const char *path, 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)); + if (is_file) + { + return svn_error_wrap_apr(status, + _("Can't change perms of file '%s'"), + svn_dirent_local_style(path, pool)); + } + else + { + return svn_error_wrap_apr(status, + _("Can't change perms of directory '%s'"), + svn_dirent_local_style(path, pool)); + } +} + +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) +{ + return svn_error_trace(io_set_perms(path, TRUE, + change_readwrite, enable_write, + change_executable, executable, + ignore_enoent, pool)); +} + +static svn_error_t * +io_set_dir_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) +{ + return svn_error_trace(io_set_perms(path, FALSE, + change_readwrite, enable_write, + change_executable, executable, + ignore_enoent, pool)); } + #endif /* !WIN32 && !__OS2__ */ #ifdef WIN32 @@ -1902,6 +1992,9 @@ static svn_error_t *win_init_dynamic_imports(void *baton, apr_pool_t *pool) get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE) GetProcAddress(kernel32, "GetFinalPathNameByHandleW"); + get_file_information_by_handle_ex_proc = (GetFileInformationByHandleEx_t) + GetProcAddress(kernel32, "GetFileInformationByHandleEx"); + set_file_information_by_handle_proc = (SetFileInformationByHandle_t) GetProcAddress(kernel32, "SetFileInformationByHandle"); } @@ -1978,6 +2071,33 @@ static svn_error_t * io_win_read_link(svn_string_t **dest, } } +/* Wrapper around Windows API function GetFileInformationByHandleEx() that + * returns APR status instead of boolean flag. */ +static apr_status_t +win32_get_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 (!get_file_information_by_handle_ex_proc) + { + return SVN_ERR_UNSUPPORTED_FEATURE; + } + + if (!get_file_information_by_handle_ex_proc(hFile, FileInformationClass, + lpFileInformation, + dwBufferSize)) + { + return apr_get_os_error(); + } + + return APR_SUCCESS; +} + /* Wrapper around Windows API function SetFileInformationByHandle() that * returns APR status instead of boolean flag. */ static apr_status_t @@ -2005,6 +2125,105 @@ win32_set_file_information_by_handle(HANDLE hFile, return APR_SUCCESS; } +/* Fast Win32-specific helper for svn_io_check_path() and related functions + * that only requires a single GetFileAttributes() call in most cases. + */ +static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p, + svn_boolean_t *is_symlink_p, + const char *path, + apr_pool_t *pool) +{ + DWORD attrs; + const WCHAR *wpath; + apr_status_t status; + + if (path[0] == '\0') + path = "."; + + SVN_ERR(svn_io__utf8_to_unicode_longpath(&wpath, path, pool)); + + attrs = GetFileAttributesW(wpath); + if (attrs == INVALID_FILE_ATTRIBUTES) + { + status = apr_get_os_error(); + if (APR_STATUS_IS_ENOENT(status) || SVN__APR_STATUS_IS_ENOTDIR(status)) + { + *kind_p = svn_node_none; + if (is_symlink_p) + *is_symlink_p = FALSE; + return SVN_NO_ERROR; + } + else + { + return svn_error_wrap_apr(status, _("Can't stat '%s'"), + svn_dirent_local_style(path, pool)); + } + } + + if (attrs & FILE_ATTRIBUTE_DIRECTORY) + *kind_p = svn_node_dir; + else + *kind_p = svn_node_file; + + /* If this is a reparse point, and if we've been asked to check whether + we are dealing with a symlink, then open the file and check that. + + Otherwise, it's either definitely not a symlink or the caller + doesn't care about this distinction. + */ + if (is_symlink_p && (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) + { + const WCHAR *wfname; + HANDLE hFile; + FILE_ATTRIBUTE_TAG_INFO taginfo = { 0 }; + + SVN_ERR(svn_io__utf8_to_unicode_longpath(&wfname, path, pool)); + + hFile = CreateFileW(wfname, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + status = apr_get_os_error(); + if (APR_STATUS_IS_ENOENT(status) || SVN__APR_STATUS_IS_ENOTDIR(status)) + { + *kind_p = svn_node_none; + *is_symlink_p = FALSE; + return SVN_NO_ERROR; + } + else + { + return svn_error_wrap_apr(status, _("Can't stat '%s'"), + svn_dirent_local_style(path, pool)); + } + } + + status = win32_get_file_information_by_handle(hFile, FileAttributeTagInfo, + &taginfo, sizeof(taginfo)); + CloseHandle(hFile); + + if (status) + return svn_error_wrap_apr(status, _("Can't stat '%s'"), + svn_dirent_local_style(path, pool)); + + /* The surrogate bit in the reparse tag specifies if "the file or directory + represents another named entity in the system" which is used to determine + if this reparse point behaves like a symlink. + + https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags + */ + *is_symlink_p = IsReparseTagNameSurrogate(taginfo.ReparseTag); + } + else if (is_symlink_p) + { + *is_symlink_p = FALSE; + } + + return SVN_NO_ERROR; +} + svn_error_t * svn_io__win_delete_file_on_close(apr_file_t *file, const char *path, @@ -2115,6 +2334,55 @@ svn_io_set_file_read_write_carefully(const char *path, return svn_io_set_file_read_only(path, ignore_enoent, pool); } +#if defined(WIN32) || defined(__OS2__) +/* Helper for svn_io_set_file_read_* */ +static svn_error_t * +io_set_readonly_flag(const char *path_apr, /* file-system path */ + const char *path, /* UTF-8 path */ + svn_boolean_t set_flag, + svn_boolean_t is_file, + svn_boolean_t ignore_enoent, + apr_pool_t *pool) +{ + apr_status_t status; + + status = apr_file_attrs_set(path_apr, + (set_flag ? APR_FILE_ATTR_READONLY : 0), + APR_FILE_ATTR_READONLY, + pool); + + if (status && status != APR_ENOTIMPL) + if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status) + || SVN__APR_STATUS_IS_ENOTDIR(status)))) + { + if (is_file) + { + if (set_flag) + return svn_error_wrap_apr(status, + _("Can't set file '%s' read-only"), + svn_dirent_local_style(path, pool)); + else + return svn_error_wrap_apr(status, + _("Can't set file '%s' read-write"), + svn_dirent_local_style(path, pool)); + } + else + { + if (set_flag) + return svn_error_wrap_apr(status, + _("Can't set directory '%s' read-only"), + svn_dirent_local_style(path, pool)); + else + return svn_error_wrap_apr(status, + _("Can't set directory '%s' read-write"), + svn_dirent_local_style(path, pool)); + } + } + return SVN_NO_ERROR; +} +#endif + + svn_error_t * svn_io_set_file_read_only(const char *path, svn_boolean_t ignore_enoent, @@ -2126,24 +2394,11 @@ svn_io_set_file_read_only(const char *path, 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; + return io_set_readonly_flag(path_apr, path, + TRUE, TRUE, ignore_enoent, pool); #endif } @@ -2159,23 +2414,11 @@ svn_io_set_file_read_write(const char *path, 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; + return io_set_readonly_flag(path_apr, path, + FALSE, TRUE, ignore_enoent, pool); #endif } @@ -2546,27 +2789,37 @@ stringbuf_from_aprfile(svn_stringbuf_t **result, { 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) */ + /* 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. */ + /* In general, there is no guarantee that the given file size is + correct, for instance, because the underlying handle could be + pointing to a pipe. We don't know that in advance, so attempt + to read *one more* byte than necessary. If we get an EOF, then + we're done and we have succesfully avoided reading the file chunk- + by-chunk. If we don't, we fall through and do so to read the + remaining part of the file. */ svn_boolean_t eof; - res_initial_len = (apr_size_t)finfo.size; + res_initial_len = (apr_size_t)finfo.size + 1; 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; + if (eof) + { + *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); + if (!res) + 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. */ @@ -2712,8 +2965,8 @@ svn_io_remove_dir(const char *path, apr_pool_t *pool) 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. + See https://issues.apache.org/jira/browse/SVN-1896 and + https://issues.apache.org/jira/browse/SVN-3501. */ /* Neither windows nor unix allows us to delete a non-empty @@ -2751,6 +3004,12 @@ svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent, return svn_error_trace(err); } + /* On Unix, nothing can be removed from a non-writable directory. */ +#if !defined(WIN32) && !defined(__OS2__) + SVN_ERR(io_set_dir_perms(path, TRUE, TRUE, FALSE, FALSE, + ignore_enoent, pool)); +#endif + for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi)) { const char *name = apr_hash_this_key(hi); @@ -4232,7 +4491,45 @@ win32_file_rename(const WCHAR *from_path_w, } if (!MoveFileExW(from_path_w, to_path_w, flags)) - return apr_get_os_error(); + { + apr_status_t err = apr_get_os_error(); + /* If the target file is read only NTFS reports EACCESS and + FAT/FAT32 reports EEXIST */ + if (APR_STATUS_IS_EACCES(err) || APR_STATUS_IS_EEXIST(err)) + { + DWORD attrs = GetFileAttributesW(to_path_w); + if (attrs == INVALID_FILE_ATTRIBUTES) + { + apr_status_t stat_err = apr_get_os_error(); + if (!(APR_STATUS_IS_ENOENT(stat_err) || SVN__APR_STATUS_IS_ENOTDIR(stat_err))) + /* We failed to stat the file, propagate the original error */ + return err; + } + else if (attrs & FILE_ATTRIBUTE_READONLY) + { + /* Try to 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. */ + attrs &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributesW(to_path_w, attrs)) + { + err = apr_get_os_error(); + if (!(APR_STATUS_IS_ENOENT(err) || SVN__APR_STATUS_IS_ENOTDIR(err))) + /* We failed to set file attributes, propagate this new error */ + return err; + } + } + + /* NOTE: If the file is not read-only, we don't know if the file did + not have the read-only attribute in the first place or if this + attribute disappeared due to a race, so try to rename it anyway. + */ + if (!MoveFileExW(from_path_w, to_path_w, flags)) + return apr_get_os_error(); + } + else + return err; + } return APR_SUCCESS; } @@ -4256,18 +4553,6 @@ svn_io_file_rename2(const char *from_path, const char *to_path, 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__) @@ -4489,8 +4774,17 @@ svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool) { svn_boolean_t retry = TRUE; + if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status)) + { + /* Make the destination directory writable because Windows + forbids deleting read-only items. */ + SVN_ERR(io_set_readonly_flag(dirname_apr, dirname, + FALSE, FALSE, TRUE, pool)); + status = apr_dir_remove(dirname_apr, pool); + } + 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)) @@ -4636,7 +4930,7 @@ svn_io_dir_walk2(const char *dirname, } else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) { - /* some other directory. pass it to the callback. */ + /* a regular file or a symlink. 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); |