Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Windows symlink handling #67766

Open
lkubb opened this issue Feb 24, 2025 · 0 comments · May be fixed by #67073
Open

[BUG] Windows symlink handling #67766

lkubb opened this issue Feb 24, 2025 · 0 comments · May be fixed by #67073
Labels
Bug broken, incorrect, or confusing behavior needs-triage

Comments

@lkubb
Copy link
Contributor

lkubb commented Feb 24, 2025

Description
Debugging the test failures for #67073 on Windows, I discovered three issues related to Windows symlink handling.

Note that I don't have a Windows minion to give proper replication instructions.

  1. Symlinks with relative targets pointing to directories create somewhat broken symlinks. This is caused by Windows (file systems?) discriminating between symlinks pointing to files and those that point to directories. The corresponding handling expects symlink targets to be specified in an absolute manner (or relative to the process' CWD, which is unintended for sure) and to exist:

is_dir = os.path.isdir(src)

Rough reproduction:

{%- set tempdir = salt["temp.dir"]() %}

target_dir:
  file.directory:
    - name: {{ tempdir | path_join("target_dir") }}

symlink:
  file.symlink:
    - name: {{ tempdir | path_join("symlink") }}
    - target: target_dir
    - require:
      - target_dir

Symlinks created in this way raised PermissionErrors during specific assertions that called .stat() on the associated pathlib.Path, such as .exists() and .is_dir():

Test runner output:

[...]
assert (base / "symlink_target_dir_overridden_by_dir").is_dir()
[...]
artifacts\salt\lib\pathlib.py:1305: in is_dir
    return S_ISDIR(self.stat().st_mode)
        self       = WindowsPath('D:/a/_temp/pytest-of-runneradmin/pytest-1/test_recurse_merge_True_True_0/target/symlink_target_dir_overridden_by_dir')
[...]
self = WindowsPath('D:/a/_temp/pytest-of-runneradmin/pytest-1/test_recurse_merge_True_True_0/target/symlink_target_dir_overridden_by_dir')

    def stat(self, *, follow_symlinks=True):
        """
        Return the result of the stat() system call on this path, like
        os.stat() does.
        """
>       return self._accessor.stat(self, follow_symlinks=follow_symlinks)
E       PermissionError: [WinError 5] Access is denied: 'D:\\a\\_temp\\pytest-of-runneradmin\\pytest-1\\test_recurse_merge_True_True_0\\target\\symlink_target_dir_overridden_by_dir'

Paths below this broken symlink still work correctly though.

  1. Similar behavior as in 1) was likely observed with file.recurse and keep_symlinks=True since the symlinks were created before the directories they pointed to (on top of relative symlinks always being broken).

  2. The roots fileserver module fails to transform symlink target paths to POSIX style paths when listing symlinks on Windows, which makes its output depend on the master's OS and thus symlink target handling unreliable.

(Not completely sure how to do this on Windows via CLI, sorry!)

{%- set root_dir = salt["config.get"]("file_roots")["base"] | first %}
{%- set test_dir = root_dir | path_join("testdir") %}

target_file:
  file.managed:
    - name: {{ test_dir | path_join("subdir", "file") }}
    - contents: 'hi'
    - makedirs: true

symlink:
  file.symlink:
    - name: {{ test_dir | path_join("symlink") }}
    - target: subdir/file
    - require:
      - target_file
$ salt-run fileserver.clear_file_list_cache
$ salt-run fileserver.update
$ salt-call cp.list_master_symlinks
local:
    ----------
    testdir/symlink:
        subdir\file

Setup
This came up in CI (based on current master branch) for Windows 2022 Server runners.

Steps to Reproduce the behavior
See above.

Expected behavior
1+2) Produce a directory symlink that works.
3) Always return POSIX style paths, otherwise the output cannot be used programmatically.

Screenshots
See above.

Versions Report
current master branch (commit 1cd6139)

@lkubb lkubb added Bug broken, incorrect, or confusing behavior needs-triage labels Feb 24, 2025
@lkubb lkubb linked a pull request Feb 24, 2025 that will close this issue
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug broken, incorrect, or confusing behavior needs-triage
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant