Skip to content

std::fs::copy causes extra AppleDouble sidecar ._ files on exFAT due to COPYFILE_ALL #154692

@konstin

Description

@konstin

std::fs::copy on an exFAT drive creates ._ AppleDouble sidcar files if there are any extended attributes on files, as it uses COPYFILE_ALL. This problem was first reported in uv: astral-sh/uv#18790.

let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { libc::COPYFILE_DATA };

This is different from python, which uses COPYFILE_DATA for shutil.copy instead:

https://github.com/python/cpython/blob/12828e5f98a47864e3f6d25fdd99c062fae28b1c/Lib/shutil.py#L319.

This is a problem extracting and then copying a directory, where there's suddenly extra files in the tree that shouldn't be there (astral-sh/uv#18790). We would like to retain the mode (specifically, the executable bit), but avoid having extra files that cause problem in a walkdir-and-copy scheme. I couldn't find any discussion on COPYFILE_ALL in #58901 (CC @ebarnard), hence I'm opening this issue.

This LLM-written script reproduces this on my macOS VM. It shows how std::fs::copy creates the sidecar file:

#!/usr/bin/env bash
# Reproducer: std::fs::copy creates ._ AppleDouble sidecar files on exFAT.
# Raw read/write and python avoid this.

WORKDIR=$(mktemp -d)
DMG="$WORKDIR/exfat.dmg"
VOL="$WORKDIR/mnt"
cleanup() { hdiutil detach "$VOL" 2>/dev/null; rm -rf "$WORKDIR"; }
trap cleanup EXIT
set -euo pipefail

mkdir -p "$VOL"
hdiutil create -size 64m -fs ExFAT -volname "EXFAT_UV" "$DMG" > /dev/null
hdiutil attach "$DMG" -mountpoint "$VOL" > /dev/null

SRC=$(mktemp)
echo "hello" > "$SRC"
xattr -w com.apple.quarantine "0081;deadbeef;test;0" "$SRC"

RDIR=$(mktemp -d)
cat > "$RDIR/main.rs" << 'EOF'
fn main() {
    let args: Vec<String> = std::env::args().collect();
    match args[1].as_str() {
        "std" => { std::fs::copy(&args[2], &args[3]).unwrap(); }
        "raw" => {
            let meta = std::fs::metadata(&args[2]).unwrap();
            let mut r = std::fs::File::open(&args[2]).unwrap();
            let mut w = std::fs::File::create(&args[3]).unwrap();
            std::io::copy(&mut r, &mut w).unwrap();
            w.set_permissions(meta.permissions()).unwrap();
        }
        _ => panic!("usage: copier std|raw <src> <dst>"),
    }
}
EOF
rustc "$RDIR/main.rs" -o "$RDIR/copier"

"$RDIR/copier" std "$SRC" "$VOL/test_std.txt"
echo "Rust std::fs::copy  → sidecar ._ files: $(find "$VOL" -name '._test_std*' | wc -l | tr -d ' ')"

"$RDIR/copier" raw "$SRC" "$VOL/test_raw.txt"
echo "Rust raw read/write → sidecar ._ files: $(find "$VOL" -name '._test_raw*' | wc -l | tr -d ' ')"

python3 -c "import shutil,sys; shutil.copyfile(sys.argv[1], sys.argv[2])" "$SRC" "$VOL/test_py.txt"
echo "Python shutil       → sidecar ._ files: $(find "$VOL" -name '._test_py*' | wc -l | tr -d ' ')"

rm -f "$SRC" && rm -rf "$RDIR"

Tested on 1.94.1, but the relevant code is in main (05cda35).

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-filesystemArea: `std::fs`C-bugCategory: This is a bug.O-appleOperating system: Apple / Darwin (macOS, iOS, tvOS, visionOS, watchOS)O-macosOperating system: macOST-libsRelevant to the library team, which will review and decide on the PR/issue.needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions