From 2b42d288c08bdfd0fc3402fa118d91a1aebdb655 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 1 Oct 2018 14:59:48 -0700 Subject: [PATCH] Windows: Add support for creating symlinks as an unprivileged user See https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/ for announcement of new flag. This change follow the same pattern as what was done in "go": https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0 Change-Id: If1e99fefdd3f787598e695731019c34b9bfcd1c2 --- platform_utils_win32.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/platform_utils_win32.py b/platform_utils_win32.py index fe76b3d6..a6431216 100644 --- a/platform_utils_win32.py +++ b/platform_utils_win32.py @@ -41,6 +41,8 @@ CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In # Symbolic link creation flags SYMBOLIC_LINK_FLAG_FILE = 0x00 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01 +# symlink support for CreateSymbolicLink() starting with Windows 10 (1703, v10.0.14972) +SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02 GetFileAttributesW = kernel32.GetFileAttributesW GetFileAttributesW.restype = DWORD @@ -147,15 +149,21 @@ def _create_symlink(source, link_name, dwFlags): # On success, the function returns "1". # On error, the function returns some random value (e.g. 1280). # The best bet seems to use "GetLastError" and check for error/success. - CreateSymbolicLinkW(link_name, source, dwFlags) + CreateSymbolicLinkW(link_name, source, dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) code = get_last_error() if code != ERROR_SUCCESS: - error_desc = FormatError(code).strip() - if code == ERROR_PRIVILEGE_NOT_HELD: - raise OSError(errno.EPERM, error_desc, link_name) - _raise_winerror( - code, - 'Error creating symbolic link \"%s\"'.format(link_name)) + # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0 + # "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972). + # retry without it." + CreateSymbolicLinkW(link_name, source, dwFlags) + code = get_last_error() + if code != ERROR_SUCCESS: + error_desc = FormatError(code).strip() + if code == ERROR_PRIVILEGE_NOT_HELD: + raise OSError(errno.EPERM, error_desc, link_name) + _raise_winerror( + code, + 'Error creating symbolic link \"%s\"'.format(link_name)) def islink(path):