2009-09-19 4 views
30

Czy ktoś wie o sposobie tworzenia/odczytu dowiązań symbolicznych między wersjami Win32 z Pythona? Najlepiej byłoby, gdyby minimalna ilość kodu była specyficzna dla platformy, ponieważ potrzebuję, aby moja aplikacja była platformą krzyżową.Dowiązania symboliczne w oknach?

+3

Funkcją, której potrzebuję, jest możliwość utworzenia katalogu zawierającego odsyłacze do plików z różnych miejsc w systemie plików, a następnie umożliwienie kodowi Python otworzenia tych plików tak, jakby znajdowały się w tym katalogu. –

+0

Dlaczego potrzebujesz tej funkcji? –

Odpowiedz

10

Problem jest, jak wyjaśniono np. here, że obsługa Windows dla funkcji dowiązań symbolicznych jest różna w różnych wersjach systemu Windows, więc np. w Vista (z dużą ilością pracy) można uzyskać więcej funkcji niż w XP lub 2000 (nic AFAIK w innych wersjach Win32). Możesz też zamiast tego mieć skróty, które oczywiście mają swój własny zestaw ograniczeń i nie są "tak naprawdę" odpowiednikiem dowiązań symbolicznych Uniksa. Musisz więc dokładnie określić, jakie funkcje potrzebujesz, ile z nich chcesz poświęcić na ołtarzu operacji "cross-win32" itd. - W ten sposób możemy opracować, jak wdrożyć kompromis, który wybrałeś w kategoriach z ctypes lub win32all połączeń ... to w pewnym sensie najmniejszy z nich.

34

system plików NTFS ma punkty połączenia, myślę, że można ich używać zamiast tego można użyć modułu API dla Pythona win32 że np

import win32file 

win32file.CreateSymbolicLink(fileSrc, fileTarget, 1) 

Jeśli nie chcesz polegać na module Win32API, zawsze można użyć ctypes i bezpośrednio zadzwonić CreateSymbolicLink API Win32 np

import ctypes 

kdll = ctypes.windll.LoadLibrary("kernel32.dll") 

kdll.CreateSymbolicLinkA("d:\\test.txt", "d:\\test_link.txt", 0) 

MSDN (http://msdn.microsoft.com/en-us/library/aa363866(VS.85).aspx) mówi Minimalna obsługiwana klient jest Windows Vista

Ponadto: To działa również z katalogów (wskazują, że z trzeciego argumentu). Z obsługą Unicode wygląda to tak:

kdll.CreateSymbolicLinkW(UR"D:\testdirLink", UR"D:\testdir", 1) 

również zobaczyć Create NTFS junction point in Python

+11

Nie powinieneś używać 'Windows API' 'A, zamiast tego użyj' ..W' (Unicode) - za każdym razem - nawet w takich przykładach jak to. – sorin

+0

Dodałem przykład linku do katalogu, używając unicode –

+1

Nazwy parametrów przedstawione dla 'win32file.CreateSymbolicLink' są trochę mylące. Dla tych, którzy się zastanawiają, pierwsza to nazwa linku do stworzenia, druga to ścieżka, do której ma prowadzić link. – brianmearns

6

umieścić następujące do lib/site-packages/sitecustomize.py

import os 

__CSL = None 
def symlink(source, link_name): 
    '''symlink(source, link_name) 
     Creates a symbolic link pointing to source named link_name''' 
    global __CSL 
    if __CSL is None: 
     import ctypes 
     csl = ctypes.windll.kernel32.CreateSymbolicLinkW 
     csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) 
     csl.restype = ctypes.c_ubyte 
     __CSL = csl 
    flags = 0 
    if source is not None and os.path.isdir(source): 
     flags = 1 
    if __CSL(link_name, source, flags) == 0: 
     raise ctypes.WinError() 

os.symlink = symlink 
12

python ntfslink extension

Or jeśli chcesz użyć pywin32, możesz użyć poprzednio podanej metody i przeczytać:

from win32file import * 
from winioctlcon import FSCTL_GET_REPARSE_POINT 

__all__ = ['islink', 'readlink'] 

# Win32file doesn't seem to have this attribute. 
FILE_ATTRIBUTE_REPARSE_POINT = 1024 
# To make things easier. 
REPARSE_FOLDER = (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT) 

# For the parse_reparse_buffer function 
SYMBOLIC_LINK = 'symbolic' 
MOUNTPOINT = 'mountpoint' 
GENERIC = 'generic' 

def islink(fpath): 
    """ Windows islink implementation. """ 
    if GetFileAttributes(fpath) & REPARSE_FOLDER == REPARSE_FOLDER: 
     return True 
    return False 


def parse_reparse_buffer(original, reparse_type=SYMBOLIC_LINK): 
    """ Implementing the below in Python: 

    typedef struct _REPARSE_DATA_BUFFER { 
     ULONG ReparseTag; 
     USHORT ReparseDataLength; 
     USHORT Reserved; 
     union { 
      struct { 
       USHORT SubstituteNameOffset; 
       USHORT SubstituteNameLength; 
       USHORT PrintNameOffset; 
       USHORT PrintNameLength; 
       ULONG Flags; 
       WCHAR PathBuffer[1]; 
      } SymbolicLinkReparseBuffer; 
      struct { 
       USHORT SubstituteNameOffset; 
       USHORT SubstituteNameLength; 
       USHORT PrintNameOffset; 
       USHORT PrintNameLength; 
       WCHAR PathBuffer[1]; 
      } MountPointReparseBuffer; 
      struct { 
       UCHAR DataBuffer[1]; 
      } GenericReparseBuffer; 
     } DUMMYUNIONNAME; 
    } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; 

    """ 
    # Size of our data types 
    SZULONG = 4 # sizeof(ULONG) 
    SZUSHORT = 2 # sizeof(USHORT) 

    # Our structure. 
    # Probably a better way to iterate a dictionary in a particular order, 
    # but I was in a hurry, unfortunately, so I used pkeys. 
    buffer = { 
     'tag' : SZULONG, 
     'data_length' : SZUSHORT, 
     'reserved' : SZUSHORT, 
     SYMBOLIC_LINK : { 
      'substitute_name_offset' : SZUSHORT, 
      'substitute_name_length' : SZUSHORT, 
      'print_name_offset' : SZUSHORT, 
      'print_name_length' : SZUSHORT, 
      'flags' : SZULONG, 
      'buffer' : u'', 
      'pkeys' : [ 
       'substitute_name_offset', 
       'substitute_name_length', 
       'print_name_offset', 
       'print_name_length', 
       'flags', 
      ] 
     }, 
     MOUNTPOINT : { 
      'substitute_name_offset' : SZUSHORT, 
      'substitute_name_length' : SZUSHORT, 
      'print_name_offset' : SZUSHORT, 
      'print_name_length' : SZUSHORT, 
      'buffer' : u'', 
      'pkeys' : [ 
       'substitute_name_offset', 
       'substitute_name_length', 
       'print_name_offset', 
       'print_name_length', 
      ] 
     }, 
     GENERIC : { 
      'pkeys' : [], 
      'buffer': '' 
     } 
    } 

    # Header stuff 
    buffer['tag'] = original[:SZULONG] 
    buffer['data_length'] = original[SZULONG:SZUSHORT] 
    buffer['reserved'] = original[SZULONG+SZUSHORT:SZUSHORT] 
    original = original[8:] 

    # Parsing 
    k = reparse_type 
    for c in buffer[k]['pkeys']: 
     if type(buffer[k][c]) == int: 
      sz = buffer[k][c] 
      bytes = original[:sz] 
      buffer[k][c] = 0 
      for b in bytes: 
       n = ord(b) 
       if n: 
        buffer[k][c] += n 
      original = original[sz:] 

    # Using the offset and length's grabbed, we'll set the buffer. 
    buffer[k]['buffer'] = original 
    return buffer 

def readlink(fpath): 
    """ Windows readlink implementation. """ 
    # This wouldn't return true if the file didn't exist, as far as I know. 
    if not islink(fpath): 
     return None 

    # Open the file correctly depending on the string type. 
    handle = CreateFileW(fpath, GENERIC_READ, 0, None, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0) \ 
       if type(fpath) == unicode else \ 
      CreateFile(fpath, GENERIC_READ, 0, None, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0) 

    # MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 = (16*1024) 
    buffer = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 16*1024) 
    # Above will return an ugly string (byte array), so we'll need to parse it. 

    # But first, we'll close the handle to our file so we're not locking it anymore. 
    CloseHandle(handle) 

    # Minimum possible length (assuming that the length of the target is bigger than 0) 
    if len(buffer) < 9: 
     return None 
    # Parse and return our result. 
    result = parse_reparse_buffer(buffer) 
    offset = result[SYMBOLIC_LINK]['substitute_name_offset'] 
    ending = offset + result[SYMBOLIC_LINK]['substitute_name_length'] 
    rpath = result[SYMBOLIC_LINK]['buffer'][offset:ending].replace('\x00','') 
    if len(rpath) > 4 and rpath[0:4] == '\\??\\': 
     rpath = rpath[4:] 
    return rpath 

def realpath(fpath): 
    from os import path 
    while islink(fpath): 
     rpath = readlink(fpath) 
     if not path.isabs(rpath): 
      rpath = path.abspath(path.join(path.dirname(fpath), rpath)) 
     fpath = rpath 
    return fpath 


def example(): 
    from os import system, unlink 
    system('cmd.exe /c echo Hello World > test.txt') 
    system('mklink test-link.txt test.txt') 
    print 'IsLink: %s' % islink('test-link.txt') 
    print 'ReadLink: %s' % readlink('test-link.txt') 
    print 'RealPath: %s' % realpath('test-link.txt') 
    unlink('test-link.txt') 
    unlink('test.txt') 

if __name__=='__main__': 
    example() 

Dostosuj atrybuty w pliku CreateFile do swoich potrzeb, ale w normalnej sytuacji powinien działać. Możesz go poprawić.

Należy także dążyć do folderu skrzyżowaniach jeśli używasz montowania zamiast SYMBOLIC_LINK.

Możesz sposób sprawdzić, że

sys.getwindowsversion()[0] >= 6 

jeśli umieścisz to w coś ty zwolnienia, ponieważ ta forma dowiązania symbolicznego jest obsługiwane tylko na Vista +. Kod

+0

Wprowadziłem do tego zmianę, ale zostało ono odrzucone, mimo że w rzeczywistości jest obecny błąd. W islink() zamaskowana wartość musi być porównywana z maską, aby uniknąć fałszywych alarmów (zero = false/nonzero = true skrót byłby tylko z maską jednobitową, np. 1024). W przeciwnym razie normalne katalogi są również oznaczone jako "linki". Wiersz powinien brzmieć "if GetFileAttributes (fpath) i REPARSE_FOLDER == REPARSE_FOLDER:" – MartyMacGyver

+0

Jeśli odrzuciłem to, musiałem zrobić to przez pomyłkę i jest mi przykro z tego powodu. (Szczerze mówiąc, nie jestem do końca pewny, gdzie chciałbym zobaczyć prośby o edycję, biorąc pod uwagę, że nigdy nie otrzymałem żadnego z nich tutaj). Poszedłem jednak naprzód i zaktualizowałem Twoją poprawkę, więc dziękuję ci za to. –

+0

O nie, * ty * nie odrzuciłeś niczego ... zrobili to dwaj inni redaktorzy, co było dziwne, ponieważ moja sugerowana edycja po prostu poprawiła błąd. To powiedziawszy, jeszcze raz dziękuję za twój post - to było bardzo pomocne i warte zagłębienia się! :-) – MartyMacGyver

1

Juntalis męska nie obsługuje Unicode, więc modyfikować go używać ctypes a także uproszczone go przy użyciu struct. Ja również konsultowany kod z Using a struct as a function argument with the python ctypes module

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) 
    CloseHandle(hfile) 

    # Minimum possible length (assuming length of the target is bigger than 0) 
    if not buffer or len(buffer) < 9: 
     return None 

    # Parse and return our result. 
    # typedef struct _REPARSE_DATA_BUFFER { 
    # ULONG ReparseTag; 
    # USHORT ReparseDataLength; 
    # USHORT Reserved; 
    # union { 
    #  struct { 
    #   USHORT SubstituteNameOffset; 
    #   USHORT SubstituteNameLength; 
    #   USHORT PrintNameOffset; 
    #   USHORT PrintNameLength; 
    #   ULONG Flags; 
    #   WCHAR PathBuffer[1]; 
    #  } SymbolicLinkReparseBuffer; 
    #  struct { 
    #   USHORT SubstituteNameOffset; 
    #   USHORT SubstituteNameLength; 
    #   USHORT PrintNameOffset; 
    #   USHORT PrintNameLength; 
    #   WCHAR PathBuffer[1]; 
    #  } MountPointReparseBuffer; 
    #  struct { 
    #   UCHAR DataBuffer[1]; 
    #  } GenericReparseBuffer; 
    # } DUMMYUNIONNAME; 
    # } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; 

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

Tak, nie zdawałem sobie sprawy w momencie pisania tego, że punkty ponownej analizy były przechowywane jako ciągi znaków Unicode. (Głupia pomyłka, aby być pewnym) Lepiej wykorzystaj powyższy kod lub moduł, który niedawno znalazłem [na githubie] (https://github.com/sid0/ntfs). –

4

os.symlink działa na Python 3.3 w systemie Windows 8.1 z systemem plików NTFS.

-1

tu jest link zawierający wszystkie sposoby kernel32.dll

http://www.geoffchappell.com/studies/windows/win32/kernel32/api/

użyłem CreateHardLinkA na Windows XP SP3, to działa!

ctypes import jeśli os.path.exists (link_file) os.remove (link_file)

dll = ctypes.windll.LoadLibrary("kernel32.dll") 
dll.CreateHardLinkA(link_file, _log_filename, 0) 
+0

Dowiązania symboliczne, tak jak zostały wprowadzone w systemie Vista, są implementowane jako punkty ponownej analizy, a tym samym bliżej dowiązań symbolicznych w POSIX. Hardlinki nie mają z tym nic wspólnego. – 0xC0000022L

4

Używając polecenia mklink w podprocesu utworzenia połączenia.

from subprocess import call 
call(['mklink', 'LINK', 'TARGET'], shell=True) 
+0

Miałem problemy z innymi rozwiązaniami tutaj - ale to działa dobrze. – Zitrax