I recently upgraded Boost from 1.61 to 1.78 (gcc 7.5.0 on an Ubuntu 18.04 VirtualBox VM) and there appears to a change in the behaviour of filesystem::copy_file.
Now, when I copy a read-only file to a directory with full permissions, I get a filesystem_error exception with detail:
boost::filesystem::copy_file: Operation not permitted [system:1]
Despite this, the read-only file is always successfully copied! Interestingly, the destination file is not read-only. In 1.61 the copy behaviour was the same but there was no exception.
This simple program reproduces the issue:
#include <boost/filesystem.hpp>
#include <string>
#include <iostream>
int main()
{
// copyFrom should be write protected
boost::filesystem::path copyFrom("./files/myfile.txt");
boost::filesystem::path copyTo("./files/myfile2.txt");
try
{
boost::filesystem::copy_file(copyFrom, copyTo, boost::filesystem::copy_options::overwrite_existing);
}
catch (const boost::filesystem::filesystem_error& e) {
std::cout << "Failed to copy " << copyFrom.string() << " to " << copyTo.string() << std::endl
<< "Exception: " << e.what() << std::endl
<< "Error code: " << e.code().message() << std::endl
<< "Error code value: " << e.code().value() << std::endl;
return -1;
}
std::cout << "Copy success!" << std::endl;
return 0;
}
Compile:
gcc -c -x c++ /home/code/copyfile/copyfile.cpp -I /home/code/Boost/1.78.0/lib/native/include -g1 -o "/home/code/copyfile/copyfile.o" -Wall -O2 -DNDEBUG -std=c++11
Link:
g++ -o "/home/code/copyfile/copyfile.out" -L/home/code/Boost/1.78.0/release/lib /home/code/copyfile/copyfile.o "-lboost_filesystem"
Output:
Failed to copy ./files/myfile.txt to ./files/myfile2.txt
Exception: boost::filesystem::copy_file: Operation not permitted [system:1]: "./files/myfile.txt", "./files/myfile2.txt"
Error code: Operation not permitted
Error code value: 1
Removing the read-only flag on the source file results in the copy succeeding with no exception, as does running the command elevated (sudo /.copyfile.out).
This seems like unusual behaviour - I am not modifying the source file in any way, so why does the exception occur?
Removing and later adding back the read-only flag on the source file feels like a hack. This code will often run over directories where there will be many read-only files.
If the exception code returned ("1") refers to the fact that the read-only state of the source file was not preserved on the copied file (for me this doesn't matter), I could use this value to decide whether to continue. Either way it seems strange because this didn't happen in the older Boost.
EDIT: strace output as per comment:
openat(AT_FDCWD, "./files/myfile.txt", O_RDONLY|O_CLOEXEC) = 3
statx(3, "", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT|AT_EMPTY_PATH, STATX_TYPE|STATX_MODE|STATX_INO|STATX_SIZE, {stx_mask=STATX_ALL, stx_attributes=0, stx_mode=S_IFREG|0555, stx_size=9, ...}) = 0
openat(AT_FDCWD, "./files/myfile2.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0100755) = 4
statx(4, "", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT|AT_EMPTY_PATH, STATX_TYPE|STATX_MODE|STATX_INO, {stx_mask=STATX_ALL, stx_attributes=0, stx_mode=S_IFREG|0777, stx_size=0, ...}) = 0
fstatfs(3, {f_type=0x786f4256, f_bsize=4096, f_blocks=244189951, f_bfree=48066312, f_bavail=48066312, f_files=1000, f_ffree=1000000, f_fsid={val=[0, 0]}, f_namelen=255, f_frsize=4096, f_flags=ST_VALID|ST_NODEV|ST_RELATIME}) = 0
copy_file_range(3, NULL, 4, NULL, 9, 0) = 9
fchmod(4, 0100555) = -1 EPERM (Operation not permitted)
close(4) = 0
close(3) = 0