Skip to content

dir::copy does not preserve symlinked directories #61

@edmorley

Description

@edmorley

Summary

If one of the file entries being copied by fs_extra::dir::copy happens to be a symlink to a directory, then that symlink is converted to a real directory as part of copying, resulting in (a) duplicate file contents in the destination directory, (b) non-identical contents between source and destination (which defeats the point of copying a directory verbatim).

Note: Symlinks to files are preserved, this is just about symlinks that target directories.

Steps to reproduce

Using fs_extra = "=1.2.0" with rustc 1.63.0-beta.3 on macOS 12.4, run the following:

use fs_extra::dir::CopyOptions;
use std::fs::{self, File};
use std::path::{Path, PathBuf};

fn main() {
    let source_dir = PathBuf::from("source");
    let destination_dir = PathBuf::from("destination");
    if source_dir.exists() {
        fs::remove_dir_all(&source_dir).unwrap();
    }
    if destination_dir.exists() {
        fs::remove_dir_all(&destination_dir).unwrap();
    }

    let subdir = source_dir.join("subdir");
    fs::create_dir_all(&subdir).unwrap();
    File::create(&subdir.join("file.txt")).unwrap();
    create_dir_symlink("subdir", &source_dir.join("symlink-to-subdir")).unwrap();

    let options = &CopyOptions {
        content_only: true,
        ..CopyOptions::default()
    };
    fs_extra::dir::copy(source_dir, destination_dir, options).unwrap();
}

#[cfg(target_family = "unix")]
fn create_dir_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> std::io::Result<()> {
    std::os::unix::fs::symlink(original.as_ref(), link.as_ref())
}

#[cfg(target_family = "windows")]
fn create_dir_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> std::io::Result<()> {
    std::os::windows::fs::symlink_dir(original.as_ref(), link.as_ref())
}

Expected

The contents of source/ and destination/ to be identical, and for any symlinks targetting directories to be preserved as symlinks.

Actual

The source/ and destination/ directories have different contents, with the symlinked directory now being a real directory containing duplicate files.

Source:

$ ls -alR source/
source/:
total 0
drwxr-xr-x  4 emorley staff 128 Jul  6 14:03 .
drwxr-xr-x 10 emorley staff 320 Jul  6 14:03 ..
drwxr-xr-x  3 emorley staff  96 Jul  6 14:03 subdir
lrwxr-xr-x  1 emorley staff   6 Jul  6 14:03 symlink-to-subdir -> subdir

source/subdir:
total 0
drwxr-xr-x 3 emorley staff  96 Jul  6 14:03 .
drwxr-xr-x 4 emorley staff 128 Jul  6 14:03 ..
-rw-r--r-- 1 emorley staff   0 Jul  6 14:03 file.txt

Destination:

$ ls -alR destination/
destination/:
total 0
drwxr-xr-x  4 emorley staff 128 Jul  6 14:03 .
drwxr-xr-x 10 emorley staff 320 Jul  6 14:03 ..
drwxr-xr-x  3 emorley staff  96 Jul  6 14:03 subdir
drwxr-xr-x  3 emorley staff  96 Jul  6 14:03 symlink-to-subdir

destination/subdir:
total 0
drwxr-xr-x 3 emorley staff  96 Jul  6 14:03 .
drwxr-xr-x 4 emorley staff 128 Jul  6 14:03 ..
-rw-r--r-- 1 emorley staff   0 Jul  6 14:03 file.txt

destination/symlink-to-subdir:
total 0
drwxr-xr-x 3 emorley staff  96 Jul  6 14:03 .
drwxr-xr-x 4 emorley staff 128 Jul  6 14:03 ..
-rw-r--r-- 1 emorley staff   0 Jul  6 14:03 file.txt

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions