Skip to content

write_atomic does not resolve chained symlinks #17443

Description

@cuishuang

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


Activity

  1. added
    C-bugCategory: bug
    S-triageStatus: This issue is waiting on initial triage.
    on Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: bugS-triageStatus: This issue is waiting on initial triage.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions