Skip to content

Report a missing hard link source before an existing destination - #1357

Open
syahra712 wants to merge 2 commits into
pytest-dev:mainfrom
syahra712:fix-link-missing-source-errno
Open

syahra712 wants to merge 2 commits into
pytest-dev:mainfrom
syahra712:fix-link-missing-source-errno

Conversation

@syahra712

Copy link
Copy Markdown

Describe the changes

os.link(src, dst) with a missing src and an existing dst raises EEXIST under pyfakefs, but the real call raises ENOENT:

open("dst", "w").close()
os.link("missing", "dst")
# real (macOS and Linux): FileNotFoundError: [Errno 2]
# pyfakefs:               FileExistsError:   [Errno 17]

On Posix, link() resolves the source before it creates the destination entry. On Linux, do_linkat looks up oldname before filename_create(newname). create_link checked the destination first.

This PR moves the source checks into a small _resolve_link_source helper:

  • Under Posix, the helper runs before the destination check.
  • Under Windows, it runs where the checks were before, so Windows behaviour is unchanged.
  • The trailing-separator ENOTDIR check on the source stays with the lookup, so it keeps its order.

I found this with a differential fuzz that runs random sequences of os/shutil calls on the real file system and on pyfakefs, then compares the results.

Tasks

  • Unit tests added that reproduce the issue or prove feature is working
    • Added test_link_bogus_to_existing_file_posix.
    • It fails without the fix and passes with it.
    • It also passes with TEST_REAL_FS=1 on macOS.
  • Fix or feature added
  • Entry to release notes added
  • Pre-commit CI shows no errors
    • ruff check and ruff format --check are clean locally.
  • Unit tests passing
    • pyfakefs/tests: 1443 passed, 1046 skipped.
    • The link tests also pass with TEST_REAL_FS=1.

AI-assisted (Claude): I used it to help read the code and draft the test. I reviewed and ran everything myself.

Under Posix, link() looks up the source first, so os.link(missing,
existing) raises ENOENT. The fake implementation checked the destination
first and raised EEXIST.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant