https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119289
Bug ID: 119289 Summary: Incorrect behavior of std::filesystem::copy() with none options and the destination link to non-existent file Product: gcc Version: 14.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: parkjuny at kaist dot ac.kr Target Milestone: --- ## Versions and Compile Options Checked on gcc 13.3.0 and 14.2.0, exact version strings are as follows: - `g++-14 (Ubuntu 14.2.0-4ubuntu2~24.04) 14.2.0` - `g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0` Compile option I used is: `g++ test.cpp -o test -Wall -Wextra`. I also tried `-std=c++17` and `-std=c++20`, but the results were the same. ## Summary `std::filesystem::copy` with no symbolic link options and `to` is a symlink to a non-existent file currently reports an error (file already exists). However, this is expected to correctly copy the contents of the source file to the path to which `to` resolves. ## Reproducing ### Files I prepared two files: - `src.txt`: a regular file with some random contents - `link_dst`: a symlink to a non-existent file `asdf` Copy would be attempted from `src.txt` to the path to which `link_dst` resolves, the non-existent file `asdf`. ``` candymate@server:~/temp$ ls -al total 56 drwxrwxr-x 2 candymate candymate 4096 3월 14 21:13 . drwxr-xr-x 9 candymate candymate 4096 3월 14 21:05 .. lrwxrwxrwx 1 candymate candymate 25 3월 14 21:00 link_dst -> /home/candymate/temp/asdf -rw-rw-r-- 1 candymate candymate 5 3월 14 12:54 src.txt -rwxrwxr-x 1 candymate candymate 39464 3월 14 21:13 test -rw-rw-r-- 1 candymate candymate 1804 3월 14 21:05 test.cpp ``` ### Code ```cpp #include <fstream> #include <filesystem> #include <iostream> namespace fs = std::filesystem; void demo_status(fs::file_status s) { switch (s.type()) { case fs::file_type::none: std::cout << " has `not-evaluated-yet` type"; break; case fs::file_type::not_found: std::cout << " does not exist"; break; case fs::file_type::regular: std::cout << " is a regular file"; break; case fs::file_type::directory: std::cout << " is a directory"; break; case fs::file_type::symlink: std::cout << " is a symlink"; break; case fs::file_type::block: std::cout << " is a block device"; break; case fs::file_type::character: std::cout << " is a character device"; break; case fs::file_type::fifo: std::cout << " is a named IPC pipe"; break; case fs::file_type::socket: std::cout << " is a named IPC socket"; break; case fs::file_type::unknown: std::cout << " has `unknown` type"; break; default: std::cout << " has `implementation-defined` type"; break; } std::cout << '\n'; } int main() { // to check control values std::cout << std::filesystem::exists("link_dst") << std::endl; demo_status(std::filesystem::status("link_dst")); std::filesystem::copy("src.txt", "link_dst"); // this does not work (throws error) // this works by correctly copying source src.txt to the destination link_dst is pointing to //std::filesystem::copy("src.txt", "link_dst", std::filesystem::copy_options::overwrite_existing); return 0; } ``` ### Build and Run ```bash g++ test.cpp -o test -Wall -Wextra ./test ``` ### Expected ``` 0 does not exist ``` ``` total 44 drwxrwxr-x 2 candymate candymate 4096 3월 14 21:02 ./ drwxr-xr-x 9 candymate candymate 4096 3월 14 21:02 ../ -rw-rw-r-- 1 candymate candymate 5 3월 14 21:02 asdf lrwxrwxrwx 1 candymate candymate 25 3월 14 21:00 link_dst -> /home/candymate/temp/asdf -rw-rw-r-- 1 candymate candymate 5 3월 14 12:54 src.txt -rwxrwxr-x 1 candymate candymate 21880 3월 14 21:02 test* -rw-rw-r-- 1 candymate candymate 1742 3월 14 21:02 test.cpp ``` ### Actual ``` 0 does not exist terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error' what(): filesystem error: cannot copy: File exists [src.txt] [link_dst] Aborted (core dumped) ``` No changes in the file system. ### Analysis on Specification I've checked the draft version of the specification for the accurate description of the behavior of `std::filesystem::copy`. (https://github.com/cplusplus/draft) - `std::filesystem::copy` - (4.1) If `create_symlinks` or `skip_symlinks` is present, ... -- **false** --> (4.2) - (4.2) Otherwise, if `copy_symlinks` is present, ... -- **false** --> (4.3) - (4.3) Otherwise, `auto f = status(from)` and `auto t = status(to)` -- **true** --> (4.4) - This gives `t.type() == fs::file_type::not_found` - (4.4) If `f.type()` or `t.type()` is an implementation-defined file type, ... -- **false** --> (4.5) - (4.5) Otherwise, an error is reported as specified in 31.12.5 if - (4.5.1) `exists(f)` is false -- **false** --> (4.5.2) - (4.5.2) `equivalent(from, to)` is true -- **false** --> (4.5.3) - (4.5.3) `is_other(f) || is_other(t)` is true -- **false** --> (4.5.4) - `exists(t)` is false, so `is_other(t)` is false - (4.5.4) `is_directory(f) && is_regular_file(t)` is true -- **false** --> (4.6) - (4.6) Otherwise, if `is_symlink(f)` -- **false** --> (4.7) - (4.7) Otherwise, if `is_regular_file(f)`, then -- **true** - (4.7.1) `directories_only` is present -- **false** --> (4.7.2) - (4.7.2) `create_symlinks` is present -- **false** --> (4.7.3) - (4.7.3) `create_hard_links` is present -- **false** --> (4.7.4) - (4.7.4) `is_directory(t)` -- **false** --> (4.7.5) - **(4.7.5) Otherwise, `copy_file(from, to, options)`** -- **true** - `std::filesystem::copy_file` - (4.1) Report an error as specified in 31.12.5 if -- **false** - (4.1.1) `is_regular_file(from)` is false -- **false** --> (4.1.2) - (4.1.2-4.1.4) `exists(to)` is true -- **false** --> (4.2) - (4.2) Otherwise, copy the contents and attributes of the file `from` resolves to, to the file `to` resolves to, if -- **true** - **(4.2.1) `exists(to)` is false** -- **true** Therefore, `std::filesystem::copy` should correctly copy the contents of the source file to the path to which `to` resolves. I also experimented with `overwrite_existing` option, and it works as expected. It correctly copies the contents of the source file to the destination path the link is pointing to. ### Etc - Due to my misunderstanding to the specification and the report rules, I once wrongly posted a bug report as https://github.com/cplusplus/draft/issues/7734. I'm sorry for this. **This report is different from the previous one.** - I've rechecked the specification three times, and I'm pretty sure that my understanding is correct. I hope I don't have any other misunderstandings.