mirror of
https://github.com/boostorg/filesystem.git
synced 2025-05-12 13:41:47 +00:00
Added support for copy_options::update_existing to copy_file.
This commit is contained in:
parent
8c3ae354a0
commit
ac02dbed2e
@ -775,7 +775,8 @@ nothing else."</p>
|
||||
{
|
||||
none = 0u,
|
||||
skip_existing,
|
||||
overwrite_existing
|
||||
overwrite_existing,
|
||||
update_existing
|
||||
};
|
||||
|
||||
// Deprecated, use <a href="#copy_options">copy_options</a> instead
|
||||
@ -3146,7 +3147,7 @@ void <a name="copy_file2">copy_file</a>(const path& from, const path& to
|
||||
void <a name="copy_file3">copy_file</a>(const path& from, const path& to, <a href="#copy_option">copy_option</a> options);
|
||||
void <a name="copy_file4">copy_file</a>(const path& from, const path& to, <a href="#copy_option">copy_option</a> options, system::error_code& ec);</pre>
|
||||
<blockquote>
|
||||
<p><i>Precondition:</i> At most one of <code>copy_options::skip_existing</code> or <code>copy_options::overwrite_existing</code> must be specified in <code>options</code>.</p>
|
||||
<p><i>Precondition:</i> At most one of <code>copy_options::skip_existing</code>, <code>copy_options::overwrite_existing</code> or <code>copy_options::update_existing</code> must be specified in <code>options</code>.</p>
|
||||
<p><i>Effects:</i> Report an error if:
|
||||
<ul>
|
||||
<li><code>!is_regular_file(from)</code>, or</li>
|
||||
@ -3154,10 +3155,15 @@ void <a name="copy_file4">copy_file</a>(const path& from, const path& to
|
||||
<li><code>exists(to) && equivalent(from, to)</code>, or</li>
|
||||
<li><code>exists(to) && (options & (copy_options::skip_existing | copy_options::overwrite_existing)) == copy_options::none</code>.</li>
|
||||
</ul>
|
||||
Otherwise, if <code>exists(to) && (options & copy_options::skip_existing) != copy_options::none</code>, return successfully with no effect.
|
||||
Otherwise, return successfully with no effect if:
|
||||
<ul>
|
||||
<li><code>exists(to) && (options & copy_options::skip_existing) != copy_options::none</code>, or</li>
|
||||
<li><code>exists(to) && (options & copy_options::update_existing) != copy_options::none</code> and last write time of <code>from</code> is more recent than that of <code>to</code>.</li>
|
||||
</ul>
|
||||
Otherwise, the contents and attributes of the file <code>from</code> resolves to are copied to the file <code>to</code> resolves to.</p>
|
||||
<p><i>Throws:</i> As specified in <a href="#Error-reporting">Error reporting</a>.</p>
|
||||
<p>[<i>Note:</i> The overloads taking <a href="#copy_option"><code>copy_option</code></a> are deprecated. Their effect is equivalent to the corresponding overloads taking <a href="#copy_options"><code>copy_options</code></a> after casting the <code>options</code> argument to <a href="#copy_options"><code>copy_options</code></a>.]</p>
|
||||
<p>[<i>Note:</i> When <code>copy_options::update_existing</code> is specified, checking the write times of <code>from</code> and <code>to</code> may not be atomic with the copy operation. Another process may create or modify the file identified by <code>to</code> after the file modification times have been checked but before copying starts. In this case the target file will be overwritten.]</p>
|
||||
</blockquote>
|
||||
<pre>void <a name="copy_symlink">copy_symlink</a>(const path& existing_symlink, const path& new_symlink);
|
||||
void copy_symlink(const path& existing_symlink, const path& new_symlink, system::error_code& ec);</pre>
|
||||
|
@ -48,6 +48,7 @@
|
||||
<li><b>New:</b> Added <code>copy_file</code> implementations based on <code>sendfile</code> and <code>copy_file_range</code> system calls on Linux, which may improve file copying performance, especially on network filesystems.</li>
|
||||
<li><b>Deprecated:</b> The <code>copy_option</code> enumeration that is used with the <code>copy_file</code> operation is deprecated. As a replacement, the new enum <code>copy_options</code> (note the trailing 's') has been added. The new enum contains values similar to the <code>copy_options</code> enum from C++20. The old enum values are mapped onto the new enum. The old enum will be removed in a future release.</li>
|
||||
<li><b>New:</b> Added <code>copy_options::skip_existing</code> option, which allows <code>copy_file</code> operation to succeed without overwriting the target file, if it exists.</li>
|
||||
<li><b>New:</b> Added <code>copy_options::update_existing</code> option, which allows <code>copy_file</code> operation to conditionally overwrite the target file, if it exists, if its last write time is older than that of the replacement file.</li>
|
||||
<li><code>equivalent</code> on POSIX systems now returns the actual error code from the OS if one of the paths does not resolve to a file. Previously the function would return an error code of 1. (<a href="https://github.com/boostorg/filesystem/issues/141">#141</a>)</li>
|
||||
<li><code>equivalent</code> no longer considers file size and last modification time in order to test whether the two paths refer to the same file. These checks could result in a false negative if the file was modified during the <code>equivalent</code> call.</li>
|
||||
<li><code>space</code> now initializes the <code>space_info</code> structure members to -1 values on error, as required by C++20 ([fs.op.space]/1).</li>
|
||||
|
@ -57,7 +57,8 @@ BOOST_SCOPED_ENUM_UT_DECLARE_BEGIN(copy_options, unsigned int)
|
||||
{
|
||||
none = 0u, // Default, error if the target file exists
|
||||
skip_existing = 1u, // Don't overwrite the existing target file, don't report an error
|
||||
overwrite_existing = 1u << 1 // Overwrite existing file
|
||||
overwrite_existing = 1u << 1, // Overwrite existing file
|
||||
update_existing = 1u << 2 // Overwrite existing file if its last write time is older than the replacement file
|
||||
}
|
||||
BOOST_SCOPED_ENUM_DECLARE_END(copy_options)
|
||||
|
||||
|
@ -1010,7 +1010,8 @@ BOOST_FILESYSTEM_DECL
|
||||
void copy_file(const path& from, const path& to, unsigned int options, error_code* ec)
|
||||
{
|
||||
BOOST_ASSERT((((options & static_cast< unsigned int >(copy_options::overwrite_existing)) != 0u) +
|
||||
((options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u)) <= 1u);
|
||||
((options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) +
|
||||
((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u)) <= 1u);
|
||||
|
||||
if (ec)
|
||||
ec->clear();
|
||||
@ -1022,19 +1023,30 @@ void copy_file(const path& from, const path& to, unsigned int options, error_cod
|
||||
// Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors
|
||||
fd_wrapper infile, outfile;
|
||||
|
||||
infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (BOOST_UNLIKELY(infile.fd < 0))
|
||||
while (true)
|
||||
{
|
||||
fail_errno:
|
||||
err = errno;
|
||||
fail:
|
||||
error(err, from, to, ec, "boost::filesystem::copy_file");
|
||||
return;
|
||||
infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (BOOST_UNLIKELY(infile.fd < 0))
|
||||
{
|
||||
err = errno;
|
||||
if (err == EINTR)
|
||||
continue;
|
||||
|
||||
fail:
|
||||
error(err, from, to, ec, "boost::filesystem::copy_file");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
struct ::stat from_stat = {};
|
||||
if (BOOST_UNLIKELY(::fstat(infile.fd, &from_stat) != 0))
|
||||
goto fail_errno;
|
||||
{
|
||||
fail_errno:
|
||||
err = errno;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (BOOST_UNLIKELY(!S_ISREG(from_stat.st_mode)))
|
||||
{
|
||||
@ -1045,20 +1057,57 @@ void copy_file(const path& from, const path& to, unsigned int options, error_cod
|
||||
// Enable writing for the newly created files. Having write permission set is important e.g. for NFS,
|
||||
// which checks the file permission on the server, even if the client's file descriptor supports writing.
|
||||
mode_t to_mode = from_stat.st_mode | S_IWUSR;
|
||||
int oflag = O_WRONLY | O_CLOEXEC;
|
||||
|
||||
int oflag = O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC;
|
||||
if ((options & static_cast< unsigned int >(copy_options::overwrite_existing)) == 0u ||
|
||||
(options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u)
|
||||
if ((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u)
|
||||
{
|
||||
oflag |= O_EXCL;
|
||||
// Try opening the existing file without truncation to test the modification time later
|
||||
while (true)
|
||||
{
|
||||
outfile.fd = ::open(to.c_str(), oflag, to_mode);
|
||||
if (outfile.fd < 0)
|
||||
{
|
||||
err = errno;
|
||||
if (err == EINTR)
|
||||
continue;
|
||||
|
||||
if (err == ENOENT)
|
||||
goto create_outfile;
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
outfile.fd = ::open(to.c_str(), oflag, to_mode);
|
||||
if (outfile.fd < 0)
|
||||
else
|
||||
{
|
||||
err = errno;
|
||||
if (err == EEXIST && (options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u)
|
||||
return;
|
||||
goto fail;
|
||||
create_outfile:
|
||||
oflag |= O_CREAT | O_TRUNC;
|
||||
if (((options & static_cast< unsigned int >(copy_options::overwrite_existing)) == 0u ||
|
||||
(options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u) &&
|
||||
(options & static_cast< unsigned int >(copy_options::update_existing)) == 0u)
|
||||
{
|
||||
oflag |= O_EXCL;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
outfile.fd = ::open(to.c_str(), oflag, to_mode);
|
||||
if (outfile.fd < 0)
|
||||
{
|
||||
err = errno;
|
||||
if (err == EINTR)
|
||||
continue;
|
||||
|
||||
if (err == EEXIST && (options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u)
|
||||
return;
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct ::stat to_stat = {};
|
||||
@ -1077,6 +1126,23 @@ void copy_file(const path& from, const path& to, unsigned int options, error_cod
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((oflag & O_TRUNC) == 0)
|
||||
{
|
||||
// O_TRUNC is not set if copy_options::update_existing is set and an existing file was opened.
|
||||
// We need to check the last write times.
|
||||
#if defined(st_mtime)
|
||||
// Modify time is available with nanosecond precision.
|
||||
if (from_stat.st_mtime < to_stat.st_mtime || (from_stat.st_mtime == to_stat.st_mtime && from_stat.st_mtim.tv_nsec <= to_stat.st_mtim.tv_nsec))
|
||||
return;
|
||||
#else
|
||||
if (from_stat.st_mtime <= to_stat.st_mtime)
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (BOOST_UNLIKELY(::ftruncate(outfile.fd, 0) != 0))
|
||||
goto fail_errno;
|
||||
}
|
||||
|
||||
err = detail::copy_file_data(infile.fd, from_stat, outfile.fd, to_stat);
|
||||
if (BOOST_UNLIKELY(err != 0))
|
||||
goto fail; // err already contains the error code
|
||||
@ -1105,8 +1171,49 @@ void copy_file(const path& from, const path& to, unsigned int options, error_cod
|
||||
|
||||
#else // defined(BOOST_POSIX_API)
|
||||
|
||||
const bool fail_if_exists = (options & static_cast< unsigned int >(copy_options::overwrite_existing)) == 0u ||
|
||||
bool fail_if_exists = (options & static_cast< unsigned int >(copy_options::overwrite_existing)) == 0u ||
|
||||
(options & static_cast< unsigned int >(copy_options::skip_existing)) != 0u;
|
||||
|
||||
if ((options & static_cast< unsigned int >(copy_options::update_existing)) != 0u)
|
||||
{
|
||||
// Create handle_wrappers here so that CloseHandle calls don't clobber error code returned by GetLastError
|
||||
handle_wrapper hw_from, hw_to;
|
||||
|
||||
hw_from.handle = create_file_handle(from.c_str(), 0,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
|
||||
|
||||
FILETIME lwt_from;
|
||||
if (hw_from.handle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
fail_last_error:
|
||||
DWORD err = ::GetLastError();
|
||||
error(err, from, to, ec, "boost::filesystem::copy_file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!::GetFileTime(hw_from.handle, 0, 0, &lwt_from))
|
||||
goto fail_last_error;
|
||||
|
||||
hw_to.handle = create_file_handle(to.c_str(), 0,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
|
||||
|
||||
if (hw_to.handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
FILETIME lwt_to;
|
||||
if (!::GetFileTime(hw_to.handle, 0, 0, &lwt_to))
|
||||
goto fail_last_error;
|
||||
|
||||
ULONGLONG tfrom = (static_cast< ULONGLONG >(lwt_from.dwHighDateTime) << 32) | static_cast< ULONGLONG >(lwt_from.dwLowDateTime);
|
||||
ULONGLONG tto = (static_cast< ULONGLONG >(lwt_to.dwHighDateTime) << 32) | static_cast< ULONGLONG >(lwt_to.dwLowDateTime);
|
||||
if (tfrom <= tto)
|
||||
return;
|
||||
}
|
||||
|
||||
fail_if_exists = false;
|
||||
}
|
||||
|
||||
BOOL res = ::CopyFileW(from.c_str(), to.c_str(), fail_if_exists);
|
||||
if (!res)
|
||||
{
|
||||
|
@ -82,6 +82,7 @@ inline void unsetenv_(const char* name)
|
||||
|
||||
#else
|
||||
|
||||
#include <unistd.h> // sleep
|
||||
#include <stdlib.h> // allow unqualifed calls to env funcs on SunOS
|
||||
|
||||
inline void setenv_(const char* name, const char* val, int ovw)
|
||||
@ -1618,8 +1619,33 @@ namespace
|
||||
BOOST_TEST_EQ(fs::file_size(d1x / "f2"), 10U);
|
||||
verify_file(d1x / "f2", "1234567890");
|
||||
|
||||
// create_file(d1x / "f2", "1234567890");
|
||||
// BOOST_TEST_EQ(fs::file_size(d1x / "f2"), 10U);
|
||||
copy_ex_ok = true;
|
||||
try { fs::copy_file(f1x, d1x / "f2", fs::copy_options::update_existing); }
|
||||
catch (const fs::filesystem_error &) { copy_ex_ok = false; }
|
||||
BOOST_TEST(copy_ex_ok);
|
||||
BOOST_TEST_EQ(fs::file_size(d1x / "f2"), 10U);
|
||||
verify_file(d1x / "f2", "1234567890");
|
||||
|
||||
// Sleep for a while so that the last modify time is more recent for new files
|
||||
#if defined(BOOST_POSIX_API)
|
||||
sleep(2);
|
||||
#else
|
||||
Sleep(2000);
|
||||
#endif
|
||||
|
||||
create_file(d1x / "f2-more-recent", "x");
|
||||
BOOST_TEST_EQ(fs::file_size(d1x / "f2-more-recent"), 1U);
|
||||
copy_ex_ok = true;
|
||||
try { fs::copy_file(d1x / "f2-more-recent", d1x / "f2", fs::copy_options::update_existing); }
|
||||
catch (const fs::filesystem_error &) { copy_ex_ok = false; }
|
||||
BOOST_TEST(copy_ex_ok);
|
||||
BOOST_TEST_EQ(fs::file_size(d1x / "f2"), 1U);
|
||||
verify_file(d1x / "f2", "x");
|
||||
fs::remove(d1x / "f2-more-recent");
|
||||
|
||||
fs::remove(d1x / "f2");
|
||||
create_file(d1x / "f2", "1234567890");
|
||||
BOOST_TEST_EQ(fs::file_size(d1x / "f2"), 10U);
|
||||
copy_ex_ok = true;
|
||||
try { fs::copy_file(f1x, d1x / "f2", fs::copy_options::overwrite_existing); }
|
||||
catch (const fs::filesystem_error &) { copy_ex_ok = false; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user