diff options
Diffstat (limited to 'subversion/libsvn_subr/io.c')
-rw-r--r-- | subversion/libsvn_subr/io.c | 299 |
1 files changed, 186 insertions, 113 deletions
diff --git a/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c index 468dd1723804f..8c4f26760507e 100644 --- a/subversion/libsvn_subr/io.c +++ b/subversion/libsvn_subr/io.c @@ -578,10 +578,8 @@ svn_io_open_uniquely_named(apr_file_t **file, continue; #ifdef WIN32 - apr_err_2 = APR_TO_OS_ERROR(apr_err); - - if (apr_err_2 == ERROR_ACCESS_DENIED || - apr_err_2 == ERROR_SHARING_VIOLATION) + 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 @@ -773,7 +771,7 @@ svn_io_copy_link(const char *src, ".tmp", pool)); /* Move the tmp-link to link. */ - return svn_io_file_rename(dst_tmp, dst, pool); + return svn_io_file_rename2(dst_tmp, dst, FALSE, pool); #else return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, @@ -942,7 +940,7 @@ svn_io_copy_file(const char *src, if (copy_perms) SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool)); - return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool)); + return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, pool)); } #if !defined(WIN32) && !defined(__OS2__) @@ -1480,18 +1478,14 @@ svn_io_file_checksum2(svn_checksum_t **checksum, 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); + SVN_ERR(svn_stream_contents_checksum(checksum, file_stream, kind, + pool, 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); + return SVN_NO_ERROR; } @@ -1527,20 +1521,23 @@ reown_file(const char *path, SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name, svn_dirent_dirname(path, pool), svn_io_file_del_none, pool, pool)); - SVN_ERR(svn_io_file_rename(path, unique_name, pool)); + SVN_ERR(svn_io_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. + 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, apr_pool_t *scratch_pool) +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; @@ -1567,12 +1564,18 @@ get_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool) 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, NULL, fname_base, + 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); @@ -1590,16 +1593,19 @@ get_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool) } /* OR together permission bits of the file FD and the default permissions - of a file as determined by get_default_file_perms(). Do temporary + 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, +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, scratch_pool)); + 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. */ @@ -1768,7 +1774,7 @@ svn_io__utf8_to_unicode_longpath(const WCHAR **result, * than the original number of utf-8 narrow chars. */ const WCHAR *prefix = NULL; - const int srclen = strlen(source); + const size_t srclen = strlen(source); WCHAR *buffer; if (srclen > 248) @@ -1879,12 +1885,12 @@ io_win_file_attrs_set(const char *fname, _("Can't set attributes of file '%s'"), svn_dirent_local_style(fname, pool)); - return SVN_NO_ERROR;; + return SVN_NO_ERROR; } static svn_error_t *win_init_dynamic_imports(void *baton, apr_pool_t *pool) { - HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); + HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); if (kernel32) { @@ -2274,7 +2280,6 @@ svn_io_is_file_executable(svn_boolean_t *executable, /*** File locking. ***/ -#if !defined(WIN32) && !defined(__OS2__) /* Clear all outstanding locks on ARG, an open apr_file_t *. */ static apr_status_t file_clear_locks(void *arg) @@ -2289,7 +2294,6 @@ file_clear_locks(void *arg) return 0; } -#endif svn_error_t * svn_io_lock_open_file(apr_file_t *lockfile_handle, @@ -2347,13 +2351,14 @@ svn_io_lock_open_file(apr_file_t *lockfile_handle, } } -/* On Windows and OS/2 file locks are automatically released when - the file handle closes */ -#if !defined(WIN32) && !defined(__OS2__) + /* 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); -#endif return SVN_NO_ERROR; } @@ -2377,11 +2382,7 @@ svn_io_unlock_open_file(apr_file_t *lockfile_handle, return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"), try_utf8_from_internal_style(fname, pool)); -/* On Windows and OS/2 file locks are automatically released when - the file handle closes */ -#if !defined(WIN32) && !defined(__OS2__) apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks); -#endif return SVN_NO_ERROR; } @@ -2455,6 +2456,14 @@ 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 @@ -2472,7 +2481,8 @@ svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, if (! FlushFileBuffers(filehand)) return svn_error_wrap_apr(apr_get_os_error(), - _("Can't flush file to disk")); + _("Can't flush file '%s' to disk"), + try_utf8_from_internal_style(fname, pool)); #else int rv; @@ -2493,7 +2503,8 @@ svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, if (rv == -1) return svn_error_wrap_apr(apr_get_os_error(), - _("Can't flush file to disk")); + _("Can't flush file '%s' to disk"), + try_utf8_from_internal_style(fname, pool)); #endif } @@ -2640,9 +2651,6 @@ svn_io_remove_file2(const char *path, 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 */ @@ -2721,7 +2729,7 @@ svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent, If we need to bail out, do so early. */ if (cancel_func) - SVN_ERR((*cancel_func)(cancel_baton)); + SVN_ERR(cancel_func(cancel_baton)); subpool = svn_pool_create(pool); @@ -2754,7 +2762,7 @@ svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent, else { if (cancel_func) - SVN_ERR((*cancel_func)(cancel_baton)); + SVN_ERR(cancel_func(cancel_baton)); err = svn_io_remove_file2(fullpath, FALSE, subpool); if (err) @@ -3736,6 +3744,32 @@ svn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted, 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, @@ -3906,21 +3940,20 @@ 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; + apr_status_t rv; - /* try a simple "write everything at once" first */ - apr_status_t rv = apr_file_write(file, buf, &bw); + rv = apr_file_write_full(file, buf, nbytes, &bw); buf = (char *)buf + bw; to_write -= bw; - /* if the OS cannot handle that, use smaller chunks */ + /* Issue #1789: On Windows, writing may fail for large values of NBYTES. + If that is the case, keep track of how many bytes have been written + by the apr_file_write_full() call, and attempt to write the remaining + part in smaller chunks. */ if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY) && nbytes > MAXBUFSIZE) { @@ -3981,11 +4014,12 @@ svn_io_write_unique(const char **tmp_path, } svn_error_t * -svn_io_write_atomic(const char *final_path, - const void *buf, - apr_size_t nbytes, - const char *copy_perms_path, - apr_pool_t *scratch_pool) +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; @@ -3998,7 +4032,7 @@ svn_io_write_atomic(const char *final_path, err = svn_io_file_write_full(tmp_file, buf, nbytes, NULL, scratch_pool); - if (!err) + if (!err && flush_to_disk) err = svn_io_file_flush_to_disk(tmp_file, scratch_pool); err = svn_error_compose_create(err, @@ -4008,7 +4042,8 @@ svn_io_write_atomic(const char *final_path, err = svn_io_copy_perms(copy_perms_path, tmp_path, scratch_pool); if (!err) - err = svn_io_file_rename(tmp_path, final_path, scratch_pool); + err = svn_io_file_rename2(tmp_path, final_path, flush_to_disk, + scratch_pool); if (err) { @@ -4022,22 +4057,6 @@ svn_io_write_atomic(const char *final_path, scratch_pool)); } -#ifdef __linux__ - { - /* Linux has the unusual feature that fsync() on a file is not - enough to ensure that a file's directory entries have been - flushed to disk; you have to fsync the directory as well. - On other operating systems, we'd only be asking for trouble - by trying to open and fsync a directory. */ - apr_file_t *file; - - SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT, - scratch_pool)); - SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool)); - SVN_ERR(svn_io_file_close(file, scratch_pool)); - } -#endif - return SVN_NO_ERROR; } @@ -4052,7 +4071,7 @@ svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool) To prevent this, write 1 dummy byte just after the OFFSET at which we will trunc it. That will force the APR file into write mode - internally and the flush() work-around below becomes affective. */ + internally and the flush() work-around below becomes effective. */ apr_off_t position = 0; /* A frequent usage is OFFSET==0, in which case we don't need to preserve @@ -4186,20 +4205,53 @@ svn_io_stat(apr_finfo_t *finfo, const char *fname, 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_rename(const char *from_path, const char *to_path, - apr_pool_t *pool) +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)); - status = apr_file_rename(from_path_apr, to_path_apr, 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 defined(WIN32) || defined(__OS2__) /* If the target file is read only NTFS reports EACCESS and FAT/FAT32 reports EEXIST */ if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status)) @@ -4209,9 +4261,25 @@ svn_io_file_rename(const char *from_path, const char *to_path, 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); } - WIN32_RETRY_LOOP(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) @@ -4219,6 +4287,34 @@ svn_io_file_rename(const char *from_path, const char *to_path, 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; } @@ -4227,37 +4323,16 @@ svn_error_t * svn_io_file_move(const char *from_path, const char *to_path, apr_pool_t *pool) { - svn_error_t *err = svn_io_file_rename(from_path, to_path, pool); + 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)) { - const char *tmp_to_path; - svn_error_clear(err); - SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path, - svn_dirent_dirname(to_path, pool), - svn_io_file_del_none, - pool, pool)); - - err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool); - if (err) - goto failed_tmp; - - err = svn_io_file_rename(tmp_to_path, to_path, pool); - if (err) - goto failed_tmp; - - err = svn_io_remove_file2(from_path, FALSE, pool); - if (! err) - return SVN_NO_ERROR; - - svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool)); - - return err; - - failed_tmp: - svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool)); + /* 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; @@ -4409,8 +4484,8 @@ svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool) { svn_boolean_t retry = TRUE; - if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY) - { + 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)) @@ -4689,7 +4764,7 @@ svn_io_write_version_file(const char *path, #endif /* WIN32 || __OS2__ */ /* rename the temp file as the real destination */ - SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); + 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); @@ -5086,7 +5161,7 @@ temp_file_create(apr_file_t **new_file, /* Generate a number that should be unique for this application and usually for the entire computer to reduce the number of cycles - through this loop. (A bit of calculation is much cheaper then + through this loop. (A bit of calculation is much cheaper than disk io) */ unique_nr = baseNr + 3 * i; @@ -5117,10 +5192,8 @@ temp_file_create(apr_file_t **new_file, if (!apr_err_2 && finfo.filetype == APR_DIR) continue; - apr_err_2 = APR_TO_OS_ERROR(apr_err); - - if (apr_err_2 == ERROR_ACCESS_DENIED || - apr_err_2 == ERROR_SHARING_VIOLATION) + 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 @@ -5254,7 +5327,8 @@ svn_io_open_unique_file3(apr_file_t **file, { svn_error_t *err; - SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool)); + SVN_ERR(merge_default_file_perms(tempfile, &perms, dirpath, + scratch_pool)); err = file_perms_set2(tempfile, perms, scratch_pool); if (err) { @@ -5334,8 +5408,7 @@ svn_io_file_readline(apr_file_t *file, apr_off_t pos; /* Check for "\r\n" by peeking at the next byte. */ - pos = 0; - SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); + SVN_ERR(svn_io_file_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') |