1 | import os
|
---|
2 | import unittest
|
---|
3 | import platform
|
---|
4 |
|
---|
5 | from test.test_support import TESTFN
|
---|
6 |
|
---|
7 | def 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 |
|
---|
23 | def 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 |
|
---|
29 | def _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 |
|
---|
91 | symlink = os.symlink if hasattr(os, 'symlink') else (
|
---|
92 | _symlink_win32 if platform.system() == 'Windows' else None
|
---|
93 | )
|
---|
94 |
|
---|
95 | def 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)
|
---|