2682. filesystem::copy() won't create a symlink to a directory

Section: [fs.op.copy] Status: Open Submitter: Jonathan Wakely Opened: 2016-04-19 Last modified: 2016-10-16

Priority: 2

View other active issues in [fs.op.copy].

View all other issues in [fs.op.copy].

View all issues with Open status.


(First raised in c++std-lib-38544)

filesystem::copy doesn't create a symlink to a directory in this case:

copy("/", "root", copy_options::create_symlinks);

If the first path is a file then a symlink is created, but I think my implementation is correct to do nothing for a directory. We get to bullet [fs.op.copy] (3.6) where is_directory(f) is true, but options == create_symlinks, so we go to the next bullet (3.7) which says "Otherwise, no effects."

I think the case above should either create a symlink, or should report an error. GNU cp -s gives an error in this case, printing "omitting directory '/'". An error seems reasonable, you can use create_symlink to create a symlink to a directory.

[2016-05 Issues Telecon]

This is related to 2681; and should be considered together.

[2016-08 Chicago]

Wed AM: Move to Tentatively Ready

Previous resolution [SUPERSEDED]:

Add a new bullet following (3.6) in [fs.op.copy] as shown:

[2016-10-16, Eric reopens and provides improved wording]

The current PR makes using copy(...) to copy/create a directory symlink an error. For example, the following is now an error:

copy("/", "root", copy_options::create_symlinks);

However the current PR doesn't handle the case where both copy_options::create_symlinks and copy_options::recursive are specified. This case is still incorrectly handled by bullet (3.6) [fs.op.copy].

I suggest we move the PR before this bullet so that it catches the recursive copy case, since currently the conditions are ordered:

3.6 Otherwise if is_directory(f) && (bool(options & copy_options::recursive) || ...)
3.X Otherwise if is_directory(f) && bool(options & copy_options::create_symlinks)

So 3.6 catches create_symlinks | recursive but I believe we want 3.X to handle it instead.

Proposed resolution:

This wording is relative to N4606.

  1. Add a new bullet before (3.6) in [fs.op.copy] as shown:

    1. (3.5) — Otherwise, if is_regular_file(f), then:

    2. (3.?) — Otherwise, if

      is_directory(f) && 
      (options & copy_options::create_symlinks) != copy_options::none

      then report an error with an error_code argument equal to make_error_code(errc::is_a_directory).

    3. (3.6) — Otherwise, if

      is_directory(f) &&
      ((options & copy_options::recursive) != copy_options::none ||
      options == copy_options::none)


      1. (3.6.1) — If !exists(t), then create_directory(to, from).

      2. (3.6.2) — Then, iterate over the files in from, as if by for (directory_entry& x : directory_iterator(from)), and for each iteration

        copy(x.path(), to/x.path().filename(), options | copy_options::unspecified)
    4. (3.7) — Otherwise, for the signature with argument ec, ec.clear().

    5. (3.8) — Otherwise, no effects.