【问题标题】:Having trouble implementing a readlink() function在实现 readlink() 函数时遇到问题
【发布时间】:2015-03-14 09:32:45
【问题描述】:

我一直试图找出一种方法来获得某种能够在 Python 2.7 下返回 Windows 中符号链接的真正抽象路径的能力。 (我无法升级到 3.x,因为 Maya/3ds max 等大多数 DCC 不使用该版本的 Python)

我查看了 sid0 ntfs utils(其 islink() 函数有效,但 readlink() 函数由于某种原因总是为我返回一个空的 unicode 字符串),以及 juntalis ntfs 库(不幸的是,我不能开始工作),以及某人发布的有用脚本:

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>

总的来说,我对 Python 和 ctypes 的这一部分还很陌生,因此非常感谢任何有关在此处处理符号链接的指针!

【问题讨论】:

    标签: python ctypes symlink junction readlink


    【解决方案1】:

    ERROR_MOD_NOT_FOUND (126) 可能是由于windll.CloseHandle(hfile),它试图加载“closehandle.dll”。它不见了kernel32

    这是处理联结和符号链接的替代实现。

    ctypes 定义

    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'
    

    【讨论】:

    • 这对 OOTB 很有帮助!我不喜欢这样做,但是对于开始使用 ctypes(以及一般的 Windows API)的人,你会推荐除 MSDN 之外的任何其他资源,让初学者了解如何使用 ctypes?我发现有些文档有点难以理解……无论如何,感谢您的帮助!
    • 我的答案非常接近 CPython 的 win_readlink,但扩展到支持联结。
    • 您在ctypes tutorial 或 MSDN 文档中是否有一个很难的示例?对于一般的 C 编程,你见过comp.lang.c FAQ吗?
    • 您好,感谢您的资源!实际上我确实做了一些 ctypes 的东西(只是编写一个简单的 .c lib 并通过 ctypes 访问它的方法),但我以前从未听说过 Structure 或 Union 类(我在 C/C++ 方面没有那么丰富的经验(弄清楚发生了什么。再次,非常感谢! :D
    • 太棒了!我一直在寻找这样的功能几个月!您的解决方案是最好的!
    【解决方案2】:

    这是在 Tcl 中以file readlink 实现的,它的实现可能值得一读,因为似乎存在一些差异。 WinReadLinkDirectory 函数调用 NativeReadReparse 来读取 REPARSE_DATA_BUFFER 但这使用与 CreateFile 函数不同的标志。此外,我认为缓冲区大小不同,Win32 调用中的结构大小通常用于检测所用 API 的版本,因此可能需要注意将大小设置为正确的值(或者可能与Tcl 实现)。

    只是为了说明我所说的 Tcl 对此的支持是什么意思:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-07
      • 1970-01-01
      • 2012-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多