python - hard - Symlinks en windows?
windows create link to directory (8)
El código de Juntalis no maneja Unicode, así que lo modifiqué para usar ctypes y también lo simplifiqué usando struct. También consulté el código de Uso de una estructura como argumento de función con el módulo python ctypes
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
¿Alguien sabe de una forma de hacer / leer enlaces simbólicos en las versiones de Win32 de Python? Idealmente, debería haber una cantidad mínima de código específico de la plataforma, ya que necesito que mi aplicación sea multiplataforma.
El problema es, como se explica here , que el propio soporte de Windows para la funcionalidad de los enlaces simbólicos varía según las versiones de Windows, de modo que, por ejemplo, en Vista (con mucho trabajo) puede obtener más funcionalidades que en XP o 2000 (nada AFAIK en otro versiones win32). O podría tener atajos en su lugar, que por supuesto tienen su propio conjunto de limitaciones y no son "realmente" equivalentes a los enlaces simbólicos de Unix. Entonces, debe especificar exactamente qué funcionalidades necesita, cuánto de las que está dispuesto a sacrificar en el altar de la operación cross-win32, etc. - ENTONCES, podemos encontrar la forma de implementar el compromiso que ha elegido en términos de ctypes
o llamadas de win32all
... eso es lo de menos, en cierto sentido.
El sistema de archivos NTFS tiene puntos de unión, creo que puedes usarlos en su lugar, puedes usar el módulo API de Python Win32 para eso, por ejemplo
import win32file
win32file.CreateSymbolicLink(fileSrc, fileTarget, 1)
Si no quiere confiar en el módulo win32API, siempre puede usar ctypes
y llamar directamente a la API CreateSymbolicLink
win32, por ej.
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 ) dice que el cliente mínimo compatible es Windows Vista
Además : Esto también funciona con directorios (indíquelo con el tercer argumento). Con compatibilidad Unicode se ve así:
kdll.CreateSymbolicLinkW(UR"D:/testdirLink", UR"D:/testdir", 1)
también vea Crear punto de unión NTFS en Python
Puse lo siguiente en 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
Usando el comando mklink en el subproceso crear enlace.
from subprocess import call
call([''mklink'', ''LINK'', ''TARGET''], shell=True)
aquí está el enlace que contiene todos los métodos de kernel32.dll
http://www.geoffchappell.com/studies/windows/win32/kernel32/api/
Utilicé CreateHardLinkA en Windows XP Sp3, ¡funcionó!
import ctypes si os.path.exists (link_file): os.remove (link_file)
dll = ctypes.windll.LoadLibrary("kernel32.dll")
dll.CreateHardLinkA(link_file, _log_filename, 0)
os.symlink funciona en Python 3.3 usando Windows 8.1 con un sistema de archivos NTFS.
O si desea utilizar pywin32, puede usar el método indicado anteriormente, y para leer, use:
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()
Ajuste los atributos en CreateFile a sus necesidades, pero para una situación normal, debería funcionar. Siéntase libre de mejorarlo.
También debería funcionar para uniones de carpetas si usa MOUNTPOINT en lugar de SYMBOLIC_LINK.
Puede manera de verificar que
sys.getwindowsversion()[0] >= 6
si pones esto en algo que estás liberando, ya que esta forma de enlace simbólico solo se admite en Vista +.