|  | 
|  | 1 | +""" | 
|  | 2 | +Low-level OS functionality wrappers used by pathlib. | 
|  | 3 | +""" | 
|  | 4 | + | 
|  | 5 | +from errno import EBADF, EOPNOTSUPP, ETXTBSY, EXDEV | 
|  | 6 | +import os | 
|  | 7 | +import sys | 
|  | 8 | +try: | 
|  | 9 | +    import fcntl | 
|  | 10 | +except ImportError: | 
|  | 11 | +    fcntl = None | 
|  | 12 | +try: | 
|  | 13 | +    import posix | 
|  | 14 | +except ImportError: | 
|  | 15 | +    posix = None | 
|  | 16 | +try: | 
|  | 17 | +    import _winapi | 
|  | 18 | +except ImportError: | 
|  | 19 | +    _winapi = None | 
|  | 20 | + | 
|  | 21 | + | 
|  | 22 | +def get_copy_blocksize(infd): | 
|  | 23 | +    """Determine blocksize for fastcopying on Linux. | 
|  | 24 | +    Hopefully the whole file will be copied in a single call. | 
|  | 25 | +    The copying itself should be performed in a loop 'till EOF is | 
|  | 26 | +    reached (0 return) so a blocksize smaller or bigger than the actual | 
|  | 27 | +    file size should not make any difference, also in case the file | 
|  | 28 | +    content changes while being copied. | 
|  | 29 | +    """ | 
|  | 30 | +    try: | 
|  | 31 | +        blocksize = max(os.fstat(infd).st_size, 2 ** 23)  # min 8 MiB | 
|  | 32 | +    except OSError: | 
|  | 33 | +        blocksize = 2 ** 27  # 128 MiB | 
|  | 34 | +    # On 32-bit architectures truncate to 1 GiB to avoid OverflowError, | 
|  | 35 | +    # see gh-82500. | 
|  | 36 | +    if sys.maxsize < 2 ** 32: | 
|  | 37 | +        blocksize = min(blocksize, 2 ** 30) | 
|  | 38 | +    return blocksize | 
|  | 39 | + | 
|  | 40 | + | 
|  | 41 | +if fcntl and hasattr(fcntl, 'FICLONE'): | 
|  | 42 | +    def clonefd(source_fd, target_fd): | 
|  | 43 | +        """ | 
|  | 44 | +        Perform a lightweight copy of two files, where the data blocks are | 
|  | 45 | +        copied only when modified. This is known as Copy on Write (CoW), | 
|  | 46 | +        instantaneous copy or reflink. | 
|  | 47 | +        """ | 
|  | 48 | +        fcntl.ioctl(target_fd, fcntl.FICLONE, source_fd) | 
|  | 49 | +else: | 
|  | 50 | +    clonefd = None | 
|  | 51 | + | 
|  | 52 | + | 
|  | 53 | +if posix and hasattr(posix, '_fcopyfile'): | 
|  | 54 | +    def copyfd(source_fd, target_fd): | 
|  | 55 | +        """ | 
|  | 56 | +        Copy a regular file content using high-performance fcopyfile(3) | 
|  | 57 | +        syscall (macOS). | 
|  | 58 | +        """ | 
|  | 59 | +        posix._fcopyfile(source_fd, target_fd, posix._COPYFILE_DATA) | 
|  | 60 | +elif hasattr(os, 'copy_file_range'): | 
|  | 61 | +    def copyfd(source_fd, target_fd): | 
|  | 62 | +        """ | 
|  | 63 | +        Copy data from one regular mmap-like fd to another by using a | 
|  | 64 | +        high-performance copy_file_range(2) syscall that gives filesystems | 
|  | 65 | +        an opportunity to implement the use of reflinks or server-side | 
|  | 66 | +        copy. | 
|  | 67 | +        This should work on Linux >= 4.5 only. | 
|  | 68 | +        """ | 
|  | 69 | +        blocksize = get_copy_blocksize(source_fd) | 
|  | 70 | +        offset = 0 | 
|  | 71 | +        while True: | 
|  | 72 | +            sent = os.copy_file_range(source_fd, target_fd, blocksize, | 
|  | 73 | +                                      offset_dst=offset) | 
|  | 74 | +            if sent == 0: | 
|  | 75 | +                break  # EOF | 
|  | 76 | +            offset += sent | 
|  | 77 | +elif hasattr(os, 'sendfile'): | 
|  | 78 | +    def copyfd(source_fd, target_fd): | 
|  | 79 | +        """Copy data from one regular mmap-like fd to another by using | 
|  | 80 | +        high-performance sendfile(2) syscall. | 
|  | 81 | +        This should work on Linux >= 2.6.33 only. | 
|  | 82 | +        """ | 
|  | 83 | +        blocksize = get_copy_blocksize(source_fd) | 
|  | 84 | +        offset = 0 | 
|  | 85 | +        while True: | 
|  | 86 | +            sent = os.sendfile(target_fd, source_fd, offset, blocksize) | 
|  | 87 | +            if sent == 0: | 
|  | 88 | +                break  # EOF | 
|  | 89 | +            offset += sent | 
|  | 90 | +else: | 
|  | 91 | +    copyfd = None | 
|  | 92 | + | 
|  | 93 | + | 
|  | 94 | +if _winapi and hasattr(_winapi, 'CopyFile2'): | 
|  | 95 | +    def copyfile(source, target): | 
|  | 96 | +        """ | 
|  | 97 | +        Copy from one file to another using CopyFile2 (Windows only). | 
|  | 98 | +        """ | 
|  | 99 | +        _winapi.CopyFile2(source, target, 0) | 
|  | 100 | +else: | 
|  | 101 | +    copyfile = None | 
|  | 102 | + | 
|  | 103 | + | 
|  | 104 | +def copyfileobj(source_f, target_f): | 
|  | 105 | +    """ | 
|  | 106 | +    Copy data from file-like object source_f to file-like object target_f. | 
|  | 107 | +    """ | 
|  | 108 | +    try: | 
|  | 109 | +        source_fd = source_f.fileno() | 
|  | 110 | +        target_fd = target_f.fileno() | 
|  | 111 | +    except Exception: | 
|  | 112 | +        pass  # Fall through to generic code. | 
|  | 113 | +    else: | 
|  | 114 | +        try: | 
|  | 115 | +            # Use OS copy-on-write where available. | 
|  | 116 | +            if clonefd: | 
|  | 117 | +                try: | 
|  | 118 | +                    clonefd(source_fd, target_fd) | 
|  | 119 | +                    return | 
|  | 120 | +                except OSError as err: | 
|  | 121 | +                    if err.errno not in (EBADF, EOPNOTSUPP, ETXTBSY, EXDEV): | 
|  | 122 | +                        raise err | 
|  | 123 | + | 
|  | 124 | +            # Use OS copy where available. | 
|  | 125 | +            if copyfd: | 
|  | 126 | +                copyfd(source_fd, target_fd) | 
|  | 127 | +                return | 
|  | 128 | +        except OSError as err: | 
|  | 129 | +            # Produce more useful error messages. | 
|  | 130 | +            err.filename = source_f.name | 
|  | 131 | +            err.filename2 = target_f.name | 
|  | 132 | +            raise err | 
|  | 133 | + | 
|  | 134 | +    # Last resort: copy with fileobj read() and write(). | 
|  | 135 | +    read_source = source_f.read | 
|  | 136 | +    write_target = target_f.write | 
|  | 137 | +    while buf := read_source(1024 * 1024): | 
|  | 138 | +        write_target(buf) | 
0 commit comments