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.