NTFS においてシンボリックリンクやらハードリンクやらはリパースポイントという機能で実装されています。
パスがリパースポイントかどうか判別する
from ctypes import * FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 kernel = windll.kernel32 GetFileAttributes = kernel.GetFileAttributesW GetFileAttributes.argtypes = [c_wchar_p] def islink(path): attributes = GetFileAttributes(path) return attributes != -1 and bool(attributes & FILE_ATTRIBUTE_REPARSE_POINT)
リンク先を取得する
from os.path import * from ctypes import * from ctypes.wintypes import * OPEN_EXISTING = 0x3 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 FSCTL_GET_REPARSE_POINT = 0x000900A8 MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024 IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003L IO_REPARSE_TAG_SYMLINK = 0xA000000CL class REPARSE_DATA_BUFFER(Structure): class Data(Union): class SymbolicLinkReparseBuffer(Structure): _fields_ = [ ('SubstituteNameOffset', WORD, ), ('SubstituteNameLength', WORD, ), ('PrintNameOffset', WORD, ), ('PrintNameLength', WORD, ), ('Flags', ULONG, ), ('PathBuffer', WCHAR*MAXIMUM_REPARSE_DATA_BUFFER_SIZE, ), ] class MountPointReparseBuffer(Structure): _fields_ = [ ('SubstituteNameOffset', WORD, ), ('SubstituteNameLength', WORD, ), ('PrintNameOffset', WORD, ), ('PrintNameLength', WORD, ), ('PathBuffer', WCHAR*MAXIMUM_REPARSE_DATA_BUFFER_SIZE, ), ] class GenericReparseBuffer(Structure): _fields_ = [ ('DataBuffer', BYTE*MAXIMUM_REPARSE_DATA_BUFFER_SIZE, ), ] _fields_ = [ ('SymbolicLinkReparseBuffer', SymbolicLinkReparseBuffer, ), ('MountPointReparseBuffer', MountPointReparseBuffer, ), ('GenericReparseBuffer', GenericReparseBuffer, ), ] _fields_ = [ ('ReparseTag', DWORD, ), ('ReparseDataLength', WORD, ), ('Reserved', WORD, ), ('Data', Data, ), ] _anonymous_ = ('Data', ) kernel = windll.kernel32 CreateFile = kernel.CreateFileW CloseHandle = kernel.CloseHandle DeviceIoControl = kernel.DeviceIoControl GetVolumeNameForVolumeMountPoint = kernel.GetVolumeNameForVolumeMountPointW GetVolumeNameForVolumeMountPoint.argtypes = [c_wchar_p, c_wchar_p, c_ulong, ] GetVolumeNameForVolumeMountPoint.restype = bool def GetVolumeNameForVolumeMountPoint(path): if not path.endswith(sep): path += sep volume = (c_wchar * 1024)() if kernel.GetVolumeNameForVolumeMountPointW(path, volume, len(volume)): return volume.value else: return u'' def ismount(path): return bool(GetVolumeNameForVolumeMountPoint(path)) def readlink(path): if ismount(path): return path data = REPARSE_DATA_BUFFER() handle = CreateFile(c_wchar_p(path), 0, 0, None, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, None) try: DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 0, byref(data), 1024, byref(c_ulong()), None) finally: CloseHandle(handle) if data.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT: info = data.MountPointReparseBuffer start = info.SubstituteNameOffset / sizeof(c_wchar) end = start + info.SubstituteNameLength / sizeof(c_wchar) src = info.PathBuffer[start:end] elif data.ReparseTag == IO_REPARSE_TAG_SYMLINK: info = data.SymbolicLinkReparseBuffer start = info.SubstituteNameOffset / sizeof(c_wchar) end = start + info.SubstituteNameLength / sizeof(c_wchar) src = info.PathBuffer[start:end] #info.Flags # 0x0: abs, 0x1: rel else: raise IOError(0, 'Target object is not link object.', path) if src.startswith(u'\\??\\'): src = src[4:] return normpath(src)
リンク先取得するだけで DeviceIoControl かよ……