【问题标题】:Create NTFS junction point in Python在 Python 中创建 NTFS 连接点
【发布时间】:2010-11-11 17:03:54
【问题描述】:

有没有办法在 Python 中创建 NTFS 连接点?我知道我可以调用junction 实用程序,但最好不要依赖外部工具。

【问题讨论】:

    标签: python windows ntfs junction


    【解决方案1】:

    根据 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
    

    【讨论】:

      【解决方案2】:

      从 Python 3.5 开始,_winapi 模块中有一个函数 CreateJunction

      import _winapi
      _winapi.CreateJunction(source, target)
      

      【讨论】:

      【解决方案3】:

      我在similar question 中回答了这个问题,所以我将把我的回答复制到下面。自从写了那个答案后,我最终编写了一个仅 python(如果您可以调用使用 ctypes python-only 的模块)模块来创建、读取和检查可在this folder 中找到的联结。希望对您有所帮助。

      此外,与使用 CreateSymbolicLinkA API 的答案不同,链接实现应该适用于支持联结的任何 Windows 版本。 CreateSymbolicLinkA 仅在 Vista+ 中受支持。

      答案:

      python ntfslink extension

      或者如果你想使用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+ 上受支持。

      【讨论】:

      • 请注意,ntfslink 扩展目前在 Python3 下被破坏
      • 在下面查看我对 Python >= 3.5 的回答
      【解决方案4】:

      您可以使用 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

      【讨论】:

      • 我认为联结是从 Win2K 开始的,但没有得到 MS 的官方(或很好)支持,因为关于如何做的文档很少。新的符号链接看起来好多了,特别是因为您可以对文件执行它们并且(我认为)它们现在可以跨网络。
      • 连接不是符号链接的子集。联结仅适用于目录。这个答案是不正确的,它为文件创建了一个符号链接(仅适用于 Vista 及更高版本),而不是目录连接(适用于 Windows 2000 中的 NTFS)及更高版本。不幸的是,在 Python 中没有真正简单的方法可以做到这一点。
      • 对 Mike McQuaid 的评论投反对票。我正在寻找连接点,即指向目录的硬链接。
      • @Mike McQuaid,根据 MSDN CreateSymboliLink 带有标志 SYMBOLIC_LINK_FLAG_DIRECTORY,这不就像目录的连接点吗?
      • @Kim Gräsman,CreateSymboliLink 带标志 SYMBOLIC_LINK_FLAG_DIRECTORY,你试过了吗?
      【解决方案5】:

      您不想依赖外部工具但不介意依赖特定环境?我认为您可以放心地假设,如果您正在运行的是 NTFS,那么联结实用程序可能会在那里。

      但是,如果您的意思是您不想调用外部程序,我发现ctypes 的东西非常宝贵。它允许您直接从 Python 调用 Windows DLL。而且我很确定它现在在标准 Python 版本中。

      您只需确定CreateJunction()(或任何Windows 调用它的)API 调用在哪个Windows DLL 中,并设置参数和调用。祝你好运,微软似乎不太支持它。您可以反汇编 SysInternals junction 程序或 linkd 或其他工具之一,以了解它们是如何做到的。

      我,我很懒,我只是将 junction 作为外部进程调用 :-)

      【讨论】:

      • ctypes 从 2.5 开始包含在 Python 中。
      • Vista 和 Win7 上不存在 junction 命令。它已被 mklink 取代。
      • 它作为 Sysinternals 工具 Junction 存在。
      • @Charles,一揽子断言很少是个好主意。您可以使用DeviceIoControl 创建联结,传递SET_REPARSE_POINT
      • 对不起毯子。我不是说你做不到,我的意思是 Windows API 没有提供一个函数调用来在一条指令中创建它......
      猜你喜欢
      • 2011-05-01
      • 1970-01-01
      • 2016-07-03
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-08
      • 2016-04-12
      相关资源
      最近更新 更多