Skip to content

Commit

Permalink
util: make symlink() work on older Windows versions
Browse files Browse the repository at this point in the history
  • Loading branch information
piscisaureus committed Aug 2, 2018
1 parent e5b7d31 commit 788b079
Showing 1 changed file with 32 additions and 15 deletions.
47 changes: 32 additions & 15 deletions tools/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,38 @@ def remove_and_symlink(target, name, target_is_dir=False):

def symlink(target, name, target_is_dir=False):
if os.name == "nt":
import ctypes
CreateSymbolicLinkW = ctypes.windll.kernel32.CreateSymbolicLinkW
CreateSymbolicLinkW.restype = ctypes.c_ubyte
CreateSymbolicLinkW.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p,
ctypes.c_uint32)

# Replace forward slashes by backward slashes.
# Strangely it seems that this is only necessary for symlinks to files.
# Forward slashes don't cause any issues when the target is a directory.
target = target.replace("/", "\\")
flags = 0x02 # SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
if (target_is_dir):
flags |= 0x01 # SYMBOLIC_LINK_FLAG_DIRECTORY
if not CreateSymbolicLinkW(name, target, flags):
raise ctypes.WinError()
from ctypes import WinDLL, WinError, GetLastError
from ctypes.wintypes import BOOLEAN, DWORD, LPCWSTR

kernel32 = WinDLL('kernel32', use_last_error=False)
CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
CreateSymbolicLinkW.restype = BOOLEAN
CreateSymbolicLinkW.argtypes = (LPCWSTR, LPCWSTR, DWORD)

# File-type symlinks can only use backslashes as separators.
target = os.path.normpath(target)

# If the symlink points at a directory, it needs to have the appropriate
# flag set, otherwise the link will be created but it won't work.
if target_is_dir:
type_flag = 0x01 # SYMBOLIC_LINK_FLAG_DIRECTORY
else:
type_flag = 0

# Before Windows 10, creating symlinks requires admin privileges.
# As of Win 10, there is a flag that allows anyone to create them.
# Initially, try to use this flag.
unpriv_flag = 0x02 # SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
r = CreateSymbolicLinkW(name, target, type_flag | unpriv_flag)

# If it failed with ERROR_INVALID_PARAMETER, try again without the
# 'allow unprivileged create' flag.
if not r and GetLastError() == 87: # ERROR_INVALID_PARAMETER
r = CreateSymbolicLinkW(name, target, type_flag)

# Throw if unsuccessful even after the second attempt.
if not r:
raise WinError()
else:
os.symlink(target, name)

Expand Down

0 comments on commit 788b079

Please sign in to comment.