101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
|
import os
|
||
|
import unittest
|
||
|
import platform
|
||
|
|
||
|
from test.test_support import TESTFN
|
||
|
|
||
|
def can_symlink():
|
||
|
# cache the result in can_symlink.prev_val
|
||
|
prev_val = getattr(can_symlink, 'prev_val', None)
|
||
|
if prev_val is not None:
|
||
|
return prev_val
|
||
|
symlink_path = TESTFN + "can_symlink"
|
||
|
try:
|
||
|
symlink(TESTFN, symlink_path)
|
||
|
can = True
|
||
|
except (OSError, NotImplementedError, AttributeError):
|
||
|
can = False
|
||
|
else:
|
||
|
os.remove(symlink_path)
|
||
|
can_symlink.prev_val = can
|
||
|
return can
|
||
|
|
||
|
def skip_unless_symlink(test):
|
||
|
"""Skip decorator for tests that require functional symlink"""
|
||
|
ok = can_symlink()
|
||
|
msg = "Requires functional symlink implementation"
|
||
|
return test if ok else unittest.skip(msg)(test)
|
||
|
|
||
|
def _symlink_win32(target, link, target_is_directory=False):
|
||
|
"""
|
||
|
Ctypes symlink implementation since Python doesn't support
|
||
|
symlinks in windows yet. Borrowed from jaraco.windows project.
|
||
|
"""
|
||
|
import ctypes.wintypes
|
||
|
CreateSymbolicLink = ctypes.windll.kernel32.CreateSymbolicLinkW
|
||
|
CreateSymbolicLink.argtypes = (
|
||
|
ctypes.wintypes.LPWSTR,
|
||
|
ctypes.wintypes.LPWSTR,
|
||
|
ctypes.wintypes.DWORD,
|
||
|
)
|
||
|
CreateSymbolicLink.restype = ctypes.wintypes.BOOLEAN
|
||
|
|
||
|
def format_system_message(errno):
|
||
|
"""
|
||
|
Call FormatMessage with a system error number to retrieve
|
||
|
the descriptive error message.
|
||
|
"""
|
||
|
# first some flags used by FormatMessageW
|
||
|
ALLOCATE_BUFFER = 0x100
|
||
|
ARGUMENT_ARRAY = 0x2000
|
||
|
FROM_HMODULE = 0x800
|
||
|
FROM_STRING = 0x400
|
||
|
FROM_SYSTEM = 0x1000
|
||
|
IGNORE_INSERTS = 0x200
|
||
|
|
||
|
# Let FormatMessageW allocate the buffer (we'll free it below)
|
||
|
# Also, let it know we want a system error message.
|
||
|
flags = ALLOCATE_BUFFER | FROM_SYSTEM
|
||
|
source = None
|
||
|
message_id = errno
|
||
|
language_id = 0
|
||
|
result_buffer = ctypes.wintypes.LPWSTR()
|
||
|
buffer_size = 0
|
||
|
arguments = None
|
||
|
bytes = ctypes.windll.kernel32.FormatMessageW(
|
||
|
flags,
|
||
|
source,
|
||
|
message_id,
|
||
|
language_id,
|
||
|
ctypes.byref(result_buffer),
|
||
|
buffer_size,
|
||
|
arguments,
|
||
|
)
|
||
|
# note the following will cause an infinite loop if GetLastError
|
||
|
# repeatedly returns an error that cannot be formatted, although
|
||
|
# this should not happen.
|
||
|
handle_nonzero_success(bytes)
|
||
|
message = result_buffer.value
|
||
|
ctypes.windll.kernel32.LocalFree(result_buffer)
|
||
|
return message
|
||
|
|
||
|
def handle_nonzero_success(result):
|
||
|
if result == 0:
|
||
|
value = ctypes.windll.kernel32.GetLastError()
|
||
|
strerror = format_system_message(value)
|
||
|
raise WindowsError(value, strerror)
|
||
|
|
||
|
target_is_directory = target_is_directory or os.path.isdir(target)
|
||
|
handle_nonzero_success(CreateSymbolicLink(link, target, target_is_directory))
|
||
|
|
||
|
symlink = os.symlink if hasattr(os, 'symlink') else (
|
||
|
_symlink_win32 if platform.system() == 'Windows' else None
|
||
|
)
|
||
|
|
||
|
def remove_symlink(name):
|
||
|
# On Windows, to remove a directory symlink, one must use rmdir
|
||
|
try:
|
||
|
os.rmdir(name)
|
||
|
except OSError:
|
||
|
os.remove(name)
|