Original post is here: eklausmeier.goip.de
I had to detect Windows junctions (similar but not identical to symbolic links) in Python. On stackoverflow.com I read the following Python code given by a user named eryksun for handling them, as os.islink()
does not work for junctions.
The routines access Windows functions CreateFileW()
, DeviceIoControl()
, CloseHandle()
in kernel32
.
[more_WP_Tag]
1from ctypes import *
2from ctypes.wintypes import *
3
4kernel32 = WinDLL('kernel32')
5LPDWORD = POINTER(DWORD)
6UCHAR = c_ubyte
7
8GetFileAttributesW = kernel32.GetFileAttributesW
9GetFileAttributesW.restype = DWORD
10GetFileAttributesW.argtypes = (LPCWSTR,) #lpFileName In
11
12INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
13FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
14
15CreateFileW = kernel32.CreateFileW
16CreateFileW.restype = HANDLE
17CreateFileW.argtypes = (LPCWSTR, #lpFileName In
18 DWORD, #dwDesiredAccess In
19 DWORD, #dwShareMode In
20 LPVOID, #lpSecurityAttributes In_opt
21 DWORD, #dwCreationDisposition In
22 DWORD, #dwFlagsAndAttributes In
23 HANDLE) #hTemplateFile In_opt
24
25CloseHandle = kernel32.CloseHandle
26CloseHandle.restype = BOOL
27CloseHandle.argtypes = (HANDLE,) #hObject In
28
29INVALID_HANDLE_VALUE = HANDLE(-1).value
30OPEN_EXISTING = 3
31FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
32FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
33
34DeviceIoControl = kernel32.DeviceIoControl
35DeviceIoControl.restype = BOOL
36DeviceIoControl.argtypes = (HANDLE, #hDevice In
37 DWORD, #dwIoControlCode In
38 LPVOID, #lpInBuffer In_opt
39 DWORD, #nInBufferSize In
40 LPVOID, #lpOutBuffer Out_opt
41 DWORD, #nOutBufferSize In
42 LPDWORD, #lpBytesReturned Out_opt
43 LPVOID) #lpOverlapped Inout_opt
44
45FSCTL_GET_REPARSE_POINT = 0x000900A8
46IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
47IO_REPARSE_TAG_SYMLINK = 0xA000000C
48MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
49
50class GENERIC_REPARSE_BUFFER(Structure):
51 _fields_ = (('DataBuffer', UCHAR * 1),)
52
53
54class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
55 _fields_ = (('SubstituteNameOffset', USHORT),
56 ('SubstituteNameLength', USHORT),
57 ('PrintNameOffset', USHORT),
58 ('PrintNameLength', USHORT),
59 ('Flags', ULONG),
60 ('PathBuffer', WCHAR * 1))
61 @property
62 def PrintName(self):
63 arrayt = WCHAR * (self.PrintNameLength // 2)
64 offset = type(self).PathBuffer.offset + self.PrintNameOffset
65 return arrayt.from_address(addressof(self) + offset).value
66
67
68class MOUNT_POINT_REPARSE_BUFFER(Structure):
69 _fields_ = (('SubstituteNameOffset', USHORT),
70 ('SubstituteNameLength', USHORT),
71 ('PrintNameOffset', USHORT),
72 ('PrintNameLength', USHORT),
73 ('PathBuffer', WCHAR * 1))
74 @property
75 def PrintName(self):
76 arrayt = WCHAR * (self.PrintNameLength // 2)
77 offset = type(self).PathBuffer.offset + self.PrintNameOffset
78 return arrayt.from_address(addressof(self) + offset).value
79
80
81class REPARSE_DATA_BUFFER(Structure):
82 class REPARSE_BUFFER(Union):
83 _fields_ = (('SymbolicLinkReparseBuffer',
84 SYMBOLIC_LINK_REPARSE_BUFFER),
85 ('MountPointReparseBuffer',
86 MOUNT_POINT_REPARSE_BUFFER),
87 ('GenericReparseBuffer',
88 GENERIC_REPARSE_BUFFER))
89 _fields_ = (('ReparseTag', ULONG),
90 ('ReparseDataLength', USHORT),
91 ('Reserved', USHORT),
92 ('ReparseBuffer', REPARSE_BUFFER))
93 _anonymous_ = ('ReparseBuffer',)
94
95
96def islink(path):
97 result = GetFileAttributesW(path)
98 if result == INVALID_FILE_ATTRIBUTES:
99 raise WinError()
100 return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
101
102def readlink(path):
103 reparse_point_handle = CreateFileW(path,
104 0,
105 0,
106 None,
107 OPEN_EXISTING,
108 FILE_FLAG_OPEN_REPARSE_POINT |
109 FILE_FLAG_BACKUP_SEMANTICS,
110 None)
111 if reparse_point_handle == INVALID_HANDLE_VALUE:
112 raise WinError()
113 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
114 n_bytes_returned = DWORD()
115 io_result = DeviceIoControl(reparse_point_handle,
116 FSCTL_GET_REPARSE_POINT,
117 None, 0,
118 target_buffer, len(target_buffer),
119 byref(n_bytes_returned),
120 None)
121 CloseHandle(reparse_point_handle)
122 if not io_result:
123 raise WinError()
124 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
125 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
126 return rdb.SymbolicLinkReparseBuffer.PrintName
127 elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
128 return rdb.MountPointReparseBuffer.PrintName
129 raise ValueError("not a link")
The user calls islink()
to detect whether the supplied path-name is a junction or not. readlink()
gives you the path to which the arguments points to. So if X
points to Y
, then readlink(X)
gives Y
.
One comment in stackoverflow.com says: This works out of the box. Indeed.