有没有办法在 Python 中创建 NTFS 连接点?我知道我可以调用该junction
实用程序,但最好不要依赖外部工具。
5 回答
从 Python 3.5CreateJunction
开始,_winapi
模块中有一个函数。
import _winapi
_winapi.CreateJunction(source, target)
我在类似的问题中回答了这个问题,所以我将在下面复制我的答案。自从写了那个答案后,我最终编写了一个仅 python(如果你可以调用一个使用 ctypes python-only 的模块)模块来创建、读取和检查可以在这个文件夹中找到的连接。希望有帮助。
此外,与使用CreateSymbolicLinkA API 的答案不同,链接的实现应该适用于支持联结的任何 Windows 版本。CreateSymbolicLinkA 仅在 Vista+ 中受支持。
回答:
或者如果你想使用 pywin32,你可以使用前面提到的方法,并阅读,使用:
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:
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()
根据您的需要调整 CreateFile 中的属性,但对于正常情况,它应该可以工作。随意改进它。
如果您使用 MOUNTPOINT 而不是 SYMBOLIC_LINK,它也应该适用于文件夹连接。
你可以检查一下
sys.getwindowsversion()[0] >= 6
如果你把它放到你要发布的东西中,因为这种形式的符号链接只在 Vista+ 上受支持。
您可以使用 python win32 API 模块,例如
import win32file
win32file.CreateSymbolicLink(srcDir, targetDir, 1)
有关详细信息,请参阅http://docs.activestate.com/activepython/2.5/pywin32/win32file__CreateSymbolicLink_meth.html
如果你不想依赖它,你总是可以使用 ctypes 并直接调用 CreateSymbolicLinl win32 API,这无论如何都是一个简单的调用
这是使用 ctypes 的示例调用
import ctypes
kdll = ctypes.windll.LoadLibrary("kernel32.dll")
kdll.CreateSymbolicLinkA("d:\testdir", "d:\testdir_link", 1)
MSDN说最低支持的客户端 Windows Vista
根据 Charles 接受的答案,这里改进了(和跨平台)函数版本(Python 2.7 和 3.5+)。
- islink() 现在还可以检测 Windows 下的文件符号链接(就像 POSIX 等效项一样)
- parse_reparse_buffer() 和 readlink() 现在实际检测正确解码路径所需的重解析点类型(NTFS 连接点、符号链接或通用)
- readlink() 不再因 NTFS 连接或目录符号链接上的访问被拒绝而失败(除非您真的没有读取属性的权限)
import os
import struct
import sys
if sys.platform == "win32":
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
# These are defined in win32\lib\winnt.py, but with wrong values
IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 # Junction
IO_REPARSE_TAG_SYMLINK = 0xA000000C
def islink(path):
"""
Cross-platform islink implementation.
Supports Windows NT symbolic links and reparse points.
"""
if sys.platform != "win32" or sys.getwindowsversion()[0] < 6:
return os.path.islink(path)
return bool(os.path.exists(path) and GetFileAttributes(path) &
FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT)
def parse_reparse_buffer(buf):
""" 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;
"""
# See https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/ns-ntifs-_reparse_data_buffer
data = {'tag': struct.unpack('<I', buf[:4])[0],
'data_length': struct.unpack('<H', buf[4:6])[0],
'reserved': struct.unpack('<H', buf[6:8])[0]}
buf = buf[8:]
if data['tag'] in (IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK):
keys = ['substitute_name_offset',
'substitute_name_length',
'print_name_offset',
'print_name_length']
if data['tag'] == IO_REPARSE_TAG_SYMLINK:
keys.append('flags')
# Parsing
for k in keys:
if k == 'flags':
fmt, sz = '<I', 4
else:
fmt, sz = '<H', 2
data[k] = struct.unpack(fmt, buf[:sz])[0]
buf = buf[sz:]
# Using the offset and lengths grabbed, we'll set the buffer.
data['buffer'] = buf
return data
def readlink(path):
"""
Cross-platform implenentation of readlink.
Supports Windows NT symbolic links and reparse points.
"""
if sys.platform != "win32":
return os.readlink(path)
# This wouldn't return true if the file didn't exist
if not islink(path):
# Mimic POSIX error
raise OSError(22, 'Invalid argument', path)
# Open the file correctly depending on the string type.
if type(path) is type(u''):
createfilefn = CreateFileW
else:
createfilefn = CreateFile
# FILE_FLAG_OPEN_REPARSE_POINT alone is not enough if 'path'
# is a symbolic link to a directory or a NTFS junction.
# We need to set FILE_FLAG_BACKUP_SEMANTICS as well.
# See https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea
handle = createfilefn(path, GENERIC_READ, 0, None, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0)
# MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 = (16 * 1024)
buf = 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 is bigger than 0)
if len(buf) < 9:
return type(path)()
# Parse and return our result.
result = parse_reparse_buffer(buf)
if result['tag'] in (IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK):
offset = result['substitute_name_offset']
ending = offset + result['substitute_name_length']
rpath = result['buffer'][offset:ending].decode('UTF-16-LE')
else:
rpath = result['buffer']
if len(rpath) > 4 and rpath[0:4] == '\\??\\':
rpath = rpath[4:]
return rpath
您不想依赖外部工具但不介意依赖特定环境?我认为您可以放心地假设,如果您正在运行的是 NTFS,则联结实用程序可能会在那里。
但是,如果您的意思是您不想调用外部程序,我发现ctypes的东西是无价的。它允许您直接从 Python 调用 Windows DLL。而且我很确定它现在在标准的 Python 版本中。
您只需要弄清楚CreateJunction()
(或任何 Windows 调用它的)API 调用在哪个 Windows DLL 中,并设置参数和调用。祝你好运,微软似乎不太支持它。您可以反汇编 SysInternalsjunction
程序或linkd
其他工具之一,以了解它们是如何做到的。
我,我很懒,我只是junction
作为外部进程调用:-)