2015-01-15 12 views
5

Próbowałem znaleźć sposób, aby uzyskać zdolność do zwrócenia prawdziwego abspath dowiązania symbolicznego w systemie Windows, w Pythonie 2.7. (Nie mogę uaktualnić do wersji 3.x, ponieważ większość DCC, takich jak Maya/3ds max, nie używa tej wersji Pythona)Masz problemy z implementacją funkcji readlink()

Sprawdziłem narzędzia sid0 ntfs (których funkcja islink() działa, ale readlink (funkcja) zawsze zwraca pusty ciąg unicode dla mnie z jakiegoś powodu) i libs ntfs juntalis (który niestety, nie mogłem dostać się do pracy), wraz z pomocnym skryptu ktoś pisał:

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 

jednak w większości przypadków różne rozwiązania, które próbowałem, dały mi:

[Błąd 126] Nie można znaleźć określonego modułu

chociaż jestem w stanie importować ctypes i robić rzeczy takie jak importującego cdll:

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

Jestem całkiem nowy w tej części Python i ctypes w ogóle, więc wszelkie wskazówki na temat radzenia sobie z linkami symbolicznymi tutaj Byłbym bardzo doceniony!

Odpowiedz

2

ERROR_MOD_NOT_FOUND (126) jest prawdopodobnie spowodowany windll.CloseHandle(hfile), który próbuje załadować "closehandle.dll". Brakuje kernel32.

Oto alternatywna implementacja, która obsługuje połączenia, a także dowiązania symboliczne.

ctypes definicje

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',) 

funkcje

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) 

przykładowe

>>> 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' 
+0

To działało OOTB jak urok! Nie lubię tego robić, ale dla kogoś zaczynającego się od ctypes (i ogólnie API Windowsa), czy poleciłbyś innym źródłom innym niż MSDN dla początkującego, żeby niby oblał mi głowę jak pracować z ctypami? Zrozumienie niektórych dokumentów jest dla mnie trochę trudne ... W każdym razie dzięki za pomoc! – sonictk

+0

Dokładnie wymodelowałem swoją odpowiedź na CPython's ['win_readlink'] (https://hg.python.org/cpython/file/ab2c023a9432/Modules/posixmodule.c#l7396), ale rozszerzony do obsługi złączy. – eryksun

+0

Czy masz przykład czegoś w tutorialu [ctypes] (https://docs.python.org/2/library/ctypes.html#ctypes-tutorial) lub dokumentach MSDN, które są trudne?Ogólnie, do programowania C, czy widziałeś [comp.lang.c FAQ] (http://c-faq.com)? – eryksun

1

Jest to zaimplementowane w Tcl jako file readlink, a wdrożenie tego może być warte przeczytania, ponieważ wydaje się, że jest kilka różnic. Funkcja wywołuje NativeReadReparse, aby odczytać REPARSE_DATA_BUFFER, ale wykorzystuje różne flagi do funkcji CreateFile. Również rozmiar bufora jest inny i myślę, że rozmiar struktur w wywołaniach Win32 jest często używany do wykrywania wersji interfejsu API, więc prawdopodobnie warto zadbać o ustawienie rozmiaru na poprawną wartość (lub ewentualnie taką samą wartość implementacja Tcl).

Wystarczy, aby pokazać, co mam na myśli wsparcie Tcl na to:

C:\>dir 
Directory of C:\ 

22/09/2014 14:29 <JUNCTION>  Code [C:\src] 
... 

C:\>tclsh 
% file readlink Code 
C:\src 
Powiązane problemy