Symlinks on windows?

后端 未结 10 1333
轻奢々
轻奢々 2020-11-29 01:09

Does anyone know of a way to make/read symbolic links across versions of win32 from Python? Ideally there should be a minimum amount of platform specific code, as I need my

相关标签:
10条回答
  • 2020-11-29 01:33

    As mentioned in another answer, using subprocess.call is likely the best option for windows. However calling 'mklink' directly may result in:

    [WinError 2] The system cannot find the file specified

    On Windows Server 2016, I was able to get the following to work for files:

    import subprocess
    subprocess.call(['cmd', '/c', 'mklink', '<path_for_symlink>', '<path_for_file>'])
    

    Change the switches above as per mklink docs.

    0 讨论(0)
  • 2020-11-29 01:36

    Juntalis's code does not handle Unicode so I modified it to use ctypes and also simplified it using struct. I've also consulted the code from 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 讨论(0)
  • 2020-11-29 01:37

    NTFS file system has junction points, I think you may use them instead, You can use python win32 API module for that e.g.

    import win32file
    
    win32file.CreateSymbolicLink(fileSrc, fileTarget, 1)
    

    If you do not want to rely on win32API module, you can always use ctypes and directly call CreateSymbolicLink win32 API e.g.

    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) says Minimum supported client is Windows Vista

    In addition: This also works with directories (indicate that with the third argument). With unicode support it looks like this:

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

    also see Create NTFS junction point in Python

    0 讨论(0)
  • 2020-11-29 01:37

    python ntfslink extension

    Or if you want to use pywin32, you can use the previously stated method, and to read, 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()
    

    Adjust the attributes in the CreateFile to your needs, but for a normal situation, it should work. Feel free to improve on it.

    It should also work for folder junctions if you use MOUNTPOINT instead of SYMBOLIC_LINK.

    You may way to check that

    sys.getwindowsversion()[0] >= 6
    

    if you put this into something you're releasing, since this form of symbolic link is only supported on Vista+.

    0 讨论(0)
  • 2020-11-29 01:47

    os.symlink works on Python 3.3 using Windows 8.1 with an NTFS filesystem.

    0 讨论(0)
  • 2020-11-29 01:47

    I put the following into 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
    
    0 讨论(0)
提交回复
热议问题