1import os
2import unittest
3import platform
4
5from test.test_support import TESTFN
6
7def can_symlink():
8    # cache the result in can_symlink.prev_val
9    prev_val = getattr(can_symlink, 'prev_val', None)
10    if prev_val is not None:
11        return prev_val
12    symlink_path = TESTFN + "can_symlink"
13    try:
14        symlink(TESTFN, symlink_path)
15        can = True
16    except (OSError, NotImplementedError, AttributeError):
17        can = False
18    else:
19        os.remove(symlink_path)
20    can_symlink.prev_val = can
21    return can
22
23def skip_unless_symlink(test):
24    """Skip decorator for tests that require functional symlink"""
25    ok = can_symlink()
26    msg = "Requires functional symlink implementation"
27    return test if ok else unittest.skip(msg)(test)
28
29def _symlink_win32(target, link, target_is_directory=False):
30    """
31    Ctypes symlink implementation since Python doesn't support
32    symlinks in windows yet. Borrowed from jaraco.windows project.
33    """
34    import ctypes.wintypes
35    CreateSymbolicLink = ctypes.windll.kernel32.CreateSymbolicLinkW
36    CreateSymbolicLink.argtypes = (
37        ctypes.wintypes.LPWSTR,
38        ctypes.wintypes.LPWSTR,
39        ctypes.wintypes.DWORD,
40        )
41    CreateSymbolicLink.restype = ctypes.wintypes.BOOLEAN
42
43    def format_system_message(errno):
44        """
45        Call FormatMessage with a system error number to retrieve
46        the descriptive error message.
47        """
48        # first some flags used by FormatMessageW
49        ALLOCATE_BUFFER = 0x100
50        ARGUMENT_ARRAY = 0x2000
51        FROM_HMODULE = 0x800
52        FROM_STRING = 0x400
53        FROM_SYSTEM = 0x1000
54        IGNORE_INSERTS = 0x200
55
56        # Let FormatMessageW allocate the buffer (we'll free it below)
57        # Also, let it know we want a system error message.
58        flags = ALLOCATE_BUFFER | FROM_SYSTEM
59        source = None
60        message_id = errno
61        language_id = 0
62        result_buffer = ctypes.wintypes.LPWSTR()
63        buffer_size = 0
64        arguments = None
65        bytes = ctypes.windll.kernel32.FormatMessageW(
66            flags,
67            source,
68            message_id,
69            language_id,
70            ctypes.byref(result_buffer),
71            buffer_size,
72            arguments,
73            )
74        # note the following will cause an infinite loop if GetLastError
75        #  repeatedly returns an error that cannot be formatted, although
76        #  this should not happen.
77        handle_nonzero_success(bytes)
78        message = result_buffer.value
79        ctypes.windll.kernel32.LocalFree(result_buffer)
80        return message
81
82    def handle_nonzero_success(result):
83        if result == 0:
84            value = ctypes.windll.kernel32.GetLastError()
85            strerror = format_system_message(value)
86            raise WindowsError(value, strerror)
87
88    target_is_directory = target_is_directory or os.path.isdir(target)
89    handle_nonzero_success(CreateSymbolicLink(link, target, target_is_directory))
90
91symlink = os.symlink if hasattr(os, 'symlink') else (
92    _symlink_win32 if platform.system() == 'Windows' else None
93)
94
95def remove_symlink(name):
96    # On Windows, to remove a directory symlink, one must use rmdir
97    try:
98        os.rmdir(name)
99    except OSError:
100        os.remove(name)
101