>

Python 2.7에서 Windows의 심볼릭 링크의 진정한 절대 경로를 반환 할 수있는 능력을 얻는 방법을 찾으려고 노력했습니다. (Maya/3ds max와 같은 대부분의 DCC는 해당 버전의 Python을 사용하지 않기 때문에 3.x로 업그레이드 할 수 없습니다)

나는 sid0 ntfs 유틸리티 (islink () 함수가 작동하지만 readlink () 함수는 항상 어떤 이유로 든 빈 유니 코드 문자열을 반환)를 보았습니다)와 juntalis ntfs libs를 보았습니다 (불행히도, 나는 다른 사람이 게시 한 유용한 스크립트와 함께 작동하지 않습니다.

import os, ctypes, struct
from ctypes import windll, wintypes
FSCTL_GET_REPARSE_POINT = 0x900a8
FILE_ATTRIBUTE_READONLY      = 0x0001
FILE_ATTRIBUTE_HIDDEN        = 0x0002
FILE_ATTRIBUTE_DIRECTORY     = 0x0010
FILE_ATTRIBUTE_NORMAL        = 0x0080
FILE_ATTRIBUTE_REPARSE_POINT = 0x0400

GENERIC_READ  = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 3
FILE_READ_ATTRIBUTES = 0x80
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
FILE_FLAG_OPEN_REPARSE_POINT = 2097152
FILE_FLAG_BACKUP_SEMANTICS = 33554432
# FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTI
FILE_FLAG_REPARSE_BACKUP = 35651584

GetFileAttributes = windll.kernel32.GetFileAttributesW
_CreateFileW = windll.kernel32.CreateFileW
_DevIoCtl = windll.kernel32.DeviceIoControl
_DevIoCtl.argtypes = [
    wintypes.HANDLE, #HANDLE hDevice
    wintypes.DWORD, #DWORD dwIoControlCode
    wintypes.LPVOID, #LPVOID lpInBuffer
    wintypes.DWORD, #DWORD nInBufferSize
    wintypes.LPVOID, #LPVOID lpOutBuffer
    wintypes.DWORD, #DWORD nOutBufferSize
    ctypes.POINTER(wintypes.DWORD), #LPDWORD lpBytesReturned
    wintypes.LPVOID] #LPOVERLAPPED lpOverlapped
_DevIoCtl.restype = wintypes.BOOL

def islink(path):
    # assert os.path.isdir(path), path
    if GetFileAttributes(path) & FILE_ATTRIBUTE_REPARSE_POINT:
        return True
    else:
        return False

def DeviceIoControl(hDevice, ioControlCode, input, output):
    # DeviceIoControl Function
    # http://msdn.microsoft.com/en-us/library/aa363216(v=vs.85).aspx
    if input:
        input_size = len(input)
    else:
        input_size = 0
    if isinstance(output, int):
        output = ctypes.create_string_buffer(output)
    output_size = len(output)
    assert isinstance(output, ctypes.Array)
    bytesReturned = wintypes.DWORD()
    status = _DevIoCtl(hDevice, ioControlCode, input,
                       input_size, output, output_size, bytesReturned, None)
    print "status(%d)" % status
    if status != 0:
        return output[:bytesReturned.value]
    else:
        return None

def CreateFile(path, access, sharemode, creation, flags):
    return _CreateFileW(path, access, sharemode, None, creation, flags, None)

SymbolicLinkReparseFormat = "LHHHHHHL"
SymbolicLinkReparseSize = struct.calcsize(SymbolicLinkReparseFormat);
def readlink(path):
    """ Windows readlink implementation. """
    # This wouldn't return true if the file didn't exist, as far as I know.
    assert islink(path)
    # assert type(path) == unicode
    # Open the file correctly depending on the string type.
    hfile = CreateFile(path, GENERIC_READ, 0, OPEN_EXISTING,
                       FILE_FLAG_REPARSE_BACKUP)
    # MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 = (16*1024)
    buffer = DeviceIoControl(hfile, FSCTL_GET_REPARSE_POINT, None, 16384)
    windll.CloseHandle(hfile)
    # Minimum possible length (assuming length of the target is bigger than 0)
    if not buffer or len(buffer) < 9:
        return None
    # Only handle SymbolicLinkReparseBuffer
    (tag, dataLength, reserver, SubstituteNameOffset, SubstituteNameLength,
     PrintNameOffset, PrintNameLength,
     Flags) = struct.unpack(SymbolicLinkReparseFormat,
                            buffer[:SymbolicLinkReparseSize])
    print tag, dataLength, reserver, SubstituteNameOffset, SubstituteNameLength
    start = SubstituteNameOffset + SymbolicLinkReparseSize
    actualPath = buffer[start : start + SubstituteNameLength].decode("utf-16")
    # This utf-16 string is null terminated
    index = actualPath.find(u"\0")
    assert index > 0
    if index > 0:
        actualPath = actualPath[:index]
    if actualPath.startswith(u"?\\"):
        return actualPath[2:]
    else:
        return actualPath

그러나 내가 시도한 다양한 솔루션은 대부분 나에게 다음을 제공합니다.

[오류 126] 지정된 모듈을 찾을 수 없습니다

ctypes를 가져오고 cdll 가져 오기와 같은 작업을 수행 할 수는 있지만 :

libc = cdll.msvcrt
libc.printf
<_FuncPtr object at 0x0000000002A9F388>

일반적으로 파이썬과 ctypes의이 부분에 익숙하지 않기 때문에 여기에서 심볼릭 링크를 다루는 것에 대한 조언은 대단히 감사하겠습니다!


  • 답변 # 1

    ERROR_MOD_NOT_FOUND  (126)은 windll.CloseHandle(hfile) 로 인한 것 같습니다 "closehandle.dll"를로드하려고합니다. kernel32 가 없습니다 .

    다음은 정션 및 심볼릭 링크를 처리하는 대체 구현입니다.

    타입 정의

    import ctypes
    from ctypes import wintypes
    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    FILE_READ_ATTRIBUTES = 0x0080
    OPEN_EXISTING = 3
    FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
    FILE_FLAG_BACKUP_SEMANTICS   = 0x02000000
    FILE_ATTRIBUTE_REPARSE_POINT = 0x0400
    IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
    IO_REPARSE_TAG_SYMLINK     = 0xA000000C
    FSCTL_GET_REPARSE_POINT    = 0x000900A8
    MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
    LPDWORD = ctypes.POINTER(wintypes.DWORD)
    LPWIN32_FIND_DATA = ctypes.POINTER(wintypes.WIN32_FIND_DATAW)
    INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
    def IsReparseTagNameSurrogate(tag):
        return bool(tag & 0x20000000)
    def _check_invalid_handle(result, func, args):
        if result == INVALID_HANDLE_VALUE:
            raise ctypes.WinError(ctypes.get_last_error())
        return args
    def _check_bool(result, func, args):
        if not result:
            raise ctypes.WinError(ctypes.get_last_error())
        return args
    kernel32.FindFirstFileW.errcheck = _check_invalid_handle
    kernel32.FindFirstFileW.restype = wintypes.HANDLE
    kernel32.FindFirstFileW.argtypes = (
        wintypes.LPCWSTR,  # _In_  lpFileName
        LPWIN32_FIND_DATA) # _Out_ lpFindFileData
    kernel32.FindClose.argtypes = (
        wintypes.HANDLE,) # _Inout_ hFindFile
    kernel32.CreateFileW.errcheck = _check_invalid_handle
    kernel32.CreateFileW.restype = wintypes.HANDLE
    kernel32.CreateFileW.argtypes = (
        wintypes.LPCWSTR, # _In_     lpFileName
        wintypes.DWORD,   # _In_     dwDesiredAccess
        wintypes.DWORD,   # _In_     dwShareMode
        wintypes.LPVOID,  # _In_opt_ lpSecurityAttributes
        wintypes.DWORD,   # _In_     dwCreationDisposition
        wintypes.DWORD,   # _In_     dwFlagsAndAttributes
        wintypes.HANDLE)  # _In_opt_ hTemplateFile 
    kernel32.CloseHandle.argtypes = (
        wintypes.HANDLE,) # _In_ hObject
    kernel32.DeviceIoControl.errcheck = _check_bool
    kernel32.DeviceIoControl.argtypes = (
        wintypes.HANDLE,  # _In_        hDevice
        wintypes.DWORD,   # _In_        dwIoControlCode
        wintypes.LPVOID,  # _In_opt_    lpInBuffer
        wintypes.DWORD,   # _In_        nInBufferSize
        wintypes.LPVOID,  # _Out_opt_   lpOutBuffer
        wintypes.DWORD,   # _In_        nOutBufferSize
        LPDWORD,          # _Out_opt_   lpBytesReturned
        wintypes.LPVOID)  # _Inout_opt_ lpOverlapped 
    class REPARSE_DATA_BUFFER(ctypes.Structure):
        class ReparseData(ctypes.Union):
            class LinkData(ctypes.Structure):
                _fields_ = (('SubstituteNameOffset', wintypes.USHORT),
                            ('SubstituteNameLength', wintypes.USHORT),
                            ('PrintNameOffset',      wintypes.USHORT),
                            ('PrintNameLength',      wintypes.USHORT))
                @property
                def PrintName(self):
                    dt = wintypes.WCHAR * (self.PrintNameLength //
                                           ctypes.sizeof(wintypes.WCHAR))
                    name = dt.from_address(ctypes.addressof(self.PathBuffer) +
                                           self.PrintNameOffset).value
                    if name.startswith(r'\??'):
                        name = r'\\?' + name[3:] # NT => Windows
                    return name
            class SymbolicLinkData(LinkData):
                _fields_ = (('Flags',      wintypes.ULONG),
                            ('PathBuffer', wintypes.BYTE * 0))
            class MountPointData(LinkData):
                _fields_ = (('PathBuffer', wintypes.BYTE * 0),)
            class GenericData(ctypes.Structure):
                _fields_ = (('DataBuffer', wintypes.BYTE * 0),)
            _fields_ = (('SymbolicLinkReparseBuffer', SymbolicLinkData),
                        ('MountPointReparseBuffer',   MountPointData),
                        ('GenericReparseBuffer',      GenericData))
        _fields_ = (('ReparseTag',        wintypes.ULONG),
                    ('ReparseDataLength', wintypes.USHORT),
                    ('Reserved',          wintypes.USHORT),
                    ('ReparseData',       ReparseData))
        _anonymous_ = ('ReparseData',)
    
    

    기능

    def islink(path):
        data = wintypes.WIN32_FIND_DATAW()
        kernel32.FindClose(kernel32.FindFirstFileW(path, ctypes.byref(data)))
        if not data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT:
            return False
        return IsReparseTagNameSurrogate(data.dwReserved0)
    def readlink(path):
        n = wintypes.DWORD()
        buf = (wintypes.BYTE * MAXIMUM_REPARSE_DATA_BUFFER_SIZE)()
        flags = FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS
        handle = kernel32.CreateFileW(path, FILE_READ_ATTRIBUTES, 0, None,
                    OPEN_EXISTING, flags, None)
        try:
            kernel32.DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 0,
                buf, ctypes.sizeof(buf), ctypes.byref(n), None)
        finally:
            kernel32.CloseHandle(handle)
        rb = REPARSE_DATA_BUFFER.from_buffer(buf)
        tag = rb.ReparseTag
        if tag == IO_REPARSE_TAG_SYMLINK:
            return rb.SymbolicLinkReparseBuffer.PrintName
        if tag == IO_REPARSE_TAG_MOUNT_POINT:
            return rb.MountPointReparseBuffer.PrintName
        if not IsReparseTagNameSurrogate(tag):
            raise ValueError("not a link")
        raise ValueError("unsupported reparse tag: %d" % tag)
    
    

    >>> sys.version
    '2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) 
    [MSC v.1500 64 bit (AMD64)]'
    >>> os.system(r'mklink /d spam C:\Windows')
    symbolic link created for spam <<===>> C:\Windows
    0
    >>> islink('spam')
    True
    >>> readlink('spam')
    u'C:\\Windows'
    >>> islink('C:/Documents and Settings') # junction
    True
    >>> readlink('C:/Documents and Settings')
    u'C:\\Users'
    >>> islink('C:/Users/All Users') # symlinkd
    True
    >>> readlink('C:/Users/All Users')
    u'C:\\ProgramData'
    
    

  • 답변 # 2

    이것은 Tcl에서 file readlink 로 구현됩니다.  그리고 약간의 차이가있는 것처럼 보이기 때문에 이것을 읽을 가치가 있습니다. 와이즈 비즈  함수 호출 WinReadLinkDirectory   NativeReadReparse 를 읽으려면  그러나 이것은 REPARSE_DATA_BUFFER 에 다른 플래그를 사용합니다.  기능. 또한 버퍼 크기가 다르다고 생각하며 Win32 호출의 구조 크기는 종종 사용되는 API 버전을 감지하는 데 사용되므로 크기를 올바른 값 (또는 가능한 동일한 값)으로 설정하는 것이 좋습니다 Tcl 구현))

    Tcl 지원에 대한 의미를 보여 드리기 위해 :

    CreateFile
    
    
    C:\>dir Directory of C:\ 22/09/2014 14:29 <JUNCTION> Code [C:\src] ... C:\>tclsh % file readlink Code C:\src

관련 자료

  • 이전 .net - WPF MVVM 동적 컨트롤 생성
  • 다음 c# - setCurrentCellAddressCore에 재진입 전화를 피하는 방법은 무엇입니까?