Problem
cargo_util::paths::write_atomic currently resolves at most one symlink level.
For a symlink chain such as:
/project/Cargo.toml -> generated/intermediate.toml
/project/generated/intermediate.toml -> final/Cargo.toml
calling:
write_atomic("/project/Cargo.toml", contents)
should update:
/project/generated/final/Cargo.toml
However, the current implementation resolves only the first link and performs the atomic replacement on:
/project/generated/intermediate.toml
Since tempfile::persist replaces the destination path, the intermediate symlink is replaced with a regular file.
The final target is not updated and the symlink chain is broken.
This is a follow-up to #17361 and #17362. Those changes fixed relative targets for a single symlink level, but did not handle chained symlinks.
Steps
The following reproduces the issue on Unix:
tmp="$(mktemp -d)"
mkdir -p "$tmp/project/generated/final/src"
mkdir -p "$tmp/runner"
cat > "$tmp/project/generated/final/Cargo.toml" <<'EOF'
[package]
name = "chained-symlink-repro"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = "1"
EOF
touch "$tmp/project/generated/final/src/lib.rs"
# Create the second link in the chain.
ln -s final/Cargo.toml "$tmp/project/generated/intermediate.toml"
# Create the first link in the chain.
ln -s generated/intermediate.toml "$tmp/project/Cargo.toml"
# Run Cargo from a different directory.
cd "$tmp/runner"
cargo remove serde --manifest-path ../project/Cargo.toml
Inspect the files afterward:
printf '%s\n' '--- first symlink ---'
readlink ../project/Cargo.toml
printf '%s\n' '--- second symlink ---'
readlink ../project/generated/intermediate.toml
printf '%s\n' '--- final target ---'
cat ../project/generated/final/Cargo.toml
The final target should be updated, both symlinks should remain symlinks, and their stored targets should remain unchanged:
project/Cargo.toml -> generated/intermediate.toml
project/generated/intermediate.toml -> final/Cargo.toml
Possible Solution(s)
Resolve symlinks repeatedly until a non-symlink path is reached. Relative targets should be resolved against the parent directory of the symlink that contains them.
The implementation should also detect symlink loops such as:
first -> second
second -> first
and return an error instead of looping indefinitely.
One open design question is whether a dangling final target should continue to be created, matching the current behavior, or should instead produce an error.
Notes
No response
Version
Problem
cargo_util::paths::write_atomiccurrently resolves at most one symlink level.For a symlink chain such as:
calling:
should update:
However, the current implementation resolves only the first link and performs the atomic replacement on:
Since
tempfile::persistreplaces the destination path, the intermediate symlink is replaced with a regular file.The final target is not updated and the symlink chain is broken.
This is a follow-up to #17361 and #17362. Those changes fixed relative targets for a single symlink level, but did not handle chained symlinks.
Steps
The following reproduces the issue on Unix:
Inspect the files afterward:
The final target should be updated, both symlinks should remain symlinks, and their stored targets should remain unchanged:
Possible Solution(s)
Resolve symlinks repeatedly until a non-symlink path is reached. Relative targets should be resolved against the parent directory of the symlink that contains them.
The implementation should also detect symlink loops such as:
and return an error instead of looping indefinitely.
One open design question is whether a dangling final target should continue to be created, matching the current behavior, or should instead produce an error.
Notes
No response
Version