From 1a72c0190af0cf6706be9e1813ece18266844184 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 14:31:52 +0200 Subject: [PATCH 001/111] have shutil.copyfileobj use sendfile() if possible --- Lib/shutil.py | 57 ++++++++++++++++++++++++++++++++++++++++- Lib/test/test_shutil.py | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 3c02776a406551..42e7fae713e1f2 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -10,6 +10,7 @@ import fnmatch import collections import errno +import io try: import zlib @@ -42,6 +43,9 @@ except ImportError: getgrnam = None +_HAS_SENDFILE = hasattr(os, "sendfile") +COPY_BUFSIZE = 16 * 1024 + __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", "copytree", "move", "rmtree", "Error", "SpecialFileError", "ExecError", "make_archive", "get_archive_formats", @@ -72,9 +76,60 @@ class RegistryError(Exception): """Raised when a registry operation with the archiving and unpacking registries fails""" +class _GiveupOnSendfile(Exception): + """Raised when os.sendfile() cannot be used""" + + +def _copyfileobj_sendfile(fsrc, fdst): + """Copy data from one file object to another one by using + zero-copy sendfile() method (faster). + """ + try: + infd = fsrc.fileno() + outfd = fdst.fileno() + except (AttributeError, io.UnsupportedOperation) as err: + raise _GiveupOnSendfile(err) # not a regular file + + try: + blocksize = os.fstat(infd).st_size + except OSError: + blocksize = COPY_BUFSIZE + else: + if blocksize <= 0: + blocksize = COPY_BUFSIZE + + try: + offset = fsrc.tell() + except (AttributeError, io.UnsupportedOperation) as err: + offset = 0 -def copyfileobj(fsrc, fdst, length=16*1024): + total_sent = 0 + while True: + try: + sent = os.sendfile(outfd, infd, offset, blocksize) + except OSError as err: + if total_sent == 0: + # We can get here for different reasons, the main + # one being a fd is not a regular mmap(2)-like + # fd, in which case we'll fall back on using plain + # read()/write() copy. + raise _GiveupOnSendfile(err) + else: + raise err from None + else: + if sent == 0: + break # EOF + offset += sent + total_sent += sent + +def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): """copy data from file-like object fsrc to file-like object fdst""" + if _HAS_SENDFILE: + try: + return _copyfileobj_sendfile(fsrc, fdst) + except _GiveupOnSendfile: + pass + while 1: buf = fsrc.read(length) if not buf: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 2cb2f14643e1b3..1f990536223ea6 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -12,6 +12,8 @@ import functools import pathlib import subprocess +import random +import string from shutil import (make_archive, register_archive_format, unregister_archive_format, get_archive_formats, Error, unpack_archive, @@ -1829,6 +1831,59 @@ def test_move_dir_caseinsensitive(self): finally: os.rmdir(dst_dir) + +@unittest.skipIf(not hasattr(os, "sendfile"), 'needs os.sendfile()') +class TestCopyFileObjSendfile(unittest.TestCase): + FILESIZE = (10 * 1024 * 1024) # 10 MiB + BUFSIZE = 8192 + FILEDATA = b"" + + @classmethod + def setUpClass(cls): + def chunks(total, step): + assert total >= step + while total > step: + yield step + total -= step + if total: + yield total + + chunk = b"".join([random.choice(string.ascii_letters).encode() + for i in range(cls.BUFSIZE)]) + with open(TESTFN, 'wb') as f: + for csize in chunks(cls.FILESIZE, cls.BUFSIZE): + f.write(chunk) + with open(TESTFN, 'rb') as f: + cls.FILEDATA = f.read() + assert len(cls.FILEDATA) == cls.FILESIZE + + @classmethod + def tearDownClass(cls): + support.unlink(TESTFN) + + def tearDown(self): + support.unlink(TESTFN2) + + def get_files(self): + src = open(TESTFN, "rb") + self.addCleanup(src.close) + dst = open(TESTFN2, "wb") + self.addCleanup(dst.close) + return src, dst + + def test_regular_copy(self): + src, dst = self.get_files() + shutil.copyfileobj(src, dst) + with open(TESTFN2, "rb") as f: + self.assertEqual(f.read(), self.FILEDATA) + + def test_unhandled_exception(self): + src, dst = self.get_files() + with unittest.mock.patch('os.sendfile', + side_effect=ZeroDivisionError): + self.assertRaises(ZeroDivisionError, shutil.copyfileobj, src, dst) + + class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): """Check if get_terminal_size() returns a meaningful value. From 77c4bfae6e33718d4fcb27a055bfd5e1f82a1a56 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 14:34:15 +0200 Subject: [PATCH 002/111] refactoring: use ctx manager --- Lib/test/test_shutil.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 1f990536223ea6..ead520b0f0e70c 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -14,6 +14,7 @@ import subprocess import random import string +import contextlib from shutil import (make_archive, register_archive_format, unregister_archive_format, get_archive_formats, Error, unpack_archive, @@ -1864,24 +1865,24 @@ def tearDownClass(cls): def tearDown(self): support.unlink(TESTFN2) + @contextlib.contextmanager def get_files(self): - src = open(TESTFN, "rb") - self.addCleanup(src.close) - dst = open(TESTFN2, "wb") - self.addCleanup(dst.close) - return src, dst + with open(TESTFN, "rb") as src: + with open(TESTFN2, "wb") as dst: + yield (src, dst) def test_regular_copy(self): - src, dst = self.get_files() - shutil.copyfileobj(src, dst) + with self.get_files() as (src, dst): + shutil.copyfileobj(src, dst) with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) def test_unhandled_exception(self): - src, dst = self.get_files() with unittest.mock.patch('os.sendfile', side_effect=ZeroDivisionError): - self.assertRaises(ZeroDivisionError, shutil.copyfileobj, src, dst) + with self.get_files() as (src, dst): + self.assertRaises(ZeroDivisionError, + shutil.copyfileobj, src, dst) class TermsizeTests(unittest.TestCase): From 2afa04ac3a42955fb674156d000d543ece032e18 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 14:40:10 +0200 Subject: [PATCH 003/111] add test with non-regular file obj --- Lib/test/test_shutil.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index ead520b0f0e70c..2a8356080982a6 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -15,6 +15,7 @@ import random import string import contextlib +import io from shutil import (make_archive, register_archive_format, unregister_archive_format, get_archive_formats, Error, unpack_archive, @@ -1877,6 +1878,13 @@ def test_regular_copy(self): with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) + def test_non_regular_file(self): + with io.BytesIO(self.FILEDATA) as src: + with open(TESTFN2, "wb") as dst: + shutil.copyfileobj(src, dst) + with open(TESTFN2, "rb") as f: + self.assertEqual(f.read(), self.FILEDATA) + def test_unhandled_exception(self): with unittest.mock.patch('os.sendfile', side_effect=ZeroDivisionError): From 542cd17739348dc5f01a45628c8055c7792e7034 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 14:44:53 +0200 Subject: [PATCH 004/111] emulate case where file size can't be determined --- Lib/test/test_shutil.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 2a8356080982a6..37776435de372f 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1892,6 +1892,12 @@ def test_unhandled_exception(self): self.assertRaises(ZeroDivisionError, shutil.copyfileobj, src, dst) + def test_cant_get_size(self): + with unittest.mock.patch('os.fstat', side_effect=OSError) as m: + with self.get_files() as (src, dst): + shutil.copyfileobj(src, dst) + assert m.called + class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): From 3520c6c4a8b86f0bad21189223eb33ac3ee8e868 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 15:09:33 +0200 Subject: [PATCH 005/111] reference _copyfileobj_sendfile directly --- Lib/test/test_shutil.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 37776435de372f..1112e965f9a75c 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -21,7 +21,7 @@ get_archive_formats, Error, unpack_archive, register_unpack_format, RegistryError, unregister_unpack_format, get_unpack_formats, - SameFileError) + SameFileError, _GiveupOnSendfile) import tarfile import zipfile @@ -1874,14 +1874,17 @@ def get_files(self): def test_regular_copy(self): with self.get_files() as (src, dst): - shutil.copyfileobj(src, dst) + shutil._copyfileobj_sendfile(src, dst) with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) def test_non_regular_file(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: + with self.assertRaises(_GiveupOnSendfile): + shutil._copyfileobj_sendfile(src, dst) shutil.copyfileobj(src, dst) + with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) @@ -1895,7 +1898,7 @@ def test_unhandled_exception(self): def test_cant_get_size(self): with unittest.mock.patch('os.fstat', side_effect=OSError) as m: with self.get_files() as (src, dst): - shutil.copyfileobj(src, dst) + shutil._copyfileobj_sendfile(src, dst) assert m.called From 050a7222fe5ea76efe5e32389ac6f599a17c4c83 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 15:27:09 +0200 Subject: [PATCH 006/111] add test for offset() at certain position --- Lib/test/test_shutil.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 1112e965f9a75c..47a3e87cc8b484 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1888,6 +1888,14 @@ def test_non_regular_file(self): with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) + def test_start_offset(self): + # Modify src file position. + with self.get_files() as (src, dst): + src.seek(666) + shutil._copyfileobj_sendfile(src, dst) + with open(TESTFN2, "rb") as f: + self.assertEqual(f.read(), self.FILEDATA[666:]) + def test_unhandled_exception(self): with unittest.mock.patch('os.sendfile', side_effect=ZeroDivisionError): @@ -1895,7 +1903,20 @@ def test_unhandled_exception(self): self.assertRaises(ZeroDivisionError, shutil.copyfileobj, src, dst) + def test_exception_on_first_call(self): + # Emulate a case where the first call to sendfile() raises + # an exception in which case the function is supposed to + # give up immediately. + with unittest.mock.patch('os.sendfile', + side_effect=OSError): + with self.get_files() as (src, dst): + with self.assertRaises(_GiveupOnSendfile): + shutil._copyfileobj_sendfile(src, dst) + def test_cant_get_size(self): + # Emulate a case where src file size cannot be determined. + # Internally bufsize will be set to a small value and + # sendfile() will be called repeatedly. with unittest.mock.patch('os.fstat', side_effect=OSError) as m: with self.get_files() as (src, dst): shutil._copyfileobj_sendfile(src, dst) From c1fd38af066bbc9287a714e00b2b4d18857feccf Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 15:32:47 +0200 Subject: [PATCH 007/111] add test for empty file --- Lib/test/test_shutil.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 47a3e87cc8b484..7fa05c74f9b0ce 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1888,8 +1888,22 @@ def test_non_regular_file(self): with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) - def test_start_offset(self): - # Modify src file position. + def test_empty_file(self): + srcname = TESTFN + 'src' + dstname = TESTFN + 'dst' + self.addCleanup(lambda: support.unlink(srcname)) + self.addCleanup(lambda: support.unlink(dstname)) + with open(srcname, "wb"): + pass + + with open(srcname, "rb") as src: + with open(dstname, "wb") as dst: + shutil._copyfileobj_sendfile(src, dst) + + with open(dstname, "rb") as f: + self.assertEqual(f.read(), b"") + + def test_start_position(self): with self.get_files() as (src, dst): src.seek(666) shutil._copyfileobj_sendfile(src, dst) From 2ab63171f3340a66fcd668a856b858487d522afd Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 15:37:27 +0200 Subject: [PATCH 008/111] add test for non regular file dst --- Lib/test/test_shutil.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7fa05c74f9b0ce..0d248f48ea49cf 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1878,7 +1878,7 @@ def test_regular_copy(self): with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) - def test_non_regular_file(self): + def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: with self.assertRaises(_GiveupOnSendfile): @@ -1888,6 +1888,15 @@ def test_non_regular_file(self): with open(TESTFN2, "rb") as f: self.assertEqual(f.read(), self.FILEDATA) + def test_non_regular_file_dst(self): + with open(TESTFN, "rb") as src: + with io.BytesIO() as dst: + with self.assertRaises(_GiveupOnSendfile): + shutil._copyfileobj_sendfile(src, dst) + shutil.copyfileobj(src, dst) + dst.seek(0) + self.assertEqual(dst.read(), self.FILEDATA) + def test_empty_file(self): srcname = TESTFN + 'src' dstname = TESTFN + 'dst' From dacc3b6ddeeb9c3f79abbb956eafd5bdf35700fe Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 22 May 2018 19:14:54 +0200 Subject: [PATCH 009/111] small refactoring --- Lib/shutil.py | 4 ++-- Lib/test/test_shutil.py | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 42e7fae713e1f2..bcaa4da59435a4 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -81,8 +81,8 @@ class _GiveupOnSendfile(Exception): def _copyfileobj_sendfile(fsrc, fdst): - """Copy data from one file object to another one by using - zero-copy sendfile() method (faster). + """Copy data from one file object to another by using zero-copy + sendfile() method (faster). """ try: infd = fsrc.fileno() diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 0d248f48ea49cf..709ced093610d1 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1875,8 +1875,7 @@ def get_files(self): def test_regular_copy(self): with self.get_files() as (src, dst): shutil._copyfileobj_sendfile(src, dst) - with open(TESTFN2, "rb") as f: - self.assertEqual(f.read(), self.FILEDATA) + self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: @@ -1885,8 +1884,7 @@ def test_non_regular_file_src(self): shutil._copyfileobj_sendfile(src, dst) shutil.copyfileobj(src, dst) - with open(TESTFN2, "rb") as f: - self.assertEqual(f.read(), self.FILEDATA) + self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) def test_non_regular_file_dst(self): with open(TESTFN, "rb") as src: @@ -1916,8 +1914,7 @@ def test_start_position(self): with self.get_files() as (src, dst): src.seek(666) shutil._copyfileobj_sendfile(src, dst) - with open(TESTFN2, "rb") as f: - self.assertEqual(f.read(), self.FILEDATA[666:]) + self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA[666:]) def test_unhandled_exception(self): with unittest.mock.patch('os.sendfile', From 29d5881e78d8040d236e6240caeb1d7427cb544e Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 18:37:23 +0200 Subject: [PATCH 010/111] leave copyfileobj() alone in order to not introduce any incompatibility --- Lib/shutil.py | 22 ++++++++++++++++------ Lib/test/test_shutil.py | 5 ++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index bcaa4da59435a4..a1a828f291862b 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -124,17 +124,27 @@ def _copyfileobj_sendfile(fsrc, fdst): def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): """copy data from file-like object fsrc to file-like object fdst""" + while 1: + buf = fsrc.read(length) + if not buf: + break + fdst.write(buf) + +def _copyfileobj2(fsrc, fdst): + """Same as above but tries to use zero-copy sendfile(2) syscall + (faster). This is used by copyfile(), copy() and copy2() in order + to leave copyfileobj() alone and not introduce backward + incompatibilities. + E.g. by using sendfile() fdst.tell() is not updated() and fdst + cannot be opened in "a" mode. + """ if _HAS_SENDFILE: try: return _copyfileobj_sendfile(fsrc, fdst) except _GiveupOnSendfile: pass - while 1: - buf = fsrc.read(length) - if not buf: - break - fdst.write(buf) + return copyfileobj(fsrc, fdst) def _samefile(src, dst): # Macintosh, Unix. @@ -174,7 +184,7 @@ def copyfile(src, dst, *, follow_symlinks=True): else: with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: - copyfileobj(fsrc, fdst) + _copyfileobj2(fsrc, fdst) return dst def copymode(src, dst, *, follow_symlinks=True): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 709ced093610d1..03bb5a4133720d 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1919,9 +1919,8 @@ def test_start_position(self): def test_unhandled_exception(self): with unittest.mock.patch('os.sendfile', side_effect=ZeroDivisionError): - with self.get_files() as (src, dst): - self.assertRaises(ZeroDivisionError, - shutil.copyfileobj, src, dst) + self.assertRaises(ZeroDivisionError, + shutil.copyfile, TESTFN, TESTFN2) def test_exception_on_first_call(self): # Emulate a case where the first call to sendfile() raises From 114c4dec91206f74ad5b86a63f04cedf969a4b25 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 18:43:55 +0200 Subject: [PATCH 011/111] minor refactoring --- Lib/shutil.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index a1a828f291862b..cb5a7fa721f43d 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -98,17 +98,13 @@ def _copyfileobj_sendfile(fsrc, fdst): if blocksize <= 0: blocksize = COPY_BUFSIZE - try: - offset = fsrc.tell() - except (AttributeError, io.UnsupportedOperation) as err: - offset = 0 - - total_sent = 0 + offset = 0 + total_copied = 0 while True: try: sent = os.sendfile(outfd, infd, offset, blocksize) except OSError as err: - if total_sent == 0: + if total_copied == 0: # We can get here for different reasons, the main # one being a fd is not a regular mmap(2)-like # fd, in which case we'll fall back on using plain @@ -120,7 +116,7 @@ def _copyfileobj_sendfile(fsrc, fdst): if sent == 0: break # EOF offset += sent - total_sent += sent + total_copied += sent def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): """copy data from file-like object fsrc to file-like object fdst""" @@ -135,8 +131,8 @@ def _copyfileobj2(fsrc, fdst): (faster). This is used by copyfile(), copy() and copy2() in order to leave copyfileobj() alone and not introduce backward incompatibilities. - E.g. by using sendfile() fdst.tell() is not updated() and fdst - cannot be opened in "a" mode. + E.g. by using sendfile() fdst cannot be opened in "a"(ppend) mode + and its offset doesn't get updated. """ if _HAS_SENDFILE: try: From 501c0dd2f92e10016fa55254fce27bec396de79d Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 18:49:06 +0200 Subject: [PATCH 012/111] remove old test --- Lib/test/test_shutil.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 03bb5a4133720d..4ee5235272369f 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1910,12 +1910,6 @@ def test_empty_file(self): with open(dstname, "rb") as f: self.assertEqual(f.read(), b"") - def test_start_position(self): - with self.get_files() as (src, dst): - src.seek(666) - shutil._copyfileobj_sendfile(src, dst) - self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA[666:]) - def test_unhandled_exception(self): with unittest.mock.patch('os.sendfile', side_effect=ZeroDivisionError): From 41b4506b3f637b3bb388e37daf08cee12156aec1 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 18:52:15 +0200 Subject: [PATCH 013/111] update docstring --- Lib/shutil.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index cb5a7fa721f43d..cdbd11d0a0f40c 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -129,10 +129,11 @@ def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): def _copyfileobj2(fsrc, fdst): """Same as above but tries to use zero-copy sendfile(2) syscall (faster). This is used by copyfile(), copy() and copy2() in order - to leave copyfileobj() alone and not introduce backward - incompatibilities. + to leave copyfileobj() alone and not introduce any backward + incompatibility. E.g. by using sendfile() fdst cannot be opened in "a"(ppend) mode - and its offset doesn't get updated. + and its offset doesn't get updated. Also, fsrc and fdst may be + opened in text mode. """ if _HAS_SENDFILE: try: From fdb0973ff46eaaba332b343b7b9331f9bca6b768 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 19:45:24 +0200 Subject: [PATCH 014/111] update docstring; rename exception class --- Lib/shutil.py | 36 ++++++++++++++++++++---------------- Lib/test/test_shutil.py | 8 ++++---- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index cdbd11d0a0f40c..3f33221dc9b6c8 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -76,10 +76,18 @@ class RegistryError(Exception): """Raised when a registry operation with the archiving and unpacking registries fails""" -class _GiveupOnSendfile(Exception): +class _GiveupOnZeroCopy(Exception): """Raised when os.sendfile() cannot be used""" +def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): + """copy data from file-like object fsrc to file-like object fdst""" + while 1: + buf = fsrc.read(length) + if not buf: + break + fdst.write(buf) + def _copyfileobj_sendfile(fsrc, fdst): """Copy data from one file object to another by using zero-copy sendfile() method (faster). @@ -88,7 +96,7 @@ def _copyfileobj_sendfile(fsrc, fdst): infd = fsrc.fileno() outfd = fdst.fileno() except (AttributeError, io.UnsupportedOperation) as err: - raise _GiveupOnSendfile(err) # not a regular file + raise _GiveupOnZeroCopy(err) # not a regular file try: blocksize = os.fstat(infd).st_size @@ -109,7 +117,7 @@ def _copyfileobj_sendfile(fsrc, fdst): # one being a fd is not a regular mmap(2)-like # fd, in which case we'll fall back on using plain # read()/write() copy. - raise _GiveupOnSendfile(err) + raise _GiveupOnZeroCopy(err) else: raise err from None else: @@ -118,27 +126,23 @@ def _copyfileobj_sendfile(fsrc, fdst): offset += sent total_copied += sent -def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): - """copy data from file-like object fsrc to file-like object fdst""" - while 1: - buf = fsrc.read(length) - if not buf: - break - fdst.write(buf) - def _copyfileobj2(fsrc, fdst): - """Same as above but tries to use zero-copy sendfile(2) syscall + """Copies 2 filesystem files by using zero-copy sendfile(2) syscall (faster). This is used by copyfile(), copy() and copy2() in order to leave copyfileobj() alone and not introduce any backward incompatibility. - E.g. by using sendfile() fdst cannot be opened in "a"(ppend) mode - and its offset doesn't get updated. Also, fsrc and fdst may be - opened in text mode. + Possible incompatibilities by using sendfile() are: + - fdst cannot be opened in "a"(ppend) mode + - fdst offset doesn't get updated + - fsrc and fdst may be opened in text mode + - fsrc may be a BufferedReader (which hides unread data in a buffer), + GzipFile (which decompresses data), HTTPResponse (which decodes + chunks), ... """ if _HAS_SENDFILE: try: return _copyfileobj_sendfile(fsrc, fdst) - except _GiveupOnSendfile: + except _GiveupOnZeroCopy: pass return copyfileobj(fsrc, fdst) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 4ee5235272369f..77054ea3b47f37 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -21,7 +21,7 @@ get_archive_formats, Error, unpack_archive, register_unpack_format, RegistryError, unregister_unpack_format, get_unpack_formats, - SameFileError, _GiveupOnSendfile) + SameFileError, _GiveupOnZeroCopy) import tarfile import zipfile @@ -1880,7 +1880,7 @@ def test_regular_copy(self): def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: - with self.assertRaises(_GiveupOnSendfile): + with self.assertRaises(_GiveupOnZeroCopy): shutil._copyfileobj_sendfile(src, dst) shutil.copyfileobj(src, dst) @@ -1889,7 +1889,7 @@ def test_non_regular_file_src(self): def test_non_regular_file_dst(self): with open(TESTFN, "rb") as src: with io.BytesIO() as dst: - with self.assertRaises(_GiveupOnSendfile): + with self.assertRaises(_GiveupOnZeroCopy): shutil._copyfileobj_sendfile(src, dst) shutil.copyfileobj(src, dst) dst.seek(0) @@ -1923,7 +1923,7 @@ def test_exception_on_first_call(self): with unittest.mock.patch('os.sendfile', side_effect=OSError): with self.get_files() as (src, dst): - with self.assertRaises(_GiveupOnSendfile): + with self.assertRaises(_GiveupOnZeroCopy): shutil._copyfileobj_sendfile(src, dst) def test_cant_get_size(self): From 64d2bc59358848c2c72132bf1a8a5898b0a8e717 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 20:05:08 +0200 Subject: [PATCH 015/111] detect platforms which only support file to socket zero copy --- Lib/shutil.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 3f33221dc9b6c8..cdd816e4768b74 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -77,7 +77,7 @@ class RegistryError(Exception): and unpacking registries fails""" class _GiveupOnZeroCopy(Exception): - """Raised when os.sendfile() cannot be used""" + """Raised when os.sendfile() cannot be used for copying files.""" def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): @@ -92,6 +92,7 @@ def _copyfileobj_sendfile(fsrc, fdst): """Copy data from one file object to another by using zero-copy sendfile() method (faster). """ + global _HAS_SENDFILE try: infd = fsrc.fileno() outfd = fdst.fileno() @@ -112,11 +113,13 @@ def _copyfileobj_sendfile(fsrc, fdst): try: sent = os.sendfile(outfd, infd, offset, blocksize) except OSError as err: + if err.errno == errno.ENOTSOCK: + # sendfile() on this platform does not support copies + # between regular files (only sockets). + _HAS_SENDFILE = False if total_copied == 0: - # We can get here for different reasons, the main - # one being a fd is not a regular mmap(2)-like - # fd, in which case we'll fall back on using plain - # read()/write() copy. + # Immediately give up on first call. + # Probably one of the fds is not regular mmap(2)-like fd. raise _GiveupOnZeroCopy(err) else: raise err from None From 3a3c8efbbe91d9490fe7e0e1b233cd80a640e4ed Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 20:14:58 +0200 Subject: [PATCH 016/111] don't run test on platforms where file-to-file zero copy is not supported --- Lib/test/test_shutil.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 77054ea3b47f37..90a4f192e31c33 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -88,6 +88,28 @@ def rlistdir(path): res.append(name) return res +def supports_file2file_sendfile(): + if not hasattr(os, "sendfile"): + return False + try: + with open(TESTFN, "wb") as f: + f.write(b"0123456789") + with open(TESTFN, "rb") as src, open(TESTFN2, "wb") as dst: + infd = src.fileno() + outfd = dst.fileno() + try: + os.sendfile(outfd, infd, 0, 1024) + except OSError: + return False + else: + return True + finally: + support.unlink(TESTFN) + support.unlink(TESTFN2) + + +SUPPORTS_SENDFILE = supports_file2file_sendfile() + class TestShutil(unittest.TestCase): @@ -1834,7 +1856,7 @@ def test_move_dir_caseinsensitive(self): os.rmdir(dst_dir) -@unittest.skipIf(not hasattr(os, "sendfile"), 'needs os.sendfile()') +@unittest.skipIf(not SUPPORTS_SENDFILE, 'os.sendfile() not supported') class TestCopyFileObjSendfile(unittest.TestCase): FILESIZE = (10 * 1024 * 1024) # 10 MiB BUFSIZE = 8192 From 78617370c1e6df95be22ada5c03eebcee3c6c249 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 20:19:39 +0200 Subject: [PATCH 017/111] use tempfiles --- Lib/test/test_shutil.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 90a4f192e31c33..94bc82cf44f38e 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -89,23 +89,32 @@ def rlistdir(path): return res def supports_file2file_sendfile(): + # ...apparently Linux is the only one. if not hasattr(os, "sendfile"): return False + srcname = None + dstname = None try: - with open(TESTFN, "wb") as f: + with tempfile.NamedTemporaryFile("wb", delete=False) as f: + srcname = f.name f.write(b"0123456789") - with open(TESTFN, "rb") as src, open(TESTFN2, "wb") as dst: - infd = src.fileno() - outfd = dst.fileno() - try: - os.sendfile(outfd, infd, 0, 1024) - except OSError: - return False - else: - return True + + with open(srcname, "rb") as src: + with tempfile.NamedTemporaryFile("wb", delete=False) as dst: + dstname = f.name + infd = src.fileno() + outfd = dst.fileno() + try: + os.sendfile(outfd, infd, 0, 2) + except OSError: + return False + else: + return True finally: - support.unlink(TESTFN) - support.unlink(TESTFN2) + if srcname is not None: + support.unlink(srcname) + if dstname is not None: + support.unlink(dstname) SUPPORTS_SENDFILE = supports_file2file_sendfile() @@ -2055,4 +2064,4 @@ def test_module_all_attribute(self): if __name__ == '__main__': - unittest.main() + unittest.main(verbosity=2) From f3eecfdad2c9edb115ca59f5b487cfc5c655a03e Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 20:20:02 +0200 Subject: [PATCH 018/111] reset verbosity --- Lib/test/test_shutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 94bc82cf44f38e..6290dcddec98c9 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2064,4 +2064,4 @@ def test_module_all_attribute(self): if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main() From f67ce578e85ec46824bde4eef9033eb473f982de Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 20:41:43 +0200 Subject: [PATCH 019/111] add test for smaller chunks --- Lib/test/test_shutil.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 6290dcddec98c9..67912068ef0f9b 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1966,6 +1966,17 @@ def test_cant_get_size(self): shutil._copyfileobj_sendfile(src, dst) assert m.called + def test_smaller_chunks(self): + # Force file size detection to be smaller than the actual file + # size, resulting in multiple calls to sendfile(). + mock = unittest.mock.Mock() + mock.st_size = 65536 + 1 + with unittest.mock.patch('os.fstat', return_value=mock) as m: + with self.get_files() as (src, dst): + shutil._copyfileobj_sendfile(src, dst) + assert m.called + self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): From d45725453e370979e7344f8770acea26784da952 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 20:51:03 +0200 Subject: [PATCH 020/111] add big file size test --- Lib/test/test_shutil.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 67912068ef0f9b..82fd3ef94c4ed6 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1966,9 +1966,11 @@ def test_cant_get_size(self): shutil._copyfileobj_sendfile(src, dst) assert m.called - def test_smaller_chunks(self): - # Force file size detection to be smaller than the actual file - # size, resulting in multiple calls to sendfile(). + def test_small_chunks(self): + # Force internal file size detection to be smaller than the + # actual file size. We want to force sendfile() to be called + # multiple times, also in order to emulate a src fd which gets + # bigger while it is being copied. mock = unittest.mock.Mock() mock.st_size = 65536 + 1 with unittest.mock.patch('os.fstat', return_value=mock) as m: @@ -1977,6 +1979,19 @@ def test_smaller_chunks(self): assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + def test_big_chunk(self): + # Force internal file size detection to be +100MB bigger than + # the actual file size. Make sure sendfile() does not rely on + # file size value except for (maybe) a better throughput / + # performance. + mock = unittest.mock.Mock() + mock.st_size = self.FILESIZE + (100 * 1024 * 1024) + with unittest.mock.patch('os.fstat', return_value=mock) as m: + with self.get_files() as (src, dst): + shutil._copyfileobj_sendfile(src, dst) + assert m.called + self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): From 8eb211d4db6304ff97dab5ee2ad352169373c47c Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 20:59:02 +0200 Subject: [PATCH 021/111] add comment --- Lib/shutil.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index cdd816e4768b74..14dc7bed585b5d 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -53,7 +53,7 @@ "get_unpack_formats", "register_unpack_format", "unregister_unpack_format", "unpack_archive", "ignore_patterns", "chown", "which", "get_terminal_size", - "SameFileError"] + "SameFileError", "COPY_BUFSIZE"] # disk_usage is added later, if available on the platform class Error(OSError): @@ -80,8 +80,10 @@ class _GiveupOnZeroCopy(Exception): """Raised when os.sendfile() cannot be used for copying files.""" -def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): +def copyfileobj(fsrc, fdst, length=None): """copy data from file-like object fsrc to file-like object fdst""" + if length is None: + length = COPY_BUFSIZE while 1: buf = fsrc.read(length) if not buf: @@ -100,6 +102,10 @@ def _copyfileobj_sendfile(fsrc, fdst): raise _GiveupOnZeroCopy(err) # not a regular file try: + # Hopefully the whole file will be copied in a single call. + # sendfile() is called in a loop 'till EOF is reached (0 return) + # so a bufsize smaller than the actual file size should be OK + # also in case the src file content changes while being copied. blocksize = os.fstat(infd).st_size except OSError: blocksize = COPY_BUFSIZE @@ -118,8 +124,8 @@ def _copyfileobj_sendfile(fsrc, fdst): # between regular files (only sockets). _HAS_SENDFILE = False if total_copied == 0: - # Immediately give up on first call. - # Probably one of the fds is not regular mmap(2)-like fd. + # Immediately give up on first call. Probably one of the + # fds is not regular mmap(2)-like fd. raise _GiveupOnZeroCopy(err) else: raise err from None From a0fe7036b1f4f1266602d208b69454152b07233d Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 21:14:05 +0200 Subject: [PATCH 022/111] update doc --- Doc/library/shutil.rst | 8 ++++++++ Doc/whatsnew/3.8.rst | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 1527deb167f1e3..f2e55be1795d25 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -74,6 +74,8 @@ Directory and files operations Raise :exc:`SameFileError` instead of :exc:`Error`. Since the former is a subclass of the latter, this change is backward compatible. + .. versionchanged:: 3.8 + Uses high-performance :func:`os.sendfile` if available. .. exception:: SameFileError @@ -163,6 +165,9 @@ Directory and files operations Added *follow_symlinks* argument. Now returns path to the newly created file. + .. versionchanged:: 3.8 + Uses high-performance :func:`os.sendfile` if available. + .. function:: copy2(src, dst, *, follow_symlinks=True) Identical to :func:`~shutil.copy` except that :func:`copy2` @@ -185,6 +190,9 @@ Directory and files operations file system attributes too (currently Linux only). Now returns path to the newly created file. + .. versionchanged:: 3.8 + Uses high-performance :func:`os.sendfile` if available. + .. function:: ignore_patterns(\*patterns) This factory function creates a function that can be used as a callable for diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 9aad908f927f84..0227e75550f282 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -98,6 +98,10 @@ Optimizations first introduced in Python 3.4. It offers better performance and smaller size compared to Protocol 3 available since Python 3.0. +* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2` use + high-performance :func:`os.sendfile` if available resulting in roughly a + 20% speedup. + Build and C API Changes ======================= From 72961478b6b3288f3b5c7bba91cb7cc5ab31f98a Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 21:35:31 +0200 Subject: [PATCH 023/111] update whatsnew doc --- Doc/whatsnew/3.8.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 0227e75550f282..123566a73393ef 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -100,7 +100,8 @@ Optimizations * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2` use high-performance :func:`os.sendfile` if available resulting in roughly a - 20% speedup. + 20%/25% speedup of the copying operation and a considerably lower CPU cycles + consumption. Build and C API Changes ======================= From d0c3bbac48f615ace60b0adac2824e6477539c45 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 22:15:39 +0200 Subject: [PATCH 024/111] update doc --- Doc/whatsnew/3.8.rst | 2 +- Lib/shutil.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 123566a73393ef..3cb9bbf02b53f6 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -99,7 +99,7 @@ Optimizations size compared to Protocol 3 available since Python 3.0. * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2` use - high-performance :func:`os.sendfile` if available resulting in roughly a + high-performance :func:`os.sendfile` is available resulting in roughly a 20%/25% speedup of the copying operation and a considerably lower CPU cycles consumption. diff --git a/Lib/shutil.py b/Lib/shutil.py index 14dc7bed585b5d..6c510329aee699 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -141,7 +141,7 @@ def _copyfileobj2(fsrc, fdst): to leave copyfileobj() alone and not introduce any backward incompatibility. Possible incompatibilities by using sendfile() are: - - fdst cannot be opened in "a"(ppend) mode + - fdst cannot be open in "a"(ppend) mode - fdst offset doesn't get updated - fsrc and fdst may be opened in text mode - fsrc may be a BufferedReader (which hides unread data in a buffer), From 2cafd805162d0880f1d2b5fd135084bdeb2aab38 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 22:23:48 +0200 Subject: [PATCH 025/111] catch Exception --- Lib/shutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 6c510329aee699..1d799c6a555495 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -98,7 +98,7 @@ def _copyfileobj_sendfile(fsrc, fdst): try: infd = fsrc.fileno() outfd = fdst.fileno() - except (AttributeError, io.UnsupportedOperation) as err: + except Exception as err: raise _GiveupOnZeroCopy(err) # not a regular file try: @@ -107,7 +107,7 @@ def _copyfileobj_sendfile(fsrc, fdst): # so a bufsize smaller than the actual file size should be OK # also in case the src file content changes while being copied. blocksize = os.fstat(infd).st_size - except OSError: + except Exception: blocksize = COPY_BUFSIZE else: if blocksize <= 0: From bb2a75f50c194f3badbf4bd1ae12968fa20573bc Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 24 May 2018 22:24:50 +0200 Subject: [PATCH 026/111] remove unused import --- Lib/shutil.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 1d799c6a555495..92118b03679b30 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -10,7 +10,6 @@ import fnmatch import collections import errno -import io try: import zlib From e5025dce0d4bbccc1eca9da400643333225052f0 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 25 May 2018 00:25:40 +0200 Subject: [PATCH 027/111] add test case for error on second sendfile() call --- Doc/whatsnew/3.8.rst | 2 +- Lib/test/test_shutil.py | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 3cb9bbf02b53f6..a08cf45cbad6e0 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -100,7 +100,7 @@ Optimizations * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2` use high-performance :func:`os.sendfile` is available resulting in roughly a - 20%/25% speedup of the copying operation and a considerably lower CPU cycles + 20-25% speedup of the copying operation and a considerably lower CPU cycles consumption. Build and C API Changes diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 82fd3ef94c4ed6..3b2d5d14577200 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1938,8 +1938,7 @@ def test_empty_file(self): with open(dstname, "wb") as dst: shutil._copyfileobj_sendfile(src, dst) - with open(dstname, "rb") as f: - self.assertEqual(f.read(), b"") + self.assertEqual(read_file(dstname, binary=True), b"") def test_unhandled_exception(self): with unittest.mock.patch('os.sendfile', @@ -1957,6 +1956,25 @@ def test_exception_on_first_call(self): with self.assertRaises(_GiveupOnZeroCopy): shutil._copyfileobj_sendfile(src, dst) + def test_exception_on_second_call(self): + # ...but on subsequent calls we expect the exception to bubble up. + def sendfile(*args, **kwargs): + if not flag: + flag.append(None) + return orig_sendfile(*args, **kwargs) + else: + raise OSError(errno.EBADF, "yo") + + flag = [] + orig_sendfile = os.sendfile + with unittest.mock.patch('os.sendfile', create=True, + side_effect=sendfile): + with self.get_files() as (src, dst): + with self.assertRaises(OSError) as cm: + shutil._copyfileobj_sendfile(src, dst) + assert flag + self.assertEqual(cm.exception.errno, errno.EBADF) + def test_cant_get_size(self): # Emulate a case where src file size cannot be determined. # Internally bufsize will be set to a small value and @@ -1965,6 +1983,7 @@ def test_cant_get_size(self): with self.get_files() as (src, dst): shutil._copyfileobj_sendfile(src, dst) assert m.called + self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) def test_small_chunks(self): # Force internal file size detection to be smaller than the @@ -2083,7 +2102,7 @@ def test_module_all_attribute(self): 'unregister_archive_format', 'get_unpack_formats', 'register_unpack_format', 'unregister_unpack_format', 'unpack_archive', 'ignore_patterns', 'chown', 'which', - 'get_terminal_size', 'SameFileError'] + 'get_terminal_size', 'SameFileError', 'COPY_BUFSIZE'] if hasattr(os, 'statvfs') or os.name == 'nt': target_api.append('disk_usage') self.assertEqual(set(shutil.__all__), set(target_api)) From a36a534433471995ebe03475482b944fd8accedf Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 25 May 2018 00:36:56 +0200 Subject: [PATCH 028/111] turn docstring into comment --- Lib/shutil.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 92118b03679b30..1a00ec22b0c006 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -135,18 +135,17 @@ def _copyfileobj_sendfile(fsrc, fdst): total_copied += sent def _copyfileobj2(fsrc, fdst): - """Copies 2 filesystem files by using zero-copy sendfile(2) syscall - (faster). This is used by copyfile(), copy() and copy2() in order - to leave copyfileobj() alone and not introduce any backward - incompatibility. - Possible incompatibilities by using sendfile() are: - - fdst cannot be open in "a"(ppend) mode - - fdst offset doesn't get updated - - fsrc and fdst may be opened in text mode - - fsrc may be a BufferedReader (which hides unread data in a buffer), - GzipFile (which decompresses data), HTTPResponse (which decodes - chunks), ... - """ + # Copies 2 filesystem files by using zero-copy sendfile(2) syscall + # (faster). This is used by copyfile(), copy() and copy2() in order + # to leave copyfileobj() alone and not introduce any unexpected + # breakage. Possible risks by using sendfile() in copyfileobj() are: + # - fdst cannot be open in "a"(ppend) mode + # - fsrc and fdst may be opened in text mode + # - fdst offset doesn't get updated + # - fsrc may be a BufferedReader (which hides unread data in a buffer), + # GzipFile (which decompresses data), HTTPResponse (which decodes + # chunks). + # - possibly others... if _HAS_SENDFILE: try: return _copyfileobj_sendfile(fsrc, fdst) From e9da3fa87056e7926082afdff60f50828f8f7f51 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 25 May 2018 01:16:37 +0200 Subject: [PATCH 029/111] add one more test --- Lib/shutil.py | 27 ++++++++++++--------------- Lib/test/test_shutil.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 1a00ec22b0c006..c528950646c2f5 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -90,8 +90,8 @@ def copyfileobj(fsrc, fdst, length=None): fdst.write(buf) def _copyfileobj_sendfile(fsrc, fdst): - """Copy data from one file object to another by using zero-copy - sendfile() method (faster). + """Copy data from one file object to another by using + high-performance sendfile() method. """ global _HAS_SENDFILE try: @@ -100,20 +100,17 @@ def _copyfileobj_sendfile(fsrc, fdst): except Exception as err: raise _GiveupOnZeroCopy(err) # not a regular file + # Hopefully the whole file will be copied in a single call. + # sendfile() is called in a loop 'till EOF is reached (0 return) + # so a bufsize smaller or bigger than the actual file size + # should not make any difference. try: - # Hopefully the whole file will be copied in a single call. - # sendfile() is called in a loop 'till EOF is reached (0 return) - # so a bufsize smaller than the actual file size should be OK - # also in case the src file content changes while being copied. - blocksize = os.fstat(infd).st_size + blocksize = max(os.fstat(infd).st_size, COPY_BUFSIZE, 16 * 1024) except Exception: - blocksize = COPY_BUFSIZE - else: - if blocksize <= 0: - blocksize = COPY_BUFSIZE + blocksize = max(COPY_BUFSIZE, 16 * 1024) offset = 0 - total_copied = 0 + total = 0 while True: try: sent = os.sendfile(outfd, infd, offset, blocksize) @@ -122,9 +119,9 @@ def _copyfileobj_sendfile(fsrc, fdst): # sendfile() on this platform does not support copies # between regular files (only sockets). _HAS_SENDFILE = False - if total_copied == 0: + if total == 0: # Immediately give up on first call. Probably one of the - # fds is not regular mmap(2)-like fd. + # fds is not a regular mmap(2)-like fd. raise _GiveupOnZeroCopy(err) else: raise err from None @@ -132,7 +129,7 @@ def _copyfileobj_sendfile(fsrc, fdst): if sent == 0: break # EOF offset += sent - total_copied += sent + total += sent def _copyfileobj2(fsrc, fdst): # Copies 2 filesystem files by using zero-copy sendfile(2) syscall diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 3b2d5d14577200..6acb149cdc6d78 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2011,6 +2011,24 @@ def test_big_chunk(self): assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + def test_blocksize_arg(self): + with unittest.mock.patch('os.sendfile', + side_effect=ZeroDivisionError) as m: + self.assertRaises(ZeroDivisionError, + shutil.copyfile, TESTFN, TESTFN2) + blocksize = m.call_args[0][3] + # Make sure file size and the block size arg passed to + # sendfile() are the same. + self.assertEqual(blocksize, os.path.getsize(TESTFN)) + # ...unless we're dealing with a small file. + support.unlink(TESTFN2) + write_file(TESTFN2, b"hello", binary=True) + self.addCleanup(support.unlink, TESTFN2 + '3') + self.assertRaises(ZeroDivisionError, + shutil.copyfile, TESTFN2, TESTFN2 + '3') + blocksize = m.call_args[0][3] + self.assertEqual(blocksize, shutil.COPY_BUFSIZE) + class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): From 9fcc2e7e52bd9efcc8ae913f6ac7622a398b15f5 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 25 May 2018 01:27:09 +0200 Subject: [PATCH 030/111] update comment --- Lib/shutil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index c528950646c2f5..3a21fdfc95d7c7 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -103,7 +103,8 @@ def _copyfileobj_sendfile(fsrc, fdst): # Hopefully the whole file will be copied in a single call. # sendfile() is called in a loop 'till EOF is reached (0 return) # so a bufsize smaller or bigger than the actual file size - # should not make any difference. + # should not make any difference, also in case the file content + # changes while being copied. try: blocksize = max(os.fstat(infd).st_size, COPY_BUFSIZE, 16 * 1024) except Exception: From 4f32242aa0cd8c92d1cc4bc4e955a368f5326467 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 25 May 2018 01:42:53 +0200 Subject: [PATCH 031/111] add Misc/NEWS entry --- Doc/whatsnew/3.8.rst | 6 +++--- .../next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index a08cf45cbad6e0..0e7883eada477a 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -98,10 +98,10 @@ Optimizations first introduced in Python 3.4. It offers better performance and smaller size compared to Protocol 3 available since Python 3.0. -* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2` use - high-performance :func:`os.sendfile` is available resulting in roughly a +* :func:`shutil.copyfile`, :func:`shutil.copy` and :func:`shutil.copy2` use + high-performance :func:`os.sendfile` if available resulting in roughly a 20-25% speedup of the copying operation and a considerably lower CPU cycles - consumption. + consumption. (Contributed by Giampaolo Rodola' and desbma in :issue:`33639`) Build and C API Changes ======================= diff --git a/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst b/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst new file mode 100644 index 00000000000000..45b795fb8276a7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst @@ -0,0 +1,4 @@ +shutil.copyfile(), shutil.copy() and shutil.copy2() use high-performance +os.sendfile() if available resulting in roughly a 20-25% speedup of the +copying operation and a considerably lower CPU cycles consumption. +(Contributed by Giampaolo Rodola' and desbma in 33639) From 24ad25acb8ce01ead720a4af48eb71a59dd98e6b Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 25 May 2018 12:06:02 +0200 Subject: [PATCH 032/111] get rid of COPY_BUFSIZE; it belongs to another PR --- Lib/shutil.py | 11 ++++------- Lib/test/test_shutil.py | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 3a21fdfc95d7c7..6311440791cc69 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -43,7 +43,6 @@ getgrnam = None _HAS_SENDFILE = hasattr(os, "sendfile") -COPY_BUFSIZE = 16 * 1024 __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", "copytree", "move", "rmtree", "Error", "SpecialFileError", @@ -52,7 +51,7 @@ "get_unpack_formats", "register_unpack_format", "unregister_unpack_format", "unpack_archive", "ignore_patterns", "chown", "which", "get_terminal_size", - "SameFileError", "COPY_BUFSIZE"] + "SameFileError"] # disk_usage is added later, if available on the platform class Error(OSError): @@ -79,10 +78,8 @@ class _GiveupOnZeroCopy(Exception): """Raised when os.sendfile() cannot be used for copying files.""" -def copyfileobj(fsrc, fdst, length=None): +def copyfileobj(fsrc, fdst, length=16*1024): """copy data from file-like object fsrc to file-like object fdst""" - if length is None: - length = COPY_BUFSIZE while 1: buf = fsrc.read(length) if not buf: @@ -106,9 +103,9 @@ def _copyfileobj_sendfile(fsrc, fdst): # should not make any difference, also in case the file content # changes while being copied. try: - blocksize = max(os.fstat(infd).st_size, COPY_BUFSIZE, 16 * 1024) + blocksize = max(os.fstat(infd).st_size, 10 * 1024) except Exception: - blocksize = max(COPY_BUFSIZE, 16 * 1024) + blocksize = 100 * 1024 offset = 0 total = 0 diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 6acb149cdc6d78..340b3a906eaaa1 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2027,7 +2027,7 @@ def test_blocksize_arg(self): self.assertRaises(ZeroDivisionError, shutil.copyfile, TESTFN2, TESTFN2 + '3') blocksize = m.call_args[0][3] - self.assertEqual(blocksize, shutil.COPY_BUFSIZE) + self.assertEqual(blocksize, 10 * 1024) class TermsizeTests(unittest.TestCase): @@ -2120,7 +2120,7 @@ def test_module_all_attribute(self): 'unregister_archive_format', 'get_unpack_formats', 'register_unpack_format', 'unregister_unpack_format', 'unpack_archive', 'ignore_patterns', 'chown', 'which', - 'get_terminal_size', 'SameFileError', 'COPY_BUFSIZE'] + 'get_terminal_size', 'SameFileError'] if hasattr(os, 'statvfs') or os.name == 'nt': target_api.append('disk_usage') self.assertEqual(set(shutil.__all__), set(target_api)) From 24d20e629cf0674c86c72e677f9c42749e2aa8fc Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 25 May 2018 12:09:07 +0200 Subject: [PATCH 033/111] update doc --- Doc/library/shutil.rst | 9 ++++++--- Doc/whatsnew/3.8.rst | 7 ++++--- .../Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index f2e55be1795d25..fad3e5ce6b6fe5 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -75,7 +75,8 @@ Directory and files operations a subclass of the latter, this change is backward compatible. .. versionchanged:: 3.8 - Uses high-performance :func:`os.sendfile` if available. + Uses high-performance :func:`os.sendfile` if available and supports + file-to-file copy (namely Linux). .. exception:: SameFileError @@ -166,7 +167,8 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Uses high-performance :func:`os.sendfile` if available. + Uses high-performance :func:`os.sendfile` if available and supports + file-to-file copy (namely Linux). .. function:: copy2(src, dst, *, follow_symlinks=True) @@ -191,7 +193,8 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Uses high-performance :func:`os.sendfile` if available. + Uses high-performance :func:`os.sendfile` if available and supports + file-to-file copy (namely Linux). .. function:: ignore_patterns(\*patterns) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 0e7883eada477a..42ec12988a2d69 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -99,9 +99,10 @@ Optimizations size compared to Protocol 3 available since Python 3.0. * :func:`shutil.copyfile`, :func:`shutil.copy` and :func:`shutil.copy2` use - high-performance :func:`os.sendfile` if available resulting in roughly a - 20-25% speedup of the copying operation and a considerably lower CPU cycles - consumption. (Contributed by Giampaolo Rodola' and desbma in :issue:`33639`) + high-performance :func:`os.sendfile` if available and supports file-to-file + copy (namely Linux) resulting in roughly a 20-25% speedup of the copying + operation and a considerably lower CPU cycles consumption. + (Contributed by Giampaolo Rodola' and desbma in :issue:`33639`) Build and C API Changes ======================= diff --git a/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst b/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst index 45b795fb8276a7..b4b4a0c32db5d0 100644 --- a/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst +++ b/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst @@ -1,4 +1,5 @@ shutil.copyfile(), shutil.copy() and shutil.copy2() use high-performance -os.sendfile() if available resulting in roughly a 20-25% speedup of the -copying operation and a considerably lower CPU cycles consumption. +os.sendfile() if available and supports file-to-file copy (namely Linux) +resulting in roughly a 20-25% speedup of the copying operation and a +considerably lower CPU cycles consumption. (Contributed by Giampaolo Rodola' and desbma in 33639) From 7b6e5769d6b5f0e8f6d6b82e7ffd077ca6d69d96 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 27 May 2018 21:31:10 +0200 Subject: [PATCH 034/111] expose posix._fcopyfile() for OSX --- Modules/clinic/posixmodule.c.h | 37 +++++++++++++++++++++++++++++++++- Modules/posixmodule.c | 21 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index e4bbd082450baa..fc14d32e3c5fea 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3853,6 +3853,37 @@ os_write(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +#if defined(__APPLE__) +PyDoc_STRVAR(os__fcopyfile__doc__, +"_fcopyfile($module, infd, outfd, /)\n" +"--\n" +"\n" +"Efficiently copy 2 file descriptors (OSX only)."); + +#define OS__FCOPYFILE_METHODDEF \ + {"_fcopyfile", (PyCFunction)os__fcopyfile, METH_FASTCALL, os__fcopyfile__doc__}, + +static PyObject * +os__fcopyfile_impl(PyObject *module, int infd, int outfd); + +static PyObject * +os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int infd; + int outfd; + + if (!_PyArg_ParseStack(args, nargs, "ii:_fcopyfile", + &infd, &outfd)) { + goto exit; + } + return_value = os__fcopyfile_impl(module, infd, outfd); + +exit: + return return_value; +} +#endif /* defined(__APPLE__) */ + PyDoc_STRVAR(os_fstat__doc__, "fstat($module, /, fd)\n" "--\n" @@ -6414,6 +6445,10 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #define OS_PREADV_METHODDEF #endif /* !defined(OS_PREADV_METHODDEF) */ +#ifndef OS__FCOPYFILE_METHODDEF + #define OS__FCOPYFILE_METHODDEF +#endif /* !defined(OS__FCOPYFILE_METHODDEF) */ + #ifndef OS_PIPE_METHODDEF #define OS_PIPE_METHODDEF #endif /* !defined(OS_PIPE_METHODDEF) */ @@ -6589,4 +6624,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=8d3d9dddf254c3c2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b2a03113a42546f5 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a9b3917188c697..dfdfadca68e6d7 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8745,6 +8745,26 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) #endif /* HAVE_SENDFILE */ +#if defined(__APPLE__) +/*[clinic input] +os._fcopyfile + + infd: int + outfd: int + / + +Efficiently copy 2 file descriptors (OSX only). +[clinic start generated code]*/ + +static PyObject * +os__fcopyfile_impl(PyObject *module, int infd, int outfd) +/*[clinic end generated code: output=3e629d5c50b33d04 input=565c8d0191b573b8]*/ +{ + Py_RETURN_NONE; +} +#endif + + /*[clinic input] os.fstat @@ -12921,6 +12941,7 @@ static PyMethodDef posix_methods[] = { OS_UTIME_METHODDEF OS_TIMES_METHODDEF OS__EXIT_METHODDEF + OS__FCOPYFILE_METHODDEF OS_EXECV_METHODDEF OS_EXECVE_METHODDEF OS_SPAWNV_METHODDEF From b62b61ea05ac83b248025ad944bf7f0e2e754802 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 27 May 2018 21:46:21 +0200 Subject: [PATCH 035/111] merge from linux branch --- Lib/shutil.py | 38 +++++++++++++++++++++----------------- Lib/test/test_shutil.py | 9 ++++++++- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 6311440791cc69..e44ad4d50bb322 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -42,7 +42,8 @@ except ImportError: getgrnam = None -_HAS_SENDFILE = hasattr(os, "sendfile") +_HAS_LINUX_SENDFILE = hasattr(os, "sendfile") and \ + sys.platform.startswith("linux") __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", "copytree", "move", "rmtree", "Error", "SpecialFileError", @@ -87,10 +88,11 @@ def copyfileobj(fsrc, fdst, length=16*1024): fdst.write(buf) def _copyfileobj_sendfile(fsrc, fdst): - """Copy data from one file object to another by using - high-performance sendfile() method. + """Copy data from one regular file object to another by using + high-performance sendfile() method. Linux >= 2.6.33 is apparently + the only platform able to do this. """ - global _HAS_SENDFILE + global _HAS_LINUX_SENDFILE try: infd = fsrc.fileno() outfd = fdst.fileno() @@ -103,31 +105,33 @@ def _copyfileobj_sendfile(fsrc, fdst): # should not make any difference, also in case the file content # changes while being copied. try: - blocksize = max(os.fstat(infd).st_size, 10 * 1024) + blocksize = max(os.fstat(infd).st_size, 2 ** 23) # min 8MB except Exception: - blocksize = 100 * 1024 + blocksize = 2 ** 27 # 128MB offset = 0 - total = 0 while True: try: sent = os.sendfile(outfd, infd, offset, blocksize) except OSError as err: if err.errno == errno.ENOTSOCK: - # sendfile() on this platform does not support copies - # between regular files (only sockets). - _HAS_SENDFILE = False - if total == 0: - # Immediately give up on first call. Probably one of the - # fds is not a regular mmap(2)-like fd. - raise _GiveupOnZeroCopy(err) - else: + # sendfile() on this platform (probably Linux < 2.6.33) + # does not support copies between regular files (only + # sockets). + _HAS_LINUX_SENDFILE = False + + if err.errno == errno.ENOSPC: # filesystem is full raise err from None + + # Give up on first call and if no data was copied. + if offset == 0 and os.lseek(outfd, 0, os.SEEK_CUR) == 0: + raise _GiveupOnZeroCopy(err) + + raise err from None else: if sent == 0: break # EOF offset += sent - total += sent def _copyfileobj2(fsrc, fdst): # Copies 2 filesystem files by using zero-copy sendfile(2) syscall @@ -141,7 +145,7 @@ def _copyfileobj2(fsrc, fdst): # GzipFile (which decompresses data), HTTPResponse (which decodes # chunks). # - possibly others... - if _HAS_SENDFILE: + if _HAS_LINUX_SENDFILE: try: return _copyfileobj_sendfile(fsrc, fdst) except _GiveupOnZeroCopy: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 340b3a906eaaa1..510da441c5e25b 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2027,7 +2027,14 @@ def test_blocksize_arg(self): self.assertRaises(ZeroDivisionError, shutil.copyfile, TESTFN2, TESTFN2 + '3') blocksize = m.call_args[0][3] - self.assertEqual(blocksize, 10 * 1024) + self.assertEqual(blocksize, 2 ** 23) + + def test_filesystem_full(self): + # Emulate a case where filesystem is full and sendfile() fails + # on first call. + with unittest.mock.patch('os.sendfile', + side_effect=OSError(errno.ENOSPC, "yo")): + self.assertRaises(OSError, shutil.copyfile, TESTFN, TESTFN2) class TermsizeTests(unittest.TestCase): From 34e9618b762c3b7df16a8885bfdf7ae774982ee6 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 27 May 2018 22:12:24 +0200 Subject: [PATCH 036/111] merge from linux branch --- Lib/test/test_shutil.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 510da441c5e25b..289ded945a8df6 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -64,6 +64,24 @@ def write_file(path, content, binary=False): with open(path, 'wb' if binary else 'w') as fp: fp.write(content) +def write_test_file(path, size): + """Create a test file with an arbitrary size and random text content.""" + def chunks(total, step): + assert total >= step + while total > step: + yield step + total -= step + if total: + yield total + + bufsize = min(size, 8192) + chunk = b"".join([random.choice(string.ascii_letters).encode() + for i in range(bufsize)]) + with open(path, 'wb') as f: + for csize in chunks(size, bufsize): + f.write(chunk) + assert os.path.getsize(path) == size + def read_file(path, binary=False): """Return contents from a file located at *path*. @@ -1865,27 +1883,13 @@ def test_move_dir_caseinsensitive(self): os.rmdir(dst_dir) -@unittest.skipIf(not SUPPORTS_SENDFILE, 'os.sendfile() not supported') -class TestCopyFileObjSendfile(unittest.TestCase): +class _CopyFileTest(object): FILESIZE = (10 * 1024 * 1024) # 10 MiB - BUFSIZE = 8192 FILEDATA = b"" @classmethod def setUpClass(cls): - def chunks(total, step): - assert total >= step - while total > step: - yield step - total -= step - if total: - yield total - - chunk = b"".join([random.choice(string.ascii_letters).encode() - for i in range(cls.BUFSIZE)]) - with open(TESTFN, 'wb') as f: - for csize in chunks(cls.FILESIZE, cls.BUFSIZE): - f.write(chunk) + write_test_file(TESTFN, cls.FILESIZE) with open(TESTFN, 'rb') as f: cls.FILEDATA = f.read() assert len(cls.FILEDATA) == cls.FILESIZE @@ -1903,6 +1907,10 @@ def get_files(self): with open(TESTFN2, "wb") as dst: yield (src, dst) + +@unittest.skipIf(not SUPPORTS_SENDFILE, 'os.sendfile() not supported') +class TestCopyFileObjSendfile(_CopyFileTest, unittest.TestCase): + def test_regular_copy(self): with self.get_files() as (src, dst): shutil._copyfileobj_sendfile(src, dst) From 6b2090297c44ee9c341226ad3eda94a19bc48182 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 00:02:51 +0200 Subject: [PATCH 037/111] expose fcopyfile --- Lib/shutil.py | 30 ++++++++++++++++++ Lib/test/test_shutil.py | 67 ++++++++++++++++++++++++++++------------- Modules/posixmodule.c | 13 ++++++++ 3 files changed, 89 insertions(+), 21 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index e44ad4d50bb322..e387eca482d1f7 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -42,8 +42,14 @@ except ImportError: getgrnam = None +try: + import posix +except ImportError: + posix = None + _HAS_LINUX_SENDFILE = hasattr(os, "sendfile") and \ sys.platform.startswith("linux") +_HAS_FCOPYFILE = hasattr(posix, "_fcopyfile") __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", "copytree", "move", "rmtree", "Error", "SpecialFileError", @@ -87,6 +93,24 @@ def copyfileobj(fsrc, fdst, length=16*1024): break fdst.write(buf) +def _copyfileobj_fcopyfile(fsrc, fdst): + """Copy data from one regular file object to another by using + high-performance fcopyfile() syscall (OSX only). + """ + try: + infd = fsrc.fileno() + outfd = fdst.fileno() + except Exception as err: + raise _GiveupOnZeroCopy(err) # not a regular file + + try: + posix._fcopyfile(infd, outfd) + except OSError as err: + if err.errno in {errno.EINVAL, errno.ENOTSUP}: + raise _GiveupOnZeroCopy(err) + else: + raise err from None + def _copyfileobj_sendfile(fsrc, fdst): """Copy data from one regular file object to another by using high-performance sendfile() method. Linux >= 2.6.33 is apparently @@ -151,6 +175,12 @@ def _copyfileobj2(fsrc, fdst): except _GiveupOnZeroCopy: pass + if _HAS_FCOPYFILE: + try: + return _copyfileobj_fcopyfile(fsrc, fdst) + except _GiveupOnZeroCopy: + pass + return copyfileobj(fsrc, fdst) def _samefile(src, dst): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 289ded945a8df6..e85ba6b8f4b8f4 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -24,11 +24,16 @@ SameFileError, _GiveupOnZeroCopy) import tarfile import zipfile +try: + import posix +except ImportError: + posix = None from test import support from test.support import TESTFN, FakePath TESTFN2 = TESTFN + "2" +SUPPORTS_FCOPYFILE = hasattr(posix, "_fcopyfile") try: import grp @@ -1883,9 +1888,11 @@ def test_move_dir_caseinsensitive(self): os.rmdir(dst_dir) -class _CopyFileTest(object): +class _ZeroCopyFileTest(object): + """Tests common to all zero-copy APIs.""" FILESIZE = (10 * 1024 * 1024) # 10 MiB FILEDATA = b"" + PATCHPOINT = "" @classmethod def setUpClass(cls): @@ -1907,20 +1914,21 @@ def get_files(self): with open(TESTFN2, "wb") as dst: yield (src, dst) + def zerocopy_fun(self, *args, **kwargs): + raise NotImplementedError("must be implemented in subclass") -@unittest.skipIf(not SUPPORTS_SENDFILE, 'os.sendfile() not supported') -class TestCopyFileObjSendfile(_CopyFileTest, unittest.TestCase): + # --- def test_regular_copy(self): with self.get_files() as (src, dst): - shutil._copyfileobj_sendfile(src, dst) + self.zerocopy_fun(src, dst) self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: with self.assertRaises(_GiveupOnZeroCopy): - shutil._copyfileobj_sendfile(src, dst) + self.zerocopy_fun(src, dst) shutil.copyfileobj(src, dst) self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) @@ -1929,7 +1937,7 @@ def test_non_regular_file_dst(self): with open(TESTFN, "rb") as src: with io.BytesIO() as dst: with self.assertRaises(_GiveupOnZeroCopy): - shutil._copyfileobj_sendfile(src, dst) + self.zerocopy_fun(src, dst) shutil.copyfileobj(src, dst) dst.seek(0) self.assertEqual(dst.read(), self.FILEDATA) @@ -1944,28 +1952,43 @@ def test_empty_file(self): with open(srcname, "rb") as src: with open(dstname, "wb") as dst: - shutil._copyfileobj_sendfile(src, dst) + self.zerocopy_fun(src, dst) self.assertEqual(read_file(dstname, binary=True), b"") def test_unhandled_exception(self): - with unittest.mock.patch('os.sendfile', + with unittest.mock.patch(self.PATCHPOINT, side_effect=ZeroDivisionError): self.assertRaises(ZeroDivisionError, shutil.copyfile, TESTFN, TESTFN2) def test_exception_on_first_call(self): - # Emulate a case where the first call to sendfile() raises - # an exception in which case the function is supposed to - # give up immediately. - with unittest.mock.patch('os.sendfile', - side_effect=OSError): + # Emulate a case where the first call to the zero-copy + # function raises an exception in which case the function is + # supposed to give up immediately. + with unittest.mock.patch(self.PATCHPOINT, + side_effect=OSError(errno.EINVAL, "yo")): with self.get_files() as (src, dst): with self.assertRaises(_GiveupOnZeroCopy): - shutil._copyfileobj_sendfile(src, dst) + self.zerocopy_fun(src, dst) + + def test_filesystem_full(self): + # Emulate a case where filesystem is full and sendfile() fails + # on first call. + with unittest.mock.patch(self.PATCHPOINT, + side_effect=OSError(errno.ENOSPC, "yo")): + with self.get_files() as (src, dst): + self.assertRaises(OSError, self.zerocopy_fun, src, dst) + + +@unittest.skipIf(not SUPPORTS_SENDFILE, 'os.sendfile() not supported') +class TestCopyFileObjSendfile(_ZeroCopyFileTest, unittest.TestCase): + PATCHPOINT = "os.sendfile" + + def zerocopy_fun(self, *args, **kwargs): + return shutil._copyfileobj_sendfile(*args, **kwargs) def test_exception_on_second_call(self): - # ...but on subsequent calls we expect the exception to bubble up. def sendfile(*args, **kwargs): if not flag: flag.append(None) @@ -2037,12 +2060,14 @@ def test_blocksize_arg(self): blocksize = m.call_args[0][3] self.assertEqual(blocksize, 2 ** 23) - def test_filesystem_full(self): - # Emulate a case where filesystem is full and sendfile() fails - # on first call. - with unittest.mock.patch('os.sendfile', - side_effect=OSError(errno.ENOSPC, "yo")): - self.assertRaises(OSError, shutil.copyfile, TESTFN, TESTFN2) + +@unittest.skipIf(not SUPPORTS_FCOPYFILE, + 'os._fcopyfile() not supported (OSX only)') +class TestCopyFileFCopyFile(_ZeroCopyFileTest, unittest.TestCase): + PATCHPOINT = "posix._fcopyfile" + + def zerocopy_fun(self, *args, **kwargs): + return shutil._copyfileobj_fcopyfile(*args, **kwargs) class TermsizeTests(unittest.TestCase): diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index dfdfadca68e6d7..f48f08a714afbe 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -97,6 +97,10 @@ corresponding Unix manual entries for more information on calls."); #include #endif +#if defined(__APPLE__) +#include +#endif + #ifdef HAVE_SCHED_H #include #endif @@ -8760,6 +8764,15 @@ static PyObject * os__fcopyfile_impl(PyObject *module, int infd, int outfd) /*[clinic end generated code: output=3e629d5c50b33d04 input=565c8d0191b573b8]*/ { + // copyfile() source code: + // https://opensource.apple.com/source/copyfile/copyfile-42/copyfile.c + int ret; + + Py_BEGIN_ALLOW_THREADS + ret = fcopyfile(infd, outfd, NULL, COPYFILE_DATA); + Py_END_ALLOW_THREADS + if (ret < 0) + return PyErr_SetFromErrno(PyExc_OSError); Py_RETURN_NONE; } #endif From abf3ecbbd9bf6c87f8ab3e1de51ab295556f48e2 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 02:35:18 +0200 Subject: [PATCH 038/111] arg clinic for the win implementation --- Modules/clinic/posixmodule.c.h | 41 +++++++++++++++++++++++++++++++++- Modules/posixmodule.c | 18 +++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index fc14d32e3c5fea..7914d96a4e65fe 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3854,6 +3854,7 @@ os_write(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } #if defined(__APPLE__) + PyDoc_STRVAR(os__fcopyfile__doc__, "_fcopyfile($module, infd, outfd, /)\n" "--\n" @@ -3882,8 +3883,42 @@ os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } + #endif /* defined(__APPLE__) */ +#if (defined MS_WINDOWS) + +PyDoc_STRVAR(os__win32copyfile__doc__, +"_win32copyfile($module, src, dst, /)\n" +"--\n" +"\n" +"Efficiently copy 2 files (Windows only)."); + +#define OS__WIN32COPYFILE_METHODDEF \ + {"_win32copyfile", (PyCFunction)os__win32copyfile, METH_FASTCALL, os__win32copyfile__doc__}, + +static PyObject * +os__win32copyfile_impl(PyObject *module, const char *src, const char *dst); + +static PyObject * +os__win32copyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + const char *src; + const char *dst; + + if (!_PyArg_ParseStack(args, nargs, "ss:_win32copyfile", + &src, &dst)) { + goto exit; + } + return_value = os__win32copyfile_impl(module, src, dst); + +exit: + return return_value; +} + +#endif /* (defined MS_WINDOWS) */ + PyDoc_STRVAR(os_fstat__doc__, "fstat($module, /, fd)\n" "--\n" @@ -6449,6 +6484,10 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #define OS__FCOPYFILE_METHODDEF #endif /* !defined(OS__FCOPYFILE_METHODDEF) */ +#ifndef OS__WIN32COPYFILE_METHODDEF + #define OS__WIN32COPYFILE_METHODDEF +#endif /* !defined(OS__WIN32COPYFILE_METHODDEF) */ + #ifndef OS_PIPE_METHODDEF #define OS_PIPE_METHODDEF #endif /* !defined(OS_PIPE_METHODDEF) */ @@ -6624,4 +6663,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=b2a03113a42546f5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=38eb73468965ae35 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index f48f08a714afbe..e3a6ad7963e371 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8778,6 +8778,23 @@ os__fcopyfile_impl(PyObject *module, int infd, int outfd) #endif +#if defined MS_WINDOWS +/*[clinic input] +os._win32copyfile + + src: str + dst: str + / + +Efficiently copy 2 files (Windows only). +[clinic start generated code]*/ + +static PyObject * +os__win32copyfile_impl(PyObject *module, const char *src, const char *dst) +/*[clinic end generated code: output=029c1ab7b3f166be input=38732bd67889b557]*/ +#endif + + /*[clinic input] os.fstat @@ -12955,6 +12972,7 @@ static PyMethodDef posix_methods[] = { OS_TIMES_METHODDEF OS__EXIT_METHODDEF OS__FCOPYFILE_METHODDEF + OS__WIN32COPYFILE_METHODDEF OS_EXECV_METHODDEF OS_EXECVE_METHODDEF OS_SPAWNV_METHODDEF From 91e492c1003b061a0f9ad235a72df21efc2d90d7 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 09:28:26 +0200 Subject: [PATCH 039/111] convert path type to path_t --- Modules/clinic/posixmodule.c.h | 19 ++++++++++++------- Modules/posixmodule.c | 8 ++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 7914d96a4e65fe..54c5fd6d8803f8 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3898,22 +3898,27 @@ PyDoc_STRVAR(os__win32copyfile__doc__, {"_win32copyfile", (PyCFunction)os__win32copyfile, METH_FASTCALL, os__win32copyfile__doc__}, static PyObject * -os__win32copyfile_impl(PyObject *module, const char *src, const char *dst); +os__win32copyfile_impl(PyObject *module, path_t *src, path_t *dst); static PyObject * os__win32copyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - const char *src; - const char *dst; + path_t src = PATH_T_INITIALIZE("_win32copyfile", "src", 0, 0); + path_t dst = PATH_T_INITIALIZE("_win32copyfile", "dst", 0, 0); - if (!_PyArg_ParseStack(args, nargs, "ss:_win32copyfile", - &src, &dst)) { + if (!_PyArg_ParseStack(args, nargs, "O&O&:_win32copyfile", + path_converter, &src, path_converter, &dst)) { goto exit; } - return_value = os__win32copyfile_impl(module, src, dst); + return_value = os__win32copyfile_impl(module, &src, &dst); exit: + /* Cleanup for src */ + path_cleanup(&src); + /* Cleanup for dst */ + path_cleanup(&dst); + return return_value; } @@ -6663,4 +6668,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=38eb73468965ae35 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=14b6048140cfc105 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e3a6ad7963e371..06f79a23367018 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8782,16 +8782,16 @@ os__fcopyfile_impl(PyObject *module, int infd, int outfd) /*[clinic input] os._win32copyfile - src: str - dst: str + src: path_t + dst: path_t / Efficiently copy 2 files (Windows only). [clinic start generated code]*/ static PyObject * -os__win32copyfile_impl(PyObject *module, const char *src, const char *dst) -/*[clinic end generated code: output=029c1ab7b3f166be input=38732bd67889b557]*/ +os__win32copyfile_impl(PyObject *module, path_t *src, path_t *dst) +/*[clinic end generated code: output=9df245926c468843 input=00817871f5770bdc]*/ #endif From e02c69def2296577ad5799dc7748d6f1f037d7fd Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 00:37:09 -0700 Subject: [PATCH 040/111] expose CopyFileW --- Modules/posixmodule.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 06f79a23367018..ea7ef4c8af2963 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8792,6 +8792,13 @@ Efficiently copy 2 files (Windows only). static PyObject * os__win32copyfile_impl(PyObject *module, path_t *src, path_t *dst) /*[clinic end generated code: output=9df245926c468843 input=00817871f5770bdc]*/ +{ + if (CopyFileW(src->wide, dst->wide, FALSE) == 0) { + win32_error_object("_win32copyfile", src->object); + return NULL; + } + Py_RETURN_NONE; +} #endif From 73837e229b5201b66ab28623c686de96df14ef0d Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 01:01:52 -0700 Subject: [PATCH 041/111] fix windows tests --- Lib/shutil.py | 12 ++++++++++++ Lib/test/test_shutil.py | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Lib/shutil.py b/Lib/shutil.py index e387eca482d1f7..2820b1d0984926 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -47,6 +47,11 @@ except ImportError: posix = None +try: + import nt +except ImportError: + nt = None + _HAS_LINUX_SENDFILE = hasattr(os, "sendfile") and \ sys.platform.startswith("linux") _HAS_FCOPYFILE = hasattr(posix, "_fcopyfile") @@ -157,6 +162,9 @@ def _copyfileobj_sendfile(fsrc, fdst): break # EOF offset += sent +def _win32_copyfile(src, dst): + nt._win32copyfile(src, dst) + def _copyfileobj2(fsrc, fdst): # Copies 2 filesystem files by using zero-copy sendfile(2) syscall # (faster). This is used by copyfile(), copy() and copy2() in order @@ -219,6 +227,10 @@ def copyfile(src, dst, *, follow_symlinks=True): if not follow_symlinks and os.path.islink(src): os.symlink(os.readlink(src), dst) else: + if os.name == 'nt': + _win32_copyfile(src, dst) + return dst + with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: _copyfileobj2(fsrc, fdst) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index e85ba6b8f4b8f4..a18afd9c71120f 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1807,6 +1807,7 @@ def _open(filename, mode='r'): self.assertRaises(OSError, shutil.copyfile, 'srcfile', 'destfile') + @unittest.skipIf(os.name == 'nt', "skipped on Windows") def test_w_dest_open_fails(self): srcfile = self.Faux() @@ -1826,6 +1827,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot open "destfile"',)) + @unittest.skipIf(os.name == 'nt', "skipped on Windows") def test_w_dest_close_fails(self): srcfile = self.Faux() @@ -1848,6 +1850,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot close',)) + @unittest.skipIf(os.name == 'nt', "skipped on Windows") def test_w_source_close_fails(self): srcfile = self.Faux(True) @@ -1924,6 +1927,7 @@ def test_regular_copy(self): self.zerocopy_fun(src, dst) self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + @unittest.skipIf(os.name == 'nt', 'POSIX only') def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: @@ -1933,6 +1937,7 @@ def test_non_regular_file_src(self): self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + @unittest.skipIf(os.name == 'nt', 'POSIX only') def test_non_regular_file_dst(self): with open(TESTFN, "rb") as src: with io.BytesIO() as dst: @@ -1942,6 +1947,12 @@ def test_non_regular_file_dst(self): dst.seek(0) self.assertEqual(dst.read(), self.FILEDATA) + def test_non_existent_src(self): + name = tempfile.mktemp() + with self.assertRaises(FileNotFoundError) as cm: + shutil.copyfile(name, "new") + self.assertEqual(cm.exception.filename, name) + def test_empty_file(self): srcname = TESTFN + 'src' dstname = TESTFN + 'dst' @@ -1962,6 +1973,7 @@ def test_unhandled_exception(self): self.assertRaises(ZeroDivisionError, shutil.copyfile, TESTFN, TESTFN2) + @unittest.skipIf(os.name == 'nt', 'POSIX only') def test_exception_on_first_call(self): # Emulate a case where the first call to the zero-copy # function raises an exception in which case the function is @@ -2070,6 +2082,14 @@ def zerocopy_fun(self, *args, **kwargs): return shutil._copyfileobj_fcopyfile(*args, **kwargs) +@unittest.skipIf(not os.name == 'nt', 'Windows only') +class TestWindowsCopyFile(_ZeroCopyFileTest, unittest.TestCase): + PATCHPOINT = "nt._win32copyfile" + + def zerocopy_fun(self, src, dst): + return shutil._win32_copyfile(src.name, dst.name) + + class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): """Check if get_terminal_size() returns a meaningful value. From 28be4c1023c8eaf5842d2e8514805faf00d7b0c1 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 01:23:03 -0700 Subject: [PATCH 042/111] release GIL --- Modules/posixmodule.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index ea7ef4c8af2963..72e0d801976483 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8793,7 +8793,12 @@ static PyObject * os__win32copyfile_impl(PyObject *module, path_t *src, path_t *dst) /*[clinic end generated code: output=9df245926c468843 input=00817871f5770bdc]*/ { - if (CopyFileW(src->wide, dst->wide, FALSE) == 0) { + int ret; + + Py_BEGIN_ALLOW_THREADS + ret = CopyFileW(src->wide, dst->wide, FALSE); + Py_END_ALLOW_THREADS + if (ret == 0) { win32_error_object("_win32copyfile", src->object); return NULL; } From 6c59adfcf8292a0e5ae0a0e2f54748b1995452eb Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 01:26:32 -0700 Subject: [PATCH 043/111] minor refactoring --- Modules/posixmodule.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 72e0d801976483..8e01a0d7537e40 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8798,10 +8798,8 @@ os__win32copyfile_impl(PyObject *module, path_t *src, path_t *dst) Py_BEGIN_ALLOW_THREADS ret = CopyFileW(src->wide, dst->wide, FALSE); Py_END_ALLOW_THREADS - if (ret == 0) { - win32_error_object("_win32copyfile", src->object); - return NULL; - } + if (ret == 0) + return win32_error_object("_win32copyfile", src->object); Py_RETURN_NONE; } #endif From 700629d07266f5c2d11834782759866a0874f533 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 14:30:11 +0200 Subject: [PATCH 044/111] update doc --- Doc/library/shutil.rst | 25 ++++++++++++++----- Doc/whatsnew/3.8.rst | 6 ----- .../2018-05-25-01-41-00.bpo-33639.xkY4tq.rst | 5 ---- 3 files changed, 19 insertions(+), 17 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index fad3e5ce6b6fe5..158a1fc84a07bd 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -75,8 +75,10 @@ Directory and files operations a subclass of the latter, this change is backward compatible. .. versionchanged:: 3.8 - Uses high-performance :func:`os.sendfile` if available and supports - file-to-file copy (namely Linux). + Use high-performance zero-copy syscalls to copy the file: + :func:`os.sendfile` on Linux, `fcopyfile`_ on OSX and `CopyFile`_ on + Windows. If the copy fails and no data was copied fallback on using + :func:`copyfileobj`. .. exception:: SameFileError @@ -167,8 +169,10 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Uses high-performance :func:`os.sendfile` if available and supports - file-to-file copy (namely Linux). + Use high-performance zero-copy syscalls to copy the file: + :func:`os.sendfile` on Linux, `fcopyfile`_ on OSX and `CopyFile`_ on + Windows. If the copy fails and no data was copied fallback on using + :func:`copyfileobj`. .. function:: copy2(src, dst, *, follow_symlinks=True) @@ -193,8 +197,11 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Uses high-performance :func:`os.sendfile` if available and supports - file-to-file copy (namely Linux). + Use high-performance zero-copy syscalls to copy the file: + :func:`os.sendfile` on Linux, `fcopyfile`_ on OSX and `CopyFile`_ on + Windows. If the copy fails and no data was copied fallback on using + :func:`copyfileobj`. + .. function:: ignore_patterns(\*patterns) @@ -668,3 +675,9 @@ Querying the size of the output terminal .. _`Other Environment Variables`: http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html#tag_002_003 +.. _`fcopyfile`: + http://www.manpagez.com/man/3/fcopyfile/osx-10.5.php + +.. _`CopyFile`: + https://msdn.microsoft.com/en-us/library/windows/desktop/aa363851(v=vs.85).aspx + diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 42ec12988a2d69..9aad908f927f84 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -98,12 +98,6 @@ Optimizations first introduced in Python 3.4. It offers better performance and smaller size compared to Protocol 3 available since Python 3.0. -* :func:`shutil.copyfile`, :func:`shutil.copy` and :func:`shutil.copy2` use - high-performance :func:`os.sendfile` if available and supports file-to-file - copy (namely Linux) resulting in roughly a 20-25% speedup of the copying - operation and a considerably lower CPU cycles consumption. - (Contributed by Giampaolo Rodola' and desbma in :issue:`33639`) - Build and C API Changes ======================= diff --git a/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst b/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst deleted file mode 100644 index b4b4a0c32db5d0..00000000000000 --- a/Misc/NEWS.d/next/Library/2018-05-25-01-41-00.bpo-33639.xkY4tq.rst +++ /dev/null @@ -1,5 +0,0 @@ -shutil.copyfile(), shutil.copy() and shutil.copy2() use high-performance -os.sendfile() if available and supports file-to-file copy (namely Linux) -resulting in roughly a 20-25% speedup of the copying operation and a -considerably lower CPU cycles consumption. -(Contributed by Giampaolo Rodola' and desbma in 33639) From 077912e404ef718a5847537e25791d2411cf0833 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 15:51:24 +0200 Subject: [PATCH 045/111] update comment --- Lib/shutil.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 2820b1d0984926..5b399046e98cea 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -166,13 +166,13 @@ def _win32_copyfile(src, dst): nt._win32copyfile(src, dst) def _copyfileobj2(fsrc, fdst): - # Copies 2 filesystem files by using zero-copy sendfile(2) syscall - # (faster). This is used by copyfile(), copy() and copy2() in order - # to leave copyfileobj() alone and not introduce any unexpected - # breakage. Possible risks by using sendfile() in copyfileobj() are: + # Copies 2 filesystem files by using zero-copy sendfile(2) (Linux) + # or fcopyfile(2) (OSX). This is used by copyfile(), copy() and + # copy2() in order to leave copyfileobj() alone and not introduce + # any unexpected breakage. Possible risks by using zero-copy calls + # in copyfileobj() are: # - fdst cannot be open in "a"(ppend) mode # - fsrc and fdst may be opened in text mode - # - fdst offset doesn't get updated # - fsrc may be a BufferedReader (which hides unread data in a buffer), # GzipFile (which decompresses data), HTTPResponse (which decodes # chunks). From 62c6568a9afe2bd7d66421636117b56d14850e28 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 16:39:07 +0200 Subject: [PATCH 046/111] update docstrings --- Lib/shutil.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 5b399046e98cea..abb3b69b184799 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -52,8 +52,7 @@ except ImportError: nt = None -_HAS_LINUX_SENDFILE = hasattr(os, "sendfile") and \ - sys.platform.startswith("linux") +_HAS_SENDFILE = hasattr(os, "sendfile") _HAS_FCOPYFILE = hasattr(posix, "_fcopyfile") __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", @@ -118,10 +117,10 @@ def _copyfileobj_fcopyfile(fsrc, fdst): def _copyfileobj_sendfile(fsrc, fdst): """Copy data from one regular file object to another by using - high-performance sendfile() method. Linux >= 2.6.33 is apparently - the only platform able to do this. + high-performance sendfile() method. + This should work on Linux >= 2.6.33 and Solaris only. """ - global _HAS_LINUX_SENDFILE + global _HAS_SENDFILE try: infd = fsrc.fileno() outfd = fdst.fileno() @@ -147,7 +146,8 @@ def _copyfileobj_sendfile(fsrc, fdst): # sendfile() on this platform (probably Linux < 2.6.33) # does not support copies between regular files (only # sockets). - _HAS_LINUX_SENDFILE = False + _HAS_SENDFILE = False + raise _GiveupOnZeroCopy(err) if err.errno == errno.ENOSPC: # filesystem is full raise err from None @@ -163,21 +163,24 @@ def _copyfileobj_sendfile(fsrc, fdst): offset += sent def _win32_copyfile(src, dst): + """Uses zero-copy CopyFileW.""" nt._win32copyfile(src, dst) def _copyfileobj2(fsrc, fdst): - # Copies 2 filesystem files by using zero-copy sendfile(2) (Linux) - # or fcopyfile(2) (OSX). This is used by copyfile(), copy() and - # copy2() in order to leave copyfileobj() alone and not introduce - # any unexpected breakage. Possible risks by using zero-copy calls + """Copies 2 regular mmap-like fds by using zero-copy sendfile(2) + (Linux) and fcopyfile(2) (OSX). Fallback on using plain read()/write() + copy on error and in case no data was written. + """ + # Note: copyfileobj() is left alone in order to not introduce any + # unexpected breakage. Possible risks by using zero-copy calls # in copyfileobj() are: # - fdst cannot be open in "a"(ppend) mode # - fsrc and fdst may be opened in text mode # - fsrc may be a BufferedReader (which hides unread data in a buffer), # GzipFile (which decompresses data), HTTPResponse (which decodes # chunks). - # - possibly others... - if _HAS_LINUX_SENDFILE: + # - possibly others + if _HAS_SENDFILE: try: return _copyfileobj_sendfile(fsrc, fdst) except _GiveupOnZeroCopy: From a40a7554427a21e78184bf80e157cae8cad94a52 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 16:50:20 +0200 Subject: [PATCH 047/111] rename functions --- Lib/shutil.py | 29 +++++++++++++++-------------- Lib/test/test_shutil.py | 14 +++++++------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index abb3b69b184799..2a13e0f289d52b 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -86,8 +86,9 @@ class RegistryError(Exception): and unpacking registries fails""" class _GiveupOnZeroCopy(Exception): - """Raised when os.sendfile() cannot be used for copying files.""" - + """Raised as a signal to fallback on using raw file copy + when zero-copy functions fail to do so. + """ def copyfileobj(fsrc, fdst, length=16*1024): """copy data from file-like object fsrc to file-like object fdst""" @@ -97,9 +98,9 @@ def copyfileobj(fsrc, fdst, length=16*1024): break fdst.write(buf) -def _copyfileobj_fcopyfile(fsrc, fdst): - """Copy data from one regular file object to another by using - high-performance fcopyfile() syscall (OSX only). +def _zerocopy_osx(fsrc, fdst): + """Copy 2 regular mmap-like files another by using high-performance + fcopyfile() syscall (OSX only). """ try: infd = fsrc.fileno() @@ -115,8 +116,12 @@ def _copyfileobj_fcopyfile(fsrc, fdst): else: raise err from None -def _copyfileobj_sendfile(fsrc, fdst): - """Copy data from one regular file object to another by using +def _zerocopy_win(src, dst): + """Copy 2 files by using high-performance CopyFileW (Windows only).""" + nt._win32copyfile(src, dst) + +def _zerocopy_sendfile(fsrc, fdst): + """Copy data from one regular mmap-like fd to another by using high-performance sendfile() method. This should work on Linux >= 2.6.33 and Solaris only. """ @@ -162,10 +167,6 @@ def _copyfileobj_sendfile(fsrc, fdst): break # EOF offset += sent -def _win32_copyfile(src, dst): - """Uses zero-copy CopyFileW.""" - nt._win32copyfile(src, dst) - def _copyfileobj2(fsrc, fdst): """Copies 2 regular mmap-like fds by using zero-copy sendfile(2) (Linux) and fcopyfile(2) (OSX). Fallback on using plain read()/write() @@ -182,13 +183,13 @@ def _copyfileobj2(fsrc, fdst): # - possibly others if _HAS_SENDFILE: try: - return _copyfileobj_sendfile(fsrc, fdst) + return _zerocopy_sendfile(fsrc, fdst) except _GiveupOnZeroCopy: pass if _HAS_FCOPYFILE: try: - return _copyfileobj_fcopyfile(fsrc, fdst) + return _zerocopy_osx(fsrc, fdst) except _GiveupOnZeroCopy: pass @@ -231,7 +232,7 @@ def copyfile(src, dst, *, follow_symlinks=True): os.symlink(os.readlink(src), dst) else: if os.name == 'nt': - _win32_copyfile(src, dst) + _zerocopy_win(src, dst) return dst with open(src, 'rb') as fsrc: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index a18afd9c71120f..d343d520034257 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1998,7 +1998,7 @@ class TestCopyFileObjSendfile(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "os.sendfile" def zerocopy_fun(self, *args, **kwargs): - return shutil._copyfileobj_sendfile(*args, **kwargs) + return shutil._zerocopy_sendfile(*args, **kwargs) def test_exception_on_second_call(self): def sendfile(*args, **kwargs): @@ -2014,7 +2014,7 @@ def sendfile(*args, **kwargs): side_effect=sendfile): with self.get_files() as (src, dst): with self.assertRaises(OSError) as cm: - shutil._copyfileobj_sendfile(src, dst) + shutil._zerocopy_sendfile(src, dst) assert flag self.assertEqual(cm.exception.errno, errno.EBADF) @@ -2024,7 +2024,7 @@ def test_cant_get_size(self): # sendfile() will be called repeatedly. with unittest.mock.patch('os.fstat', side_effect=OSError) as m: with self.get_files() as (src, dst): - shutil._copyfileobj_sendfile(src, dst) + shutil._zerocopy_sendfile(src, dst) assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) @@ -2037,7 +2037,7 @@ def test_small_chunks(self): mock.st_size = 65536 + 1 with unittest.mock.patch('os.fstat', return_value=mock) as m: with self.get_files() as (src, dst): - shutil._copyfileobj_sendfile(src, dst) + shutil._zerocopy_sendfile(src, dst) assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) @@ -2050,7 +2050,7 @@ def test_big_chunk(self): mock.st_size = self.FILESIZE + (100 * 1024 * 1024) with unittest.mock.patch('os.fstat', return_value=mock) as m: with self.get_files() as (src, dst): - shutil._copyfileobj_sendfile(src, dst) + shutil._zerocopy_sendfile(src, dst) assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) @@ -2079,7 +2079,7 @@ class TestCopyFileFCopyFile(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "posix._fcopyfile" def zerocopy_fun(self, *args, **kwargs): - return shutil._copyfileobj_fcopyfile(*args, **kwargs) + return shutil._zerocopy_osx(*args, **kwargs) @unittest.skipIf(not os.name == 'nt', 'Windows only') @@ -2087,7 +2087,7 @@ class TestWindowsCopyFile(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "nt._win32copyfile" def zerocopy_fun(self, src, dst): - return shutil._win32_copyfile(src.name, dst.name) + return shutil._zerocopy_win(src.name, dst.name) class TermsizeTests(unittest.TestCase): From 7ba0085e4d9f5980c125d26148fde7d483f26cec Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 17:19:31 +0200 Subject: [PATCH 048/111] rename test classes --- Lib/test/test_shutil.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index d343d520034257..9774041974abff 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -33,7 +33,7 @@ from test.support import TESTFN, FakePath TESTFN2 = TESTFN + "2" -SUPPORTS_FCOPYFILE = hasattr(posix, "_fcopyfile") +HAS_OSX_ZEROCOPY = hasattr(posix, "_fcopyfile") try: import grp @@ -1994,7 +1994,7 @@ def test_filesystem_full(self): @unittest.skipIf(not SUPPORTS_SENDFILE, 'os.sendfile() not supported') -class TestCopyFileObjSendfile(_ZeroCopyFileTest, unittest.TestCase): +class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "os.sendfile" def zerocopy_fun(self, *args, **kwargs): @@ -2073,9 +2073,9 @@ def test_blocksize_arg(self): self.assertEqual(blocksize, 2 ** 23) -@unittest.skipIf(not SUPPORTS_FCOPYFILE, +@unittest.skipIf(not HAS_OSX_ZEROCOPY, 'os._fcopyfile() not supported (OSX only)') -class TestCopyFileFCopyFile(_ZeroCopyFileTest, unittest.TestCase): +class TestZeroCopyOSX(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "posix._fcopyfile" def zerocopy_fun(self, *args, **kwargs): @@ -2083,7 +2083,7 @@ def zerocopy_fun(self, *args, **kwargs): @unittest.skipIf(not os.name == 'nt', 'Windows only') -class TestWindowsCopyFile(_ZeroCopyFileTest, unittest.TestCase): +class TestZeroCopyWindows(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "nt._win32copyfile" def zerocopy_fun(self, src, dst): From 6c96d97eebf56d2261765b3ea9f450ba24083052 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 17:43:11 +0200 Subject: [PATCH 049/111] update doc --- Doc/library/shutil.rst | 36 +++++++++++++++++++++++------------- Doc/whatsnew/3.8.rst | 5 +++++ Lib/shutil.py | 13 +++++++------ 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 158a1fc84a07bd..e30f583b21b062 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -75,10 +75,10 @@ Directory and files operations a subclass of the latter, this change is backward compatible. .. versionchanged:: 3.8 - Use high-performance zero-copy syscalls to copy the file: - :func:`os.sendfile` on Linux, `fcopyfile`_ on OSX and `CopyFile`_ on - Windows. If the copy fails and no data was copied fallback on using - :func:`copyfileobj`. + Use platform-specific zero-copy syscalls on Linux, OSX and Windows in + order to copy the file more efficiently. + If the zero-copy operation fails and no data was written in *dst* + silently fallback on using less efficient :func:`copyfileobj` internally. .. exception:: SameFileError @@ -169,10 +169,10 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Use high-performance zero-copy syscalls to copy the file: - :func:`os.sendfile` on Linux, `fcopyfile`_ on OSX and `CopyFile`_ on - Windows. If the copy fails and no data was copied fallback on using - :func:`copyfileobj`. + Use platform-specific zero-copy syscalls on Linux, OSX and Windows in + order to copy the file more efficiently. + If the zero-copy operation fails and no data was written in *dst* + silently fallback on using less efficient :func:`copyfileobj` internally. .. function:: copy2(src, dst, *, follow_symlinks=True) @@ -197,11 +197,10 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Use high-performance zero-copy syscalls to copy the file: - :func:`os.sendfile` on Linux, `fcopyfile`_ on OSX and `CopyFile`_ on - Windows. If the copy fails and no data was copied fallback on using - :func:`copyfileobj`. - + Use platform-specific zero-copy syscalls on Linux, OSX and Windows in + order to copy the file more efficiently. + If the zero-copy operation fails and no data was written in *dst* + silently fallback on using less efficient :func:`copyfileobj` internally. .. function:: ignore_patterns(\*patterns) @@ -259,6 +258,11 @@ Directory and files operations Added the *ignore_dangling_symlinks* argument to silent dangling symlinks errors when *symlinks* is false. + .. versionchanged:: 3.8 + Use platform-specific zero-copy syscalls on Linux, OSX and Windows in + order to copy the file more efficiently. + If the zero-copy operation fails and no data was written in *dst* + silently fallback on using less efficient :func:`copyfileobj` internally. .. function:: rmtree(path, ignore_errors=False, onerror=None) @@ -332,6 +336,12 @@ Directory and files operations .. versionchanged:: 3.5 Added the *copy_function* keyword argument. + .. versionchanged:: 3.8 + Use platform-specific zero-copy syscalls on Linux, OSX and Windows in + order to copy the file more efficiently. + If the zero-copy operation fails and no data was written in *dst* + silently fallback on using less efficient :func:`copyfileobj` internally. + .. function:: disk_usage(path) Return disk usage statistics about the given path as a :term:`named tuple` diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 9aad908f927f84..9e7f7c5bd52af4 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -94,6 +94,11 @@ Improved Modules Optimizations ============= +* :func:`shutil.copyfile`, :func:`shutil.copy` and :func:`shutil.copy2` use + platform specific zero-copy syscalls on Linux, OSX and Windows in order to + copy the file more efficiently. The average speedup for copying a 512MB file + is +24% on Linux, +50% on OSX and +48% on Windows. + * The default protocol in the :mod:`pickle` module is now Protocol 4, first introduced in Python 3.4. It offers better performance and smaller size compared to Protocol 3 available since Python 3.0. diff --git a/Lib/shutil.py b/Lib/shutil.py index 2a13e0f289d52b..70dd81c8a7f642 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -99,7 +99,7 @@ def copyfileobj(fsrc, fdst, length=16*1024): fdst.write(buf) def _zerocopy_osx(fsrc, fdst): - """Copy 2 regular mmap-like files another by using high-performance + """Copy 2 regular mmap-like files by using high-performance fcopyfile() syscall (OSX only). """ try: @@ -116,9 +116,9 @@ def _zerocopy_osx(fsrc, fdst): else: raise err from None -def _zerocopy_win(src, dst): +def _zerocopy_win(fsrc, fdst): """Copy 2 files by using high-performance CopyFileW (Windows only).""" - nt._win32copyfile(src, dst) + nt._win32copyfile(fsrc, fdst) def _zerocopy_sendfile(fsrc, fdst): """Copy data from one regular mmap-like fd to another by using @@ -168,9 +168,10 @@ def _zerocopy_sendfile(fsrc, fdst): offset += sent def _copyfileobj2(fsrc, fdst): - """Copies 2 regular mmap-like fds by using zero-copy sendfile(2) - (Linux) and fcopyfile(2) (OSX). Fallback on using plain read()/write() - copy on error and in case no data was written. + """Copy 2 regular mmap-like fds by using zero-copy sendfile(2) + (Linux) and fcopyfile(2) (OSX) syscalls. + In case of error fallback on using plain read()/write() if no + data was copied. """ # Note: copyfileobj() is left alone in order to not introduce any # unexpected breakage. Possible risks by using zero-copy calls From 80fbe6e3df6f80d5d3deeb83cb3445dd65b0e2da Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 17:49:10 +0200 Subject: [PATCH 050/111] update doc --- Doc/whatsnew/3.8.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 9e7f7c5bd52af4..82396437de221b 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -90,14 +90,14 @@ New Modules Improved Modules ================ - Optimizations ============= -* :func:`shutil.copyfile`, :func:`shutil.copy` and :func:`shutil.copy2` use - platform specific zero-copy syscalls on Linux, OSX and Windows in order to - copy the file more efficiently. The average speedup for copying a 512MB file - is +24% on Linux, +50% on OSX and +48% on Windows. +* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, + :func:`shutil.copytree` and :func:`shutil.move` use platform specific + zero-copy syscalls on Linux, OSX and Windows in order to copy the file more + efficiently. The average speedup for copying a 512MB file is +24% on Linux, + +50% on OSX and +48% on Windows. * The default protocol in the :mod:`pickle` module is now Protocol 4, first introduced in Python 3.4. It offers better performance and smaller From fdf4bcb470da6ba93225c20cc44cd187f5d7d38b Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 17:59:13 +0200 Subject: [PATCH 051/111] update docstrings and comments --- Doc/library/shutil.rst | 7 ------- Lib/shutil.py | 2 +- Lib/test/test_shutil.py | 9 ++++----- Modules/clinic/posixmodule.c.h | 4 ++-- Modules/posixmodule.c | 4 ++-- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index e30f583b21b062..bac55ab9599cbc 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -684,10 +684,3 @@ Querying the size of the output terminal .. _`Other Environment Variables`: http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html#tag_002_003 - -.. _`fcopyfile`: - http://www.manpagez.com/man/3/fcopyfile/osx-10.5.php - -.. _`CopyFile`: - https://msdn.microsoft.com/en-us/library/windows/desktop/aa363851(v=vs.85).aspx - diff --git a/Lib/shutil.py b/Lib/shutil.py index 70dd81c8a7f642..19026e535c57d3 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -177,7 +177,7 @@ def _copyfileobj2(fsrc, fdst): # unexpected breakage. Possible risks by using zero-copy calls # in copyfileobj() are: # - fdst cannot be open in "a"(ppend) mode - # - fsrc and fdst may be opened in text mode + # - fsrc and fdst may be open in "t"(ext) mode # - fsrc may be a BufferedReader (which hides unread data in a buffer), # GzipFile (which decompresses data), HTTPResponse (which decodes # chunks). diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 9774041974abff..0e0cf2d622181b 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1807,7 +1807,7 @@ def _open(filename, mode='r'): self.assertRaises(OSError, shutil.copyfile, 'srcfile', 'destfile') - @unittest.skipIf(os.name == 'nt', "skipped on Windows") + @unittest.skipIf(os.name == 'nt', "not POSIX") def test_w_dest_open_fails(self): srcfile = self.Faux() @@ -1827,7 +1827,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot open "destfile"',)) - @unittest.skipIf(os.name == 'nt', "skipped on Windows") + @unittest.skipIf(os.name == 'nt', "not POSIX") def test_w_dest_close_fails(self): srcfile = self.Faux() @@ -1850,7 +1850,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot close',)) - @unittest.skipIf(os.name == 'nt', "skipped on Windows") + @unittest.skipIf(os.name == 'nt', "not POSIX") def test_w_source_close_fails(self): srcfile = self.Faux(True) @@ -2073,8 +2073,7 @@ def test_blocksize_arg(self): self.assertEqual(blocksize, 2 ** 23) -@unittest.skipIf(not HAS_OSX_ZEROCOPY, - 'os._fcopyfile() not supported (OSX only)') +@unittest.skipIf(not HAS_OSX_ZEROCOPY, 'OSX only') class TestZeroCopyOSX(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "posix._fcopyfile" diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 54c5fd6d8803f8..6cef1852891ad8 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3859,7 +3859,7 @@ PyDoc_STRVAR(os__fcopyfile__doc__, "_fcopyfile($module, infd, outfd, /)\n" "--\n" "\n" -"Efficiently copy 2 file descriptors (OSX only)."); +"Efficiently copy the content of 2 file descriptors (OSX only)."); #define OS__FCOPYFILE_METHODDEF \ {"_fcopyfile", (PyCFunction)os__fcopyfile, METH_FASTCALL, os__fcopyfile__doc__}, @@ -6668,4 +6668,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=14b6048140cfc105 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6e1b458924d141ed input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 8e01a0d7537e40..83580f1d8cd428 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8757,12 +8757,12 @@ os._fcopyfile outfd: int / -Efficiently copy 2 file descriptors (OSX only). +Efficiently copy the content of 2 file descriptors (OSX only). [clinic start generated code]*/ static PyObject * os__fcopyfile_impl(PyObject *module, int infd, int outfd) -/*[clinic end generated code: output=3e629d5c50b33d04 input=565c8d0191b573b8]*/ +/*[clinic end generated code: output=3e629d5c50b33d04 input=ef4f7667f63d3e42]*/ { // copyfile() source code: // https://opensource.apple.com/source/copyfile/copyfile-42/copyfile.c From 185f1307a2a3aa08e677ddfe050feb6cc039815e Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 28 May 2018 23:43:02 +0200 Subject: [PATCH 052/111] avoid do import nt|posix modules if unnecessary --- Doc/whatsnew/3.8.rst | 3 ++- Lib/shutil.py | 10 ++-------- Lib/test/test_shutil.py | 2 +- .../Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst | 6 ++++++ Modules/clinic/posixmodule.c.h | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 82396437de221b..19d76fcb78a2e5 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -96,8 +96,9 @@ Optimizations * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, :func:`shutil.copytree` and :func:`shutil.move` use platform specific zero-copy syscalls on Linux, OSX and Windows in order to copy the file more - efficiently. The average speedup for copying a 512MB file is +24% on Linux, + efficiently. The speedup for copying a 512MB file is about +26% on Linux, +50% on OSX and +48% on Windows. + (Contributed by Giampaolo Rodola' in :issue:`25427`.) * The default protocol in the :mod:`pickle` module is now Protocol 4, first introduced in Python 3.4. It offers better performance and smaller diff --git a/Lib/shutil.py b/Lib/shutil.py index 19026e535c57d3..7b4d7ecccdc85f 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -42,15 +42,10 @@ except ImportError: getgrnam = None -try: +if os.name == 'posix': import posix -except ImportError: - posix = None - -try: +elif os.name == 'nt': import nt -except ImportError: - nt = None _HAS_SENDFILE = hasattr(os, "sendfile") _HAS_FCOPYFILE = hasattr(posix, "_fcopyfile") @@ -1134,7 +1129,6 @@ def disk_usage(path): elif os.name == 'nt': - import nt __all__.append('disk_usage') _ntuple_diskusage = collections.namedtuple('usage', 'total used free') diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 0e0cf2d622181b..fbb51499aee4bc 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -112,7 +112,7 @@ def rlistdir(path): return res def supports_file2file_sendfile(): - # ...apparently Linux is the only one. + # ...apparently Linux and Solaris are the only ones if not hasattr(os, "sendfile"): return False srcname = None diff --git a/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst new file mode 100644 index 00000000000000..09b4eb557e70a7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst @@ -0,0 +1,6 @@ +:func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, +:func:`shutil.copytree` and :func:`shutil.move` use platform specific zero- +copy syscalls on Linux, OSX and Windows in order to copy the file more +efficiently. The speedup for copying a 512MB file is about +26% on Linux, ++50% on OSX and +48% on Windows. (Contributed by Giampaolo Rodola' in +:issue:`25427`.) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 6cef1852891ad8..6f283129f337fc 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3886,7 +3886,7 @@ os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #endif /* defined(__APPLE__) */ -#if (defined MS_WINDOWS) +#if defined(MS_WINDOWS) PyDoc_STRVAR(os__win32copyfile__doc__, "_win32copyfile($module, src, dst, /)\n" From c8c98ae23d7d1e42cc9a52ce00771160b482a6bb Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 29 May 2018 00:21:07 +0200 Subject: [PATCH 053/111] set nt|posix modules to None if not available --- Lib/shutil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 7b4d7ecccdc85f..47e0e023408f3e 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -42,13 +42,14 @@ except ImportError: getgrnam = None +posix = nt = None if os.name == 'posix': import posix elif os.name == 'nt': import nt _HAS_SENDFILE = hasattr(os, "sendfile") -_HAS_FCOPYFILE = hasattr(posix, "_fcopyfile") +_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", "copytree", "move", "rmtree", "Error", "SpecialFileError", From 17bb5e64f2188c1eb2dc91705451b733d11d159f Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 29 May 2018 00:22:31 +0200 Subject: [PATCH 054/111] micro speedup --- Lib/shutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 47e0e023408f3e..b5142d980ffea2 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -48,7 +48,7 @@ elif os.name == 'nt': import nt -_HAS_SENDFILE = hasattr(os, "sendfile") +_HAS_SENDFILE = posix and hasattr(os, "sendfile") _HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", From d8b9bf9f25a4b9655be20b6a3e9f72c3f35478b9 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 29 May 2018 00:33:24 +0200 Subject: [PATCH 055/111] update description --- Doc/whatsnew/3.8.rst | 2 +- .../next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 19d76fcb78a2e5..1bb5567c33357a 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -97,7 +97,7 @@ Optimizations :func:`shutil.copytree` and :func:`shutil.move` use platform specific zero-copy syscalls on Linux, OSX and Windows in order to copy the file more efficiently. The speedup for copying a 512MB file is about +26% on Linux, - +50% on OSX and +48% on Windows. + +50% on OSX and +48% on Windows. Also, much less CPU cycles are consumed. (Contributed by Giampaolo Rodola' in :issue:`25427`.) * The default protocol in the :mod:`pickle` module is now Protocol 4, diff --git a/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst index 09b4eb557e70a7..ffaa38ba83312a 100644 --- a/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst +++ b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst @@ -2,5 +2,5 @@ :func:`shutil.copytree` and :func:`shutil.move` use platform specific zero- copy syscalls on Linux, OSX and Windows in order to copy the file more efficiently. The speedup for copying a 512MB file is about +26% on Linux, -+50% on OSX and +48% on Windows. (Contributed by Giampaolo Rodola' in -:issue:`25427`.) ++50% on OSX and +48% on Windows. Also, much less CPU cycles are consumed +(Contributed by Giampaolo Rodola' in :issue:`25427`.) From b59ac572030ddf58dc66c807300abbb7ca49b1f1 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 29 May 2018 01:27:55 +0200 Subject: [PATCH 056/111] add doc note --- Doc/library/shutil.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index bac55ab9599cbc..855561c51c502a 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -51,7 +51,8 @@ Directory and files operations .. function:: copyfile(src, dst, *, follow_symlinks=True) Copy the contents (no metadata) of the file named *src* to a file named - *dst* and return *dst*. *src* and *dst* are path names given as strings. + *dst* and return *dst*. *src* and *dst* are path names given as strings + in the most efficient way possible. *dst* must be the complete target file name; look at :func:`shutil.copy` for a copy that accepts a target directory path. If *src* and *dst* specify the same file, :exc:`SameFileError` is raised. @@ -65,6 +66,13 @@ Directory and files operations a new symbolic link will be created instead of copying the file *src* points to. + .. note:: + Internally platform-specific zero-copy syscalls are used on Linux, OSX + and Windows in order to copy the file more efficiently. + If the zero-copy operation fails and no data was written in *dst* + it will silently fallback on using less efficient :func:`copyfileobj` + internally. + .. versionchanged:: 3.3 :exc:`IOError` used to be raised instead of :exc:`OSError`. Added *follow_symlinks* argument. From 8eefce703c5f7c348287bf41700cc1343ec4ebcf Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 29 May 2018 15:16:15 +0200 Subject: [PATCH 057/111] use better wording in doc --- Doc/library/shutil.rst | 70 +++++++++++++++++++++++++----------------- Doc/whatsnew/3.8.rst | 10 ++++-- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 855561c51c502a..8012386e3dbf2b 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -51,8 +51,9 @@ Directory and files operations .. function:: copyfile(src, dst, *, follow_symlinks=True) Copy the contents (no metadata) of the file named *src* to a file named - *dst* and return *dst*. *src* and *dst* are path names given as strings - in the most efficient way possible. + *dst* and return *dst* in the most efficient way possible. + *src* and *dst* are path names given as strings. + *dst* must be the complete target file name; look at :func:`shutil.copy` for a copy that accepts a target directory path. If *src* and *dst* specify the same file, :exc:`SameFileError` is raised. @@ -66,13 +67,6 @@ Directory and files operations a new symbolic link will be created instead of copying the file *src* points to. - .. note:: - Internally platform-specific zero-copy syscalls are used on Linux, OSX - and Windows in order to copy the file more efficiently. - If the zero-copy operation fails and no data was written in *dst* - it will silently fallback on using less efficient :func:`copyfileobj` - internally. - .. versionchanged:: 3.3 :exc:`IOError` used to be raised instead of :exc:`OSError`. Added *follow_symlinks* argument. @@ -83,10 +77,9 @@ Directory and files operations a subclass of the latter, this change is backward compatible. .. versionchanged:: 3.8 - Use platform-specific zero-copy syscalls on Linux, OSX and Windows in - order to copy the file more efficiently. - If the zero-copy operation fails and no data was written in *dst* - silently fallback on using less efficient :func:`copyfileobj` internally. + Platform-specific zero-copy syscalls are used internally in order to copy + the file more efficiently. See + `platform-dependent efficient copy operations`_ section. .. exception:: SameFileError @@ -177,10 +170,9 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Use platform-specific zero-copy syscalls on Linux, OSX and Windows in - order to copy the file more efficiently. - If the zero-copy operation fails and no data was written in *dst* - silently fallback on using less efficient :func:`copyfileobj` internally. + Platform-specific zero-copy syscalls are used internally in order to copy + the file more efficiently. See + `platform-dependent efficient copy operations`_ section. .. function:: copy2(src, dst, *, follow_symlinks=True) @@ -205,10 +197,9 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Use platform-specific zero-copy syscalls on Linux, OSX and Windows in - order to copy the file more efficiently. - If the zero-copy operation fails and no data was written in *dst* - silently fallback on using less efficient :func:`copyfileobj` internally. + Platform-specific zero-copy syscalls are used internally in order to copy + the file more efficiently. See + `platform-dependent efficient copy operations`_ section. .. function:: ignore_patterns(\*patterns) @@ -267,10 +258,9 @@ Directory and files operations errors when *symlinks* is false. .. versionchanged:: 3.8 - Use platform-specific zero-copy syscalls on Linux, OSX and Windows in - order to copy the file more efficiently. - If the zero-copy operation fails and no data was written in *dst* - silently fallback on using less efficient :func:`copyfileobj` internally. + Platform-specific zero-copy syscalls are used internally in order to copy + the file more efficiently. See + `platform-dependent efficient copy operations`_ section. .. function:: rmtree(path, ignore_errors=False, onerror=None) @@ -345,10 +335,9 @@ Directory and files operations Added the *copy_function* keyword argument. .. versionchanged:: 3.8 - Use platform-specific zero-copy syscalls on Linux, OSX and Windows in - order to copy the file more efficiently. - If the zero-copy operation fails and no data was written in *dst* - silently fallback on using less efficient :func:`copyfileobj` internally. + Platform-specific zero-copy syscalls are used internally in order to copy + the file more efficiently. See + `platform-dependent efficient copy operations`_ section. .. function:: disk_usage(path) @@ -406,6 +395,26 @@ Directory and files operations operation. For :func:`copytree`, the exception argument is a list of 3-tuples (*srcname*, *dstname*, *exception*). +.. _shutil-platform-dependent-efficient-copy-operations: + +Platform-dependent efficient copy operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Starting from Python 3.8 :func:`copyfile` uses platform-specific "zero-copy" +syscalls such as :func:`os.sendfile` in order to copy the file more efficiently +(see `bpo-33671 `_). +"zero-copy" means that the copying operation occurs within the kernel, avoiding +the use of userspace buffers as in "``outfd.write(infd.read())``". +Such platforms are OSX, Windows and POSIX systems where :func:`os.sendfile` +accepts 2 regular fds (namely Linux and Solaris). +If the zero-copy operation fails and no data was written in the destination +file then :func:`copyfile` will silently fallback on using less efficient +:func:`copyfileobj` function internally. +All functions relying on :func:`copyfile` will benefit from the same speedup. +These are :func:`shutil.copy`, :func:`shutil.copy2`, :func:`shutil.copytree` +and :func:`shutil.move`. + +.. versionadded:: 3.8 .. _shutil-copytree-example: @@ -692,3 +701,6 @@ Querying the size of the output terminal .. _`Other Environment Variables`: http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html#tag_002_003 + +.. _`platform-dependent efficient copy operations`: + shutil-platform-dependent-efficient-copy-operations_ diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 1bb5567c33357a..752859eb1e7cc6 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -95,9 +95,13 @@ Optimizations * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, :func:`shutil.copytree` and :func:`shutil.move` use platform specific - zero-copy syscalls on Linux, OSX and Windows in order to copy the file more - efficiently. The speedup for copying a 512MB file is about +26% on Linux, - +50% on OSX and +48% on Windows. Also, much less CPU cycles are consumed. + "zero-copy" syscalls such as :func:`os.sendfile` in order to copy the file + more efficiently. + "zero-copy" means that the copying operation occurs within the kernel, + avoiding the use of userspace buffers as in "``outfd.write(infd.read())``". + The speedup for copying a 512MB file within the same partition is about +26% + on Linux, +50% on OSX and +48% on Windows. Also, much less CPU cycles are + consumed. (Contributed by Giampaolo Rodola' in :issue:`25427`.) * The default protocol in the :mod:`pickle` module is now Protocol 4, From 3048e3d61fd1f6e2d4b3b4df8a9840edafafa752 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 30 May 2018 14:57:11 +0200 Subject: [PATCH 058/111] rename function using 'fastcopy' prefix instead of 'zerocopy' --- Doc/library/shutil.rst | 2 +- Lib/shutil.py | 36 ++++++++++++++++++------------------ Lib/test/test_shutil.py | 22 +++++++++++----------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 8012386e3dbf2b..824027d1bc37c8 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -402,7 +402,7 @@ Platform-dependent efficient copy operations Starting from Python 3.8 :func:`copyfile` uses platform-specific "zero-copy" syscalls such as :func:`os.sendfile` in order to copy the file more efficiently -(see `bpo-33671 `_). +(see :issue:`33671`). "zero-copy" means that the copying operation occurs within the kernel, avoiding the use of userspace buffers as in "``outfd.write(infd.read())``". Such platforms are OSX, Windows and POSIX systems where :func:`os.sendfile` diff --git a/Lib/shutil.py b/Lib/shutil.py index b5142d980ffea2..3bc64083b3234e 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -81,9 +81,9 @@ class RegistryError(Exception): """Raised when a registry operation with the archiving and unpacking registries fails""" -class _GiveupOnZeroCopy(Exception): - """Raised as a signal to fallback on using raw file copy - when zero-copy functions fail to do so. +class _GiveupOnFastCopy(Exception): + """Raised as a signal to fallback on using raw read()/write() + file copy when fast-copy functions fail to do so. """ def copyfileobj(fsrc, fdst, length=16*1024): @@ -94,7 +94,7 @@ def copyfileobj(fsrc, fdst, length=16*1024): break fdst.write(buf) -def _zerocopy_osx(fsrc, fdst): +def _fastcopy_osx(fsrc, fdst): """Copy 2 regular mmap-like files by using high-performance fcopyfile() syscall (OSX only). """ @@ -102,21 +102,21 @@ def _zerocopy_osx(fsrc, fdst): infd = fsrc.fileno() outfd = fdst.fileno() except Exception as err: - raise _GiveupOnZeroCopy(err) # not a regular file + raise _GiveupOnFastCopy(err) # not a regular file try: posix._fcopyfile(infd, outfd) except OSError as err: if err.errno in {errno.EINVAL, errno.ENOTSUP}: - raise _GiveupOnZeroCopy(err) + raise _GiveupOnFastCopy(err) else: raise err from None -def _zerocopy_win(fsrc, fdst): +def _fastcopy_win(fsrc, fdst): """Copy 2 files by using high-performance CopyFileW (Windows only).""" nt._win32copyfile(fsrc, fdst) -def _zerocopy_sendfile(fsrc, fdst): +def _fastcopy_sendfile(fsrc, fdst): """Copy data from one regular mmap-like fd to another by using high-performance sendfile() method. This should work on Linux >= 2.6.33 and Solaris only. @@ -126,7 +126,7 @@ def _zerocopy_sendfile(fsrc, fdst): infd = fsrc.fileno() outfd = fdst.fileno() except Exception as err: - raise _GiveupOnZeroCopy(err) # not a regular file + raise _GiveupOnFastCopy(err) # not a regular file # Hopefully the whole file will be copied in a single call. # sendfile() is called in a loop 'till EOF is reached (0 return) @@ -148,14 +148,14 @@ def _zerocopy_sendfile(fsrc, fdst): # does not support copies between regular files (only # sockets). _HAS_SENDFILE = False - raise _GiveupOnZeroCopy(err) + raise _GiveupOnFastCopy(err) if err.errno == errno.ENOSPC: # filesystem is full raise err from None # Give up on first call and if no data was copied. if offset == 0 and os.lseek(outfd, 0, os.SEEK_CUR) == 0: - raise _GiveupOnZeroCopy(err) + raise _GiveupOnFastCopy(err) raise err from None else: @@ -163,7 +163,7 @@ def _zerocopy_sendfile(fsrc, fdst): break # EOF offset += sent -def _copyfileobj2(fsrc, fdst): +def _fastcopy_fileobj(fsrc, fdst): """Copy 2 regular mmap-like fds by using zero-copy sendfile(2) (Linux) and fcopyfile(2) (OSX) syscalls. In case of error fallback on using plain read()/write() if no @@ -180,14 +180,14 @@ def _copyfileobj2(fsrc, fdst): # - possibly others if _HAS_SENDFILE: try: - return _zerocopy_sendfile(fsrc, fdst) - except _GiveupOnZeroCopy: + return _fastcopy_sendfile(fsrc, fdst) + except _GiveupOnFastCopy: pass if _HAS_FCOPYFILE: try: - return _zerocopy_osx(fsrc, fdst) - except _GiveupOnZeroCopy: + return _fastcopy_osx(fsrc, fdst) + except _GiveupOnFastCopy: pass return copyfileobj(fsrc, fdst) @@ -229,12 +229,12 @@ def copyfile(src, dst, *, follow_symlinks=True): os.symlink(os.readlink(src), dst) else: if os.name == 'nt': - _zerocopy_win(src, dst) + _fastcopy_win(src, dst) return dst with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: - _copyfileobj2(fsrc, fdst) + _fastcopy_fileobj(fsrc, fdst) return dst def copymode(src, dst, *, follow_symlinks=True): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index fbb51499aee4bc..4f6d04c3d7e11e 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -21,7 +21,7 @@ get_archive_formats, Error, unpack_archive, register_unpack_format, RegistryError, unregister_unpack_format, get_unpack_formats, - SameFileError, _GiveupOnZeroCopy) + SameFileError, _GiveupOnFastCopy) import tarfile import zipfile try: @@ -1931,7 +1931,7 @@ def test_regular_copy(self): def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: - with self.assertRaises(_GiveupOnZeroCopy): + with self.assertRaises(_GiveupOnFastCopy): self.zerocopy_fun(src, dst) shutil.copyfileobj(src, dst) @@ -1941,7 +1941,7 @@ def test_non_regular_file_src(self): def test_non_regular_file_dst(self): with open(TESTFN, "rb") as src: with io.BytesIO() as dst: - with self.assertRaises(_GiveupOnZeroCopy): + with self.assertRaises(_GiveupOnFastCopy): self.zerocopy_fun(src, dst) shutil.copyfileobj(src, dst) dst.seek(0) @@ -1981,7 +1981,7 @@ def test_exception_on_first_call(self): with unittest.mock.patch(self.PATCHPOINT, side_effect=OSError(errno.EINVAL, "yo")): with self.get_files() as (src, dst): - with self.assertRaises(_GiveupOnZeroCopy): + with self.assertRaises(_GiveupOnFastCopy): self.zerocopy_fun(src, dst) def test_filesystem_full(self): @@ -1998,7 +1998,7 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "os.sendfile" def zerocopy_fun(self, *args, **kwargs): - return shutil._zerocopy_sendfile(*args, **kwargs) + return shutil._fastcopy_sendfile(*args, **kwargs) def test_exception_on_second_call(self): def sendfile(*args, **kwargs): @@ -2014,7 +2014,7 @@ def sendfile(*args, **kwargs): side_effect=sendfile): with self.get_files() as (src, dst): with self.assertRaises(OSError) as cm: - shutil._zerocopy_sendfile(src, dst) + shutil._fastcopy_sendfile(src, dst) assert flag self.assertEqual(cm.exception.errno, errno.EBADF) @@ -2024,7 +2024,7 @@ def test_cant_get_size(self): # sendfile() will be called repeatedly. with unittest.mock.patch('os.fstat', side_effect=OSError) as m: with self.get_files() as (src, dst): - shutil._zerocopy_sendfile(src, dst) + shutil._fastcopy_sendfile(src, dst) assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) @@ -2037,7 +2037,7 @@ def test_small_chunks(self): mock.st_size = 65536 + 1 with unittest.mock.patch('os.fstat', return_value=mock) as m: with self.get_files() as (src, dst): - shutil._zerocopy_sendfile(src, dst) + shutil._fastcopy_sendfile(src, dst) assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) @@ -2050,7 +2050,7 @@ def test_big_chunk(self): mock.st_size = self.FILESIZE + (100 * 1024 * 1024) with unittest.mock.patch('os.fstat', return_value=mock) as m: with self.get_files() as (src, dst): - shutil._zerocopy_sendfile(src, dst) + shutil._fastcopy_sendfile(src, dst) assert m.called self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) @@ -2078,7 +2078,7 @@ class TestZeroCopyOSX(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "posix._fcopyfile" def zerocopy_fun(self, *args, **kwargs): - return shutil._zerocopy_osx(*args, **kwargs) + return shutil._fastcopy_osx(*args, **kwargs) @unittest.skipIf(not os.name == 'nt', 'Windows only') @@ -2086,7 +2086,7 @@ class TestZeroCopyWindows(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "nt._win32copyfile" def zerocopy_fun(self, src, dst): - return shutil._zerocopy_win(src.name, dst.name) + return shutil._fastcopy_win(src.name, dst.name) class TermsizeTests(unittest.TestCase): From 11102e16ee14e405f6bf08cd99735fd5d997de93 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 30 May 2018 15:02:18 +0200 Subject: [PATCH 059/111] use :ref: in rst doc --- Doc/library/shutil.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 824027d1bc37c8..12eaf7b7f8c9ac 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -79,7 +79,7 @@ Directory and files operations .. versionchanged:: 3.8 Platform-specific zero-copy syscalls are used internally in order to copy the file more efficiently. See - `platform-dependent efficient copy operations`_ section. + :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. exception:: SameFileError @@ -172,7 +172,7 @@ Directory and files operations .. versionchanged:: 3.8 Platform-specific zero-copy syscalls are used internally in order to copy the file more efficiently. See - `platform-dependent efficient copy operations`_ section. + :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: copy2(src, dst, *, follow_symlinks=True) @@ -199,7 +199,7 @@ Directory and files operations .. versionchanged:: 3.8 Platform-specific zero-copy syscalls are used internally in order to copy the file more efficiently. See - `platform-dependent efficient copy operations`_ section. + :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: ignore_patterns(\*patterns) @@ -260,7 +260,7 @@ Directory and files operations .. versionchanged:: 3.8 Platform-specific zero-copy syscalls are used internally in order to copy the file more efficiently. See - `platform-dependent efficient copy operations`_ section. + :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: rmtree(path, ignore_errors=False, onerror=None) @@ -337,7 +337,7 @@ Directory and files operations .. versionchanged:: 3.8 Platform-specific zero-copy syscalls are used internally in order to copy the file more efficiently. See - `platform-dependent efficient copy operations`_ section. + :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: disk_usage(path) From 7545273c7274e42140e4665eeac0d6c2a3faa748 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 30 May 2018 15:19:32 +0200 Subject: [PATCH 060/111] change wording in doc --- Doc/library/shutil.rst | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 12eaf7b7f8c9ac..4aecde0184b235 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -400,17 +400,18 @@ Directory and files operations Platform-dependent efficient copy operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Starting from Python 3.8 :func:`copyfile` uses platform-specific "zero-copy" -syscalls such as :func:`os.sendfile` in order to copy the file more efficiently -(see :issue:`33671`). -"zero-copy" means that the copying operation occurs within the kernel, avoiding -the use of userspace buffers as in "``outfd.write(infd.read())``". -Such platforms are OSX, Windows and POSIX systems where :func:`os.sendfile` -accepts 2 regular fds (namely Linux and Solaris). +Starting from Python 3.8 :func:`copyfile` uses platform-specific "fast-copy" +syscalls in order to copy the file more efficiently (see :issue:`33671`). +"fast-copy" means that the copying operation occurs within the kernel, avoiding +the use of userspace buffers in Python as in "``outfd.write(infd.read())``". +On Linux and Solaris or other POSIX platforms where :func:`os.sendfile` allows +copies between regular file descriptors :func:`os.sendfile` is used. +On Windows and OSX `CopyFile`_ and `fcopyfile`_ are used respectively. If the zero-copy operation fails and no data was written in the destination file then :func:`copyfile` will silently fallback on using less efficient :func:`copyfileobj` function internally. -All functions relying on :func:`copyfile` will benefit from the same speedup. +All functions relying on :func:`copyfile` internally will benefit from the same +speedup. These are :func:`shutil.copy`, :func:`shutil.copy2`, :func:`shutil.copytree` and :func:`shutil.move`. @@ -699,8 +700,11 @@ Querying the size of the output terminal .. versionadded:: 3.3 +.. _`CopyFile`: + https://msdn.microsoft.com/en-us/library/windows/desktop/aa363851(v=vs.85).aspx + +.. _`fcopyfile`: + http://www.manpagez.com/man/3/fcopyfile/ + .. _`Other Environment Variables`: http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html#tag_002_003 - -.. _`platform-dependent efficient copy operations`: - shutil-platform-dependent-efficient-copy-operations_ From 3261b746261a7b0df56f98a3d49c9f66e3cd3dbd Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 30 May 2018 15:59:15 +0200 Subject: [PATCH 061/111] add test to make sure sendfile() doesn't get called aymore in case it doesn't support file to file copies --- Doc/whatsnew/3.8.rst | 8 ++++---- Lib/test/test_shutil.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 752859eb1e7cc6..c8f2bc600e4a1a 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -95,10 +95,10 @@ Optimizations * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, :func:`shutil.copytree` and :func:`shutil.move` use platform specific - "zero-copy" syscalls such as :func:`os.sendfile` in order to copy the file - more efficiently. - "zero-copy" means that the copying operation occurs within the kernel, - avoiding the use of userspace buffers as in "``outfd.write(infd.read())``". + "fast-copy" syscalls in order to copy the file more efficiently. + "fast-copy" means that the copying operation occurs within the kernel, + avoiding the use of userspace buffers in Python as in + "``outfd.write(infd.read())``". The speedup for copying a 512MB file within the same partition is about +26% on Linux, +50% on OSX and +48% on Windows. Also, much less CPU cycles are consumed. diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 4f6d04c3d7e11e..7815359b0d6cf0 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2072,6 +2072,27 @@ def test_blocksize_arg(self): blocksize = m.call_args[0][3] self.assertEqual(blocksize, 2 ** 23) + def test_file2file_not_supported(self): + # Emulate a case where sendfile() only support file->socket + # fds. In such a case copyfile() is supposed to skip the + # fast-copy attempt from then on. + assert shutil._HAS_SENDFILE + try: + with unittest.mock.patch( + self.PATCHPOINT, + side_effect=OSError(errno.ENOTSOCK, "yo")) as m: + with self.get_files() as (src, dst): + with self.assertRaises(_GiveupOnFastCopy): + shutil._fastcopy_sendfile(src, dst) + assert m.called + assert not shutil._HAS_SENDFILE + + with unittest.mock.patch(self.PATCHPOINT) as m: + shutil.copyfile(TESTFN, TESTFN2) + assert not m.called + finally: + shutil._HAS_SENDFILE = True + @unittest.skipIf(not HAS_OSX_ZEROCOPY, 'OSX only') class TestZeroCopyOSX(_ZeroCopyFileTest, unittest.TestCase): From 51c476d0d98b06590c46ef16a229fa663a15b706 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 30 May 2018 08:54:36 -0700 Subject: [PATCH 062/111] move CopyFileW in _winapi and actually expose CopyFileExW instead --- Lib/shutil.py | 3 +- Lib/test/test_shutil.py | 2 +- Modules/_winapi.c | 48 +- Modules/clinic/_winapi.c.h | 1919 ++++++++++++++++---------------- Modules/clinic/posixmodule.c.h | 41 - Modules/posixmodule.c | 28 - 6 files changed, 1025 insertions(+), 1016 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 3bc64083b3234e..7bd4b81d5b681f 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -47,6 +47,7 @@ import posix elif os.name == 'nt': import nt + import _winapi _HAS_SENDFILE = posix and hasattr(os, "sendfile") _HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") @@ -114,7 +115,7 @@ def _fastcopy_osx(fsrc, fdst): def _fastcopy_win(fsrc, fdst): """Copy 2 files by using high-performance CopyFileW (Windows only).""" - nt._win32copyfile(fsrc, fdst) + _winapi.CopyFileExW(fsrc, fdst, 0) def _fastcopy_sendfile(fsrc, fdst): """Copy data from one regular mmap-like fd to another by using diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7815359b0d6cf0..7c23a288936796 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2104,7 +2104,7 @@ def zerocopy_fun(self, *args, **kwargs): @unittest.skipIf(not os.name == 'nt', 'Windows only') class TestZeroCopyWindows(_ZeroCopyFileTest, unittest.TestCase): - PATCHPOINT = "nt._win32copyfile" + PATCHPOINT = "_winapi.CopyFileExW" def zerocopy_fun(self, src, dst): return shutil._fastcopy_win(src.name, dst.name) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index c596cba3cbc360..c11c98ba896570 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -78,6 +78,19 @@ check_CancelIoEx() return has_CancelIoEx; } +static PyObject * +win32_error_object(const char* function, PyObject* filename) +{ + errno = GetLastError(); + if (filename) + return PyErr_SetExcFromWindowsErrWithFilenameObject( + PyExc_OSError, + errno, + filename); + else + return PyErr_SetFromWindowsErr(errno); +} + /* * A Python object wrapping an OVERLAPPED structure and other useful data @@ -163,6 +176,7 @@ create_converter('LPSECURITY_ATTRIBUTES', '" F_POINTER "') create_converter('BOOL', 'i') # F_BOOL used previously (always 'i') create_converter('DWORD', 'k') # F_DWORD is always "k" (which is much shorter) create_converter('LPCTSTR', 's') +create_converter('LPCWSTR', 'u') create_converter('LPWSTR', 'u') create_converter('UINT', 'I') # F_UINT used previously (always 'I') @@ -186,7 +200,7 @@ class DWORD_return_converter(CReturnConverter): data.return_conversion.append( 'return_value = Py_BuildValue("k", _return_value);\n') [python start generated code]*/ -/*[python end generated code: output=da39a3ee5e6b4b0d input=4527052fe06e5823]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=27456f8555228b62]*/ #include "clinic/_winapi.c.h" @@ -1699,6 +1713,37 @@ _winapi_GetFileType_impl(PyObject *module, HANDLE handle) } +/*[clinic input] +_winapi.CopyFileExW + + src: LPCWSTR + dst: LPCWSTR + flags: DWORD + / + +Efficiently copy 2 files. +[clinic start generated code]*/ + +static PyObject * +_winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, + DWORD flags) +/*[clinic end generated code: output=715613c8834b35f5 input=bea3e5c545b755be]*/ +{ + int ret; + PyObject *py_srcname; + + Py_BEGIN_ALLOW_THREADS + ret = CopyFileExW(src, dst, NULL, NULL, NULL, flags); + Py_END_ALLOW_THREADS + if (ret == 0) { + py_srcname = Py_BuildValue("u", src); + win32_error_object("CopyFileExW", py_srcname); + Py_CLEAR(py_srcname); + return NULL; + } + Py_RETURN_NONE; +} + static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF @@ -1726,6 +1771,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI_WRITEFILE_METHODDEF _WINAPI_GETACP_METHODDEF _WINAPI_GETFILETYPE_METHODDEF + _WINAPI_COPYFILEEXW_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index b14f087732ef43..9c1d8e7e3e1b8c 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -1,944 +1,975 @@ -/*[clinic input] -preserve -[clinic start generated code]*/ - -PyDoc_STRVAR(_winapi_Overlapped_GetOverlappedResult__doc__, -"GetOverlappedResult($self, wait, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OVERLAPPED_GETOVERLAPPEDRESULT_METHODDEF \ - {"GetOverlappedResult", (PyCFunction)_winapi_Overlapped_GetOverlappedResult, METH_O, _winapi_Overlapped_GetOverlappedResult__doc__}, - -static PyObject * -_winapi_Overlapped_GetOverlappedResult_impl(OverlappedObject *self, int wait); - -static PyObject * -_winapi_Overlapped_GetOverlappedResult(OverlappedObject *self, PyObject *arg) -{ - PyObject *return_value = NULL; - int wait; - - if (!PyArg_Parse(arg, "p:GetOverlappedResult", &wait)) { - goto exit; - } - return_value = _winapi_Overlapped_GetOverlappedResult_impl(self, wait); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_Overlapped_getbuffer__doc__, -"getbuffer($self, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OVERLAPPED_GETBUFFER_METHODDEF \ - {"getbuffer", (PyCFunction)_winapi_Overlapped_getbuffer, METH_NOARGS, _winapi_Overlapped_getbuffer__doc__}, - -static PyObject * -_winapi_Overlapped_getbuffer_impl(OverlappedObject *self); - -static PyObject * -_winapi_Overlapped_getbuffer(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) -{ - return _winapi_Overlapped_getbuffer_impl(self); -} - -PyDoc_STRVAR(_winapi_Overlapped_cancel__doc__, -"cancel($self, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OVERLAPPED_CANCEL_METHODDEF \ - {"cancel", (PyCFunction)_winapi_Overlapped_cancel, METH_NOARGS, _winapi_Overlapped_cancel__doc__}, - -static PyObject * -_winapi_Overlapped_cancel_impl(OverlappedObject *self); - -static PyObject * -_winapi_Overlapped_cancel(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) -{ - return _winapi_Overlapped_cancel_impl(self); -} - -PyDoc_STRVAR(_winapi_CloseHandle__doc__, -"CloseHandle($module, handle, /)\n" -"--\n" -"\n" -"Close handle."); - -#define _WINAPI_CLOSEHANDLE_METHODDEF \ - {"CloseHandle", (PyCFunction)_winapi_CloseHandle, METH_O, _winapi_CloseHandle__doc__}, - -static PyObject * -_winapi_CloseHandle_impl(PyObject *module, HANDLE handle); - -static PyObject * -_winapi_CloseHandle(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - HANDLE handle; - - if (!PyArg_Parse(arg, "" F_HANDLE ":CloseHandle", &handle)) { - goto exit; - } - return_value = _winapi_CloseHandle_impl(module, handle); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_ConnectNamedPipe__doc__, -"ConnectNamedPipe($module, /, handle, overlapped=False)\n" -"--\n" -"\n"); - -#define _WINAPI_CONNECTNAMEDPIPE_METHODDEF \ - {"ConnectNamedPipe", (PyCFunction)_winapi_ConnectNamedPipe, METH_FASTCALL|METH_KEYWORDS, _winapi_ConnectNamedPipe__doc__}, - -static PyObject * -_winapi_ConnectNamedPipe_impl(PyObject *module, HANDLE handle, - int use_overlapped); - -static PyObject * -_winapi_ConnectNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", "overlapped", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE "|i:ConnectNamedPipe", _keywords, 0}; - HANDLE handle; - int use_overlapped = 0; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle, &use_overlapped)) { - goto exit; - } - return_value = _winapi_ConnectNamedPipe_impl(module, handle, use_overlapped); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateFile__doc__, -"CreateFile($module, file_name, desired_access, share_mode,\n" -" security_attributes, creation_disposition,\n" -" flags_and_attributes, template_file, /)\n" -"--\n" -"\n"); - -#define _WINAPI_CREATEFILE_METHODDEF \ - {"CreateFile", (PyCFunction)_winapi_CreateFile, METH_FASTCALL, _winapi_CreateFile__doc__}, - -static HANDLE -_winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, - DWORD desired_access, DWORD share_mode, - LPSECURITY_ATTRIBUTES security_attributes, - DWORD creation_disposition, - DWORD flags_and_attributes, HANDLE template_file); - -static PyObject * -_winapi_CreateFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCTSTR file_name; - DWORD desired_access; - DWORD share_mode; - LPSECURITY_ATTRIBUTES security_attributes; - DWORD creation_disposition; - DWORD flags_and_attributes; - HANDLE template_file; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "skk" F_POINTER "kk" F_HANDLE ":CreateFile", - &file_name, &desired_access, &share_mode, &security_attributes, &creation_disposition, &flags_and_attributes, &template_file)) { - goto exit; - } - _return_value = _winapi_CreateFile_impl(module, file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateJunction__doc__, -"CreateJunction($module, src_path, dst_path, /)\n" -"--\n" -"\n"); - -#define _WINAPI_CREATEJUNCTION_METHODDEF \ - {"CreateJunction", (PyCFunction)_winapi_CreateJunction, METH_FASTCALL, _winapi_CreateJunction__doc__}, - -static PyObject * -_winapi_CreateJunction_impl(PyObject *module, LPWSTR src_path, - LPWSTR dst_path); - -static PyObject * -_winapi_CreateJunction(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPWSTR src_path; - LPWSTR dst_path; - - if (!_PyArg_ParseStack(args, nargs, "uu:CreateJunction", - &src_path, &dst_path)) { - goto exit; - } - return_value = _winapi_CreateJunction_impl(module, src_path, dst_path); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateNamedPipe__doc__, -"CreateNamedPipe($module, name, open_mode, pipe_mode, max_instances,\n" -" out_buffer_size, in_buffer_size, default_timeout,\n" -" security_attributes, /)\n" -"--\n" -"\n"); - -#define _WINAPI_CREATENAMEDPIPE_METHODDEF \ - {"CreateNamedPipe", (PyCFunction)_winapi_CreateNamedPipe, METH_FASTCALL, _winapi_CreateNamedPipe__doc__}, - -static HANDLE -_winapi_CreateNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD open_mode, - DWORD pipe_mode, DWORD max_instances, - DWORD out_buffer_size, DWORD in_buffer_size, - DWORD default_timeout, - LPSECURITY_ATTRIBUTES security_attributes); - -static PyObject * -_winapi_CreateNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCTSTR name; - DWORD open_mode; - DWORD pipe_mode; - DWORD max_instances; - DWORD out_buffer_size; - DWORD in_buffer_size; - DWORD default_timeout; - LPSECURITY_ATTRIBUTES security_attributes; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "skkkkkk" F_POINTER ":CreateNamedPipe", - &name, &open_mode, &pipe_mode, &max_instances, &out_buffer_size, &in_buffer_size, &default_timeout, &security_attributes)) { - goto exit; - } - _return_value = _winapi_CreateNamedPipe_impl(module, name, open_mode, pipe_mode, max_instances, out_buffer_size, in_buffer_size, default_timeout, security_attributes); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreatePipe__doc__, -"CreatePipe($module, pipe_attrs, size, /)\n" -"--\n" -"\n" -"Create an anonymous pipe.\n" -"\n" -" pipe_attrs\n" -" Ignored internally, can be None.\n" -"\n" -"Returns a 2-tuple of handles, to the read and write ends of the pipe."); - -#define _WINAPI_CREATEPIPE_METHODDEF \ - {"CreatePipe", (PyCFunction)_winapi_CreatePipe, METH_FASTCALL, _winapi_CreatePipe__doc__}, - -static PyObject * -_winapi_CreatePipe_impl(PyObject *module, PyObject *pipe_attrs, DWORD size); - -static PyObject * -_winapi_CreatePipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - PyObject *pipe_attrs; - DWORD size; - - if (!_PyArg_ParseStack(args, nargs, "Ok:CreatePipe", - &pipe_attrs, &size)) { - goto exit; - } - return_value = _winapi_CreatePipe_impl(module, pipe_attrs, size); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateProcess__doc__, -"CreateProcess($module, application_name, command_line, proc_attrs,\n" -" thread_attrs, inherit_handles, creation_flags,\n" -" env_mapping, current_directory, startup_info, /)\n" -"--\n" -"\n" -"Create a new process and its primary thread.\n" -"\n" -" proc_attrs\n" -" Ignored internally, can be None.\n" -" thread_attrs\n" -" Ignored internally, can be None.\n" -"\n" -"The return value is a tuple of the process handle, thread handle,\n" -"process ID, and thread ID."); - -#define _WINAPI_CREATEPROCESS_METHODDEF \ - {"CreateProcess", (PyCFunction)_winapi_CreateProcess, METH_FASTCALL, _winapi_CreateProcess__doc__}, - -static PyObject * -_winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name, - Py_UNICODE *command_line, PyObject *proc_attrs, - PyObject *thread_attrs, BOOL inherit_handles, - DWORD creation_flags, PyObject *env_mapping, - Py_UNICODE *current_directory, - PyObject *startup_info); - -static PyObject * -_winapi_CreateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - Py_UNICODE *application_name; - Py_UNICODE *command_line; - PyObject *proc_attrs; - PyObject *thread_attrs; - BOOL inherit_handles; - DWORD creation_flags; - PyObject *env_mapping; - Py_UNICODE *current_directory; - PyObject *startup_info; - - if (!_PyArg_ParseStack(args, nargs, "ZZOOikOZO:CreateProcess", - &application_name, &command_line, &proc_attrs, &thread_attrs, &inherit_handles, &creation_flags, &env_mapping, ¤t_directory, &startup_info)) { - goto exit; - } - return_value = _winapi_CreateProcess_impl(module, application_name, command_line, proc_attrs, thread_attrs, inherit_handles, creation_flags, env_mapping, current_directory, startup_info); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_DuplicateHandle__doc__, -"DuplicateHandle($module, source_process_handle, source_handle,\n" -" target_process_handle, desired_access, inherit_handle,\n" -" options=0, /)\n" -"--\n" -"\n" -"Return a duplicate handle object.\n" -"\n" -"The duplicate handle refers to the same object as the original\n" -"handle. Therefore, any changes to the object are reflected\n" -"through both handles."); - -#define _WINAPI_DUPLICATEHANDLE_METHODDEF \ - {"DuplicateHandle", (PyCFunction)_winapi_DuplicateHandle, METH_FASTCALL, _winapi_DuplicateHandle__doc__}, - -static HANDLE -_winapi_DuplicateHandle_impl(PyObject *module, HANDLE source_process_handle, - HANDLE source_handle, - HANDLE target_process_handle, - DWORD desired_access, BOOL inherit_handle, - DWORD options); - -static PyObject * -_winapi_DuplicateHandle(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE source_process_handle; - HANDLE source_handle; - HANDLE target_process_handle; - DWORD desired_access; - BOOL inherit_handle; - DWORD options = 0; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "" F_HANDLE "" F_HANDLE "ki|k:DuplicateHandle", - &source_process_handle, &source_handle, &target_process_handle, &desired_access, &inherit_handle, &options)) { - goto exit; - } - _return_value = _winapi_DuplicateHandle_impl(module, source_process_handle, source_handle, target_process_handle, desired_access, inherit_handle, options); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_ExitProcess__doc__, -"ExitProcess($module, ExitCode, /)\n" -"--\n" -"\n"); - -#define _WINAPI_EXITPROCESS_METHODDEF \ - {"ExitProcess", (PyCFunction)_winapi_ExitProcess, METH_O, _winapi_ExitProcess__doc__}, - -static PyObject * -_winapi_ExitProcess_impl(PyObject *module, UINT ExitCode); - -static PyObject * -_winapi_ExitProcess(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - UINT ExitCode; - - if (!PyArg_Parse(arg, "I:ExitProcess", &ExitCode)) { - goto exit; - } - return_value = _winapi_ExitProcess_impl(module, ExitCode); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetCurrentProcess__doc__, -"GetCurrentProcess($module, /)\n" -"--\n" -"\n" -"Return a handle object for the current process."); - -#define _WINAPI_GETCURRENTPROCESS_METHODDEF \ - {"GetCurrentProcess", (PyCFunction)_winapi_GetCurrentProcess, METH_NOARGS, _winapi_GetCurrentProcess__doc__}, - -static HANDLE -_winapi_GetCurrentProcess_impl(PyObject *module); - -static PyObject * -_winapi_GetCurrentProcess(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - PyObject *return_value = NULL; - HANDLE _return_value; - - _return_value = _winapi_GetCurrentProcess_impl(module); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetExitCodeProcess__doc__, -"GetExitCodeProcess($module, process, /)\n" -"--\n" -"\n" -"Return the termination status of the specified process."); - -#define _WINAPI_GETEXITCODEPROCESS_METHODDEF \ - {"GetExitCodeProcess", (PyCFunction)_winapi_GetExitCodeProcess, METH_O, _winapi_GetExitCodeProcess__doc__}, - -static DWORD -_winapi_GetExitCodeProcess_impl(PyObject *module, HANDLE process); - -static PyObject * -_winapi_GetExitCodeProcess(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - HANDLE process; - DWORD _return_value; - - if (!PyArg_Parse(arg, "" F_HANDLE ":GetExitCodeProcess", &process)) { - goto exit; - } - _return_value = _winapi_GetExitCodeProcess_impl(module, process); - if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { - goto exit; - } - return_value = Py_BuildValue("k", _return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetLastError__doc__, -"GetLastError($module, /)\n" -"--\n" -"\n"); - -#define _WINAPI_GETLASTERROR_METHODDEF \ - {"GetLastError", (PyCFunction)_winapi_GetLastError, METH_NOARGS, _winapi_GetLastError__doc__}, - -static DWORD -_winapi_GetLastError_impl(PyObject *module); - -static PyObject * -_winapi_GetLastError(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - PyObject *return_value = NULL; - DWORD _return_value; - - _return_value = _winapi_GetLastError_impl(module); - if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { - goto exit; - } - return_value = Py_BuildValue("k", _return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetModuleFileName__doc__, -"GetModuleFileName($module, module_handle, /)\n" -"--\n" -"\n" -"Return the fully-qualified path for the file that contains module.\n" -"\n" -"The module must have been loaded by the current process.\n" -"\n" -"The module parameter should be a handle to the loaded module\n" -"whose path is being requested. If this parameter is 0,\n" -"GetModuleFileName retrieves the path of the executable file\n" -"of the current process."); - -#define _WINAPI_GETMODULEFILENAME_METHODDEF \ - {"GetModuleFileName", (PyCFunction)_winapi_GetModuleFileName, METH_O, _winapi_GetModuleFileName__doc__}, - -static PyObject * -_winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle); - -static PyObject * -_winapi_GetModuleFileName(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - HMODULE module_handle; - - if (!PyArg_Parse(arg, "" F_HANDLE ":GetModuleFileName", &module_handle)) { - goto exit; - } - return_value = _winapi_GetModuleFileName_impl(module, module_handle); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetStdHandle__doc__, -"GetStdHandle($module, std_handle, /)\n" -"--\n" -"\n" -"Return a handle to the specified standard device.\n" -"\n" -" std_handle\n" -" One of STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, or STD_ERROR_HANDLE.\n" -"\n" -"The integer associated with the handle object is returned."); - -#define _WINAPI_GETSTDHANDLE_METHODDEF \ - {"GetStdHandle", (PyCFunction)_winapi_GetStdHandle, METH_O, _winapi_GetStdHandle__doc__}, - -static HANDLE -_winapi_GetStdHandle_impl(PyObject *module, DWORD std_handle); - -static PyObject * -_winapi_GetStdHandle(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - DWORD std_handle; - HANDLE _return_value; - - if (!PyArg_Parse(arg, "k:GetStdHandle", &std_handle)) { - goto exit; - } - _return_value = _winapi_GetStdHandle_impl(module, std_handle); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetVersion__doc__, -"GetVersion($module, /)\n" -"--\n" -"\n" -"Return the version number of the current operating system."); - -#define _WINAPI_GETVERSION_METHODDEF \ - {"GetVersion", (PyCFunction)_winapi_GetVersion, METH_NOARGS, _winapi_GetVersion__doc__}, - -static long -_winapi_GetVersion_impl(PyObject *module); - -static PyObject * -_winapi_GetVersion(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - PyObject *return_value = NULL; - long _return_value; - - _return_value = _winapi_GetVersion_impl(module); - if ((_return_value == -1) && PyErr_Occurred()) { - goto exit; - } - return_value = PyLong_FromLong(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_OpenProcess__doc__, -"OpenProcess($module, desired_access, inherit_handle, process_id, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OPENPROCESS_METHODDEF \ - {"OpenProcess", (PyCFunction)_winapi_OpenProcess, METH_FASTCALL, _winapi_OpenProcess__doc__}, - -static HANDLE -_winapi_OpenProcess_impl(PyObject *module, DWORD desired_access, - BOOL inherit_handle, DWORD process_id); - -static PyObject * -_winapi_OpenProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - DWORD desired_access; - BOOL inherit_handle; - DWORD process_id; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "kik:OpenProcess", - &desired_access, &inherit_handle, &process_id)) { - goto exit; - } - _return_value = _winapi_OpenProcess_impl(module, desired_access, inherit_handle, process_id); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_PeekNamedPipe__doc__, -"PeekNamedPipe($module, handle, size=0, /)\n" -"--\n" -"\n"); - -#define _WINAPI_PEEKNAMEDPIPE_METHODDEF \ - {"PeekNamedPipe", (PyCFunction)_winapi_PeekNamedPipe, METH_FASTCALL, _winapi_PeekNamedPipe__doc__}, - -static PyObject * -_winapi_PeekNamedPipe_impl(PyObject *module, HANDLE handle, int size); - -static PyObject * -_winapi_PeekNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE handle; - int size = 0; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "|i:PeekNamedPipe", - &handle, &size)) { - goto exit; - } - return_value = _winapi_PeekNamedPipe_impl(module, handle, size); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_ReadFile__doc__, -"ReadFile($module, /, handle, size, overlapped=False)\n" -"--\n" -"\n"); - -#define _WINAPI_READFILE_METHODDEF \ - {"ReadFile", (PyCFunction)_winapi_ReadFile, METH_FASTCALL|METH_KEYWORDS, _winapi_ReadFile__doc__}, - -static PyObject * -_winapi_ReadFile_impl(PyObject *module, HANDLE handle, int size, - int use_overlapped); - -static PyObject * -_winapi_ReadFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", "size", "overlapped", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE "i|i:ReadFile", _keywords, 0}; - HANDLE handle; - int size; - int use_overlapped = 0; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle, &size, &use_overlapped)) { - goto exit; - } - return_value = _winapi_ReadFile_impl(module, handle, size, use_overlapped); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_SetNamedPipeHandleState__doc__, -"SetNamedPipeHandleState($module, named_pipe, mode,\n" -" max_collection_count, collect_data_timeout, /)\n" -"--\n" -"\n"); - -#define _WINAPI_SETNAMEDPIPEHANDLESTATE_METHODDEF \ - {"SetNamedPipeHandleState", (PyCFunction)_winapi_SetNamedPipeHandleState, METH_FASTCALL, _winapi_SetNamedPipeHandleState__doc__}, - -static PyObject * -_winapi_SetNamedPipeHandleState_impl(PyObject *module, HANDLE named_pipe, - PyObject *mode, - PyObject *max_collection_count, - PyObject *collect_data_timeout); - -static PyObject * -_winapi_SetNamedPipeHandleState(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE named_pipe; - PyObject *mode; - PyObject *max_collection_count; - PyObject *collect_data_timeout; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "OOO:SetNamedPipeHandleState", - &named_pipe, &mode, &max_collection_count, &collect_data_timeout)) { - goto exit; - } - return_value = _winapi_SetNamedPipeHandleState_impl(module, named_pipe, mode, max_collection_count, collect_data_timeout); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_TerminateProcess__doc__, -"TerminateProcess($module, handle, exit_code, /)\n" -"--\n" -"\n" -"Terminate the specified process and all of its threads."); - -#define _WINAPI_TERMINATEPROCESS_METHODDEF \ - {"TerminateProcess", (PyCFunction)_winapi_TerminateProcess, METH_FASTCALL, _winapi_TerminateProcess__doc__}, - -static PyObject * -_winapi_TerminateProcess_impl(PyObject *module, HANDLE handle, - UINT exit_code); - -static PyObject * -_winapi_TerminateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE handle; - UINT exit_code; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "I:TerminateProcess", - &handle, &exit_code)) { - goto exit; - } - return_value = _winapi_TerminateProcess_impl(module, handle, exit_code); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WaitNamedPipe__doc__, -"WaitNamedPipe($module, name, timeout, /)\n" -"--\n" -"\n"); - -#define _WINAPI_WAITNAMEDPIPE_METHODDEF \ - {"WaitNamedPipe", (PyCFunction)_winapi_WaitNamedPipe, METH_FASTCALL, _winapi_WaitNamedPipe__doc__}, - -static PyObject * -_winapi_WaitNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD timeout); - -static PyObject * -_winapi_WaitNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCTSTR name; - DWORD timeout; - - if (!_PyArg_ParseStack(args, nargs, "sk:WaitNamedPipe", - &name, &timeout)) { - goto exit; - } - return_value = _winapi_WaitNamedPipe_impl(module, name, timeout); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WaitForMultipleObjects__doc__, -"WaitForMultipleObjects($module, handle_seq, wait_flag,\n" -" milliseconds=_winapi.INFINITE, /)\n" -"--\n" -"\n"); - -#define _WINAPI_WAITFORMULTIPLEOBJECTS_METHODDEF \ - {"WaitForMultipleObjects", (PyCFunction)_winapi_WaitForMultipleObjects, METH_FASTCALL, _winapi_WaitForMultipleObjects__doc__}, - -static PyObject * -_winapi_WaitForMultipleObjects_impl(PyObject *module, PyObject *handle_seq, - BOOL wait_flag, DWORD milliseconds); - -static PyObject * -_winapi_WaitForMultipleObjects(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - PyObject *handle_seq; - BOOL wait_flag; - DWORD milliseconds = INFINITE; - - if (!_PyArg_ParseStack(args, nargs, "Oi|k:WaitForMultipleObjects", - &handle_seq, &wait_flag, &milliseconds)) { - goto exit; - } - return_value = _winapi_WaitForMultipleObjects_impl(module, handle_seq, wait_flag, milliseconds); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WaitForSingleObject__doc__, -"WaitForSingleObject($module, handle, milliseconds, /)\n" -"--\n" -"\n" -"Wait for a single object.\n" -"\n" -"Wait until the specified object is in the signaled state or\n" -"the time-out interval elapses. The timeout value is specified\n" -"in milliseconds."); - -#define _WINAPI_WAITFORSINGLEOBJECT_METHODDEF \ - {"WaitForSingleObject", (PyCFunction)_winapi_WaitForSingleObject, METH_FASTCALL, _winapi_WaitForSingleObject__doc__}, - -static long -_winapi_WaitForSingleObject_impl(PyObject *module, HANDLE handle, - DWORD milliseconds); - -static PyObject * -_winapi_WaitForSingleObject(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE handle; - DWORD milliseconds; - long _return_value; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "k:WaitForSingleObject", - &handle, &milliseconds)) { - goto exit; - } - _return_value = _winapi_WaitForSingleObject_impl(module, handle, milliseconds); - if ((_return_value == -1) && PyErr_Occurred()) { - goto exit; - } - return_value = PyLong_FromLong(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WriteFile__doc__, -"WriteFile($module, /, handle, buffer, overlapped=False)\n" -"--\n" -"\n"); - -#define _WINAPI_WRITEFILE_METHODDEF \ - {"WriteFile", (PyCFunction)_winapi_WriteFile, METH_FASTCALL|METH_KEYWORDS, _winapi_WriteFile__doc__}, - -static PyObject * -_winapi_WriteFile_impl(PyObject *module, HANDLE handle, PyObject *buffer, - int use_overlapped); - -static PyObject * -_winapi_WriteFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", "buffer", "overlapped", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE "O|i:WriteFile", _keywords, 0}; - HANDLE handle; - PyObject *buffer; - int use_overlapped = 0; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle, &buffer, &use_overlapped)) { - goto exit; - } - return_value = _winapi_WriteFile_impl(module, handle, buffer, use_overlapped); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetACP__doc__, -"GetACP($module, /)\n" -"--\n" -"\n" -"Get the current Windows ANSI code page identifier."); - -#define _WINAPI_GETACP_METHODDEF \ - {"GetACP", (PyCFunction)_winapi_GetACP, METH_NOARGS, _winapi_GetACP__doc__}, - -static PyObject * -_winapi_GetACP_impl(PyObject *module); - -static PyObject * -_winapi_GetACP(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - return _winapi_GetACP_impl(module); -} - -PyDoc_STRVAR(_winapi_GetFileType__doc__, -"GetFileType($module, /, handle)\n" -"--\n" -"\n"); - -#define _WINAPI_GETFILETYPE_METHODDEF \ - {"GetFileType", (PyCFunction)_winapi_GetFileType, METH_FASTCALL|METH_KEYWORDS, _winapi_GetFileType__doc__}, - -static DWORD -_winapi_GetFileType_impl(PyObject *module, HANDLE handle); - -static PyObject * -_winapi_GetFileType(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE ":GetFileType", _keywords, 0}; - HANDLE handle; - DWORD _return_value; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle)) { - goto exit; - } - _return_value = _winapi_GetFileType_impl(module, handle); - if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { - goto exit; - } - return_value = Py_BuildValue("k", _return_value); - -exit: - return return_value; -} -/*[clinic end generated code: output=ec7f36eb7efc9d00 input=a9049054013a1b77]*/ +/*[clinic input] +preserve +[clinic start generated code]*/ + +PyDoc_STRVAR(_winapi_Overlapped_GetOverlappedResult__doc__, +"GetOverlappedResult($self, wait, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OVERLAPPED_GETOVERLAPPEDRESULT_METHODDEF \ + {"GetOverlappedResult", (PyCFunction)_winapi_Overlapped_GetOverlappedResult, METH_O, _winapi_Overlapped_GetOverlappedResult__doc__}, + +static PyObject * +_winapi_Overlapped_GetOverlappedResult_impl(OverlappedObject *self, int wait); + +static PyObject * +_winapi_Overlapped_GetOverlappedResult(OverlappedObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + int wait; + + if (!PyArg_Parse(arg, "p:GetOverlappedResult", &wait)) { + goto exit; + } + return_value = _winapi_Overlapped_GetOverlappedResult_impl(self, wait); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_Overlapped_getbuffer__doc__, +"getbuffer($self, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OVERLAPPED_GETBUFFER_METHODDEF \ + {"getbuffer", (PyCFunction)_winapi_Overlapped_getbuffer, METH_NOARGS, _winapi_Overlapped_getbuffer__doc__}, + +static PyObject * +_winapi_Overlapped_getbuffer_impl(OverlappedObject *self); + +static PyObject * +_winapi_Overlapped_getbuffer(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_Overlapped_getbuffer_impl(self); +} + +PyDoc_STRVAR(_winapi_Overlapped_cancel__doc__, +"cancel($self, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OVERLAPPED_CANCEL_METHODDEF \ + {"cancel", (PyCFunction)_winapi_Overlapped_cancel, METH_NOARGS, _winapi_Overlapped_cancel__doc__}, + +static PyObject * +_winapi_Overlapped_cancel_impl(OverlappedObject *self); + +static PyObject * +_winapi_Overlapped_cancel(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_Overlapped_cancel_impl(self); +} + +PyDoc_STRVAR(_winapi_CloseHandle__doc__, +"CloseHandle($module, handle, /)\n" +"--\n" +"\n" +"Close handle."); + +#define _WINAPI_CLOSEHANDLE_METHODDEF \ + {"CloseHandle", (PyCFunction)_winapi_CloseHandle, METH_O, _winapi_CloseHandle__doc__}, + +static PyObject * +_winapi_CloseHandle_impl(PyObject *module, HANDLE handle); + +static PyObject * +_winapi_CloseHandle(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + HANDLE handle; + + if (!PyArg_Parse(arg, "" F_HANDLE ":CloseHandle", &handle)) { + goto exit; + } + return_value = _winapi_CloseHandle_impl(module, handle); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_ConnectNamedPipe__doc__, +"ConnectNamedPipe($module, /, handle, overlapped=False)\n" +"--\n" +"\n"); + +#define _WINAPI_CONNECTNAMEDPIPE_METHODDEF \ + {"ConnectNamedPipe", (PyCFunction)_winapi_ConnectNamedPipe, METH_FASTCALL|METH_KEYWORDS, _winapi_ConnectNamedPipe__doc__}, + +static PyObject * +_winapi_ConnectNamedPipe_impl(PyObject *module, HANDLE handle, + int use_overlapped); + +static PyObject * +_winapi_ConnectNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", "overlapped", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE "|i:ConnectNamedPipe", _keywords, 0}; + HANDLE handle; + int use_overlapped = 0; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle, &use_overlapped)) { + goto exit; + } + return_value = _winapi_ConnectNamedPipe_impl(module, handle, use_overlapped); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateFile__doc__, +"CreateFile($module, file_name, desired_access, share_mode,\n" +" security_attributes, creation_disposition,\n" +" flags_and_attributes, template_file, /)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATEFILE_METHODDEF \ + {"CreateFile", (PyCFunction)_winapi_CreateFile, METH_FASTCALL, _winapi_CreateFile__doc__}, + +static HANDLE +_winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, + DWORD desired_access, DWORD share_mode, + LPSECURITY_ATTRIBUTES security_attributes, + DWORD creation_disposition, + DWORD flags_and_attributes, HANDLE template_file); + +static PyObject * +_winapi_CreateFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCTSTR file_name; + DWORD desired_access; + DWORD share_mode; + LPSECURITY_ATTRIBUTES security_attributes; + DWORD creation_disposition; + DWORD flags_and_attributes; + HANDLE template_file; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "skk" F_POINTER "kk" F_HANDLE ":CreateFile", + &file_name, &desired_access, &share_mode, &security_attributes, &creation_disposition, &flags_and_attributes, &template_file)) { + goto exit; + } + _return_value = _winapi_CreateFile_impl(module, file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateJunction__doc__, +"CreateJunction($module, src_path, dst_path, /)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATEJUNCTION_METHODDEF \ + {"CreateJunction", (PyCFunction)_winapi_CreateJunction, METH_FASTCALL, _winapi_CreateJunction__doc__}, + +static PyObject * +_winapi_CreateJunction_impl(PyObject *module, LPWSTR src_path, + LPWSTR dst_path); + +static PyObject * +_winapi_CreateJunction(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPWSTR src_path; + LPWSTR dst_path; + + if (!_PyArg_ParseStack(args, nargs, "uu:CreateJunction", + &src_path, &dst_path)) { + goto exit; + } + return_value = _winapi_CreateJunction_impl(module, src_path, dst_path); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateNamedPipe__doc__, +"CreateNamedPipe($module, name, open_mode, pipe_mode, max_instances,\n" +" out_buffer_size, in_buffer_size, default_timeout,\n" +" security_attributes, /)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATENAMEDPIPE_METHODDEF \ + {"CreateNamedPipe", (PyCFunction)_winapi_CreateNamedPipe, METH_FASTCALL, _winapi_CreateNamedPipe__doc__}, + +static HANDLE +_winapi_CreateNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD open_mode, + DWORD pipe_mode, DWORD max_instances, + DWORD out_buffer_size, DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +static PyObject * +_winapi_CreateNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCTSTR name; + DWORD open_mode; + DWORD pipe_mode; + DWORD max_instances; + DWORD out_buffer_size; + DWORD in_buffer_size; + DWORD default_timeout; + LPSECURITY_ATTRIBUTES security_attributes; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "skkkkkk" F_POINTER ":CreateNamedPipe", + &name, &open_mode, &pipe_mode, &max_instances, &out_buffer_size, &in_buffer_size, &default_timeout, &security_attributes)) { + goto exit; + } + _return_value = _winapi_CreateNamedPipe_impl(module, name, open_mode, pipe_mode, max_instances, out_buffer_size, in_buffer_size, default_timeout, security_attributes); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreatePipe__doc__, +"CreatePipe($module, pipe_attrs, size, /)\n" +"--\n" +"\n" +"Create an anonymous pipe.\n" +"\n" +" pipe_attrs\n" +" Ignored internally, can be None.\n" +"\n" +"Returns a 2-tuple of handles, to the read and write ends of the pipe."); + +#define _WINAPI_CREATEPIPE_METHODDEF \ + {"CreatePipe", (PyCFunction)_winapi_CreatePipe, METH_FASTCALL, _winapi_CreatePipe__doc__}, + +static PyObject * +_winapi_CreatePipe_impl(PyObject *module, PyObject *pipe_attrs, DWORD size); + +static PyObject * +_winapi_CreatePipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *pipe_attrs; + DWORD size; + + if (!_PyArg_ParseStack(args, nargs, "Ok:CreatePipe", + &pipe_attrs, &size)) { + goto exit; + } + return_value = _winapi_CreatePipe_impl(module, pipe_attrs, size); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateProcess__doc__, +"CreateProcess($module, application_name, command_line, proc_attrs,\n" +" thread_attrs, inherit_handles, creation_flags,\n" +" env_mapping, current_directory, startup_info, /)\n" +"--\n" +"\n" +"Create a new process and its primary thread.\n" +"\n" +" proc_attrs\n" +" Ignored internally, can be None.\n" +" thread_attrs\n" +" Ignored internally, can be None.\n" +"\n" +"The return value is a tuple of the process handle, thread handle,\n" +"process ID, and thread ID."); + +#define _WINAPI_CREATEPROCESS_METHODDEF \ + {"CreateProcess", (PyCFunction)_winapi_CreateProcess, METH_FASTCALL, _winapi_CreateProcess__doc__}, + +static PyObject * +_winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name, + Py_UNICODE *command_line, PyObject *proc_attrs, + PyObject *thread_attrs, BOOL inherit_handles, + DWORD creation_flags, PyObject *env_mapping, + Py_UNICODE *current_directory, + PyObject *startup_info); + +static PyObject * +_winapi_CreateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_UNICODE *application_name; + Py_UNICODE *command_line; + PyObject *proc_attrs; + PyObject *thread_attrs; + BOOL inherit_handles; + DWORD creation_flags; + PyObject *env_mapping; + Py_UNICODE *current_directory; + PyObject *startup_info; + + if (!_PyArg_ParseStack(args, nargs, "ZZOOikOZO:CreateProcess", + &application_name, &command_line, &proc_attrs, &thread_attrs, &inherit_handles, &creation_flags, &env_mapping, ¤t_directory, &startup_info)) { + goto exit; + } + return_value = _winapi_CreateProcess_impl(module, application_name, command_line, proc_attrs, thread_attrs, inherit_handles, creation_flags, env_mapping, current_directory, startup_info); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_DuplicateHandle__doc__, +"DuplicateHandle($module, source_process_handle, source_handle,\n" +" target_process_handle, desired_access, inherit_handle,\n" +" options=0, /)\n" +"--\n" +"\n" +"Return a duplicate handle object.\n" +"\n" +"The duplicate handle refers to the same object as the original\n" +"handle. Therefore, any changes to the object are reflected\n" +"through both handles."); + +#define _WINAPI_DUPLICATEHANDLE_METHODDEF \ + {"DuplicateHandle", (PyCFunction)_winapi_DuplicateHandle, METH_FASTCALL, _winapi_DuplicateHandle__doc__}, + +static HANDLE +_winapi_DuplicateHandle_impl(PyObject *module, HANDLE source_process_handle, + HANDLE source_handle, + HANDLE target_process_handle, + DWORD desired_access, BOOL inherit_handle, + DWORD options); + +static PyObject * +_winapi_DuplicateHandle(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE source_process_handle; + HANDLE source_handle; + HANDLE target_process_handle; + DWORD desired_access; + BOOL inherit_handle; + DWORD options = 0; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "" F_HANDLE "" F_HANDLE "ki|k:DuplicateHandle", + &source_process_handle, &source_handle, &target_process_handle, &desired_access, &inherit_handle, &options)) { + goto exit; + } + _return_value = _winapi_DuplicateHandle_impl(module, source_process_handle, source_handle, target_process_handle, desired_access, inherit_handle, options); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_ExitProcess__doc__, +"ExitProcess($module, ExitCode, /)\n" +"--\n" +"\n"); + +#define _WINAPI_EXITPROCESS_METHODDEF \ + {"ExitProcess", (PyCFunction)_winapi_ExitProcess, METH_O, _winapi_ExitProcess__doc__}, + +static PyObject * +_winapi_ExitProcess_impl(PyObject *module, UINT ExitCode); + +static PyObject * +_winapi_ExitProcess(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + UINT ExitCode; + + if (!PyArg_Parse(arg, "I:ExitProcess", &ExitCode)) { + goto exit; + } + return_value = _winapi_ExitProcess_impl(module, ExitCode); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetCurrentProcess__doc__, +"GetCurrentProcess($module, /)\n" +"--\n" +"\n" +"Return a handle object for the current process."); + +#define _WINAPI_GETCURRENTPROCESS_METHODDEF \ + {"GetCurrentProcess", (PyCFunction)_winapi_GetCurrentProcess, METH_NOARGS, _winapi_GetCurrentProcess__doc__}, + +static HANDLE +_winapi_GetCurrentProcess_impl(PyObject *module); + +static PyObject * +_winapi_GetCurrentProcess(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + HANDLE _return_value; + + _return_value = _winapi_GetCurrentProcess_impl(module); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetExitCodeProcess__doc__, +"GetExitCodeProcess($module, process, /)\n" +"--\n" +"\n" +"Return the termination status of the specified process."); + +#define _WINAPI_GETEXITCODEPROCESS_METHODDEF \ + {"GetExitCodeProcess", (PyCFunction)_winapi_GetExitCodeProcess, METH_O, _winapi_GetExitCodeProcess__doc__}, + +static DWORD +_winapi_GetExitCodeProcess_impl(PyObject *module, HANDLE process); + +static PyObject * +_winapi_GetExitCodeProcess(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + HANDLE process; + DWORD _return_value; + + if (!PyArg_Parse(arg, "" F_HANDLE ":GetExitCodeProcess", &process)) { + goto exit; + } + _return_value = _winapi_GetExitCodeProcess_impl(module, process); + if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { + goto exit; + } + return_value = Py_BuildValue("k", _return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetLastError__doc__, +"GetLastError($module, /)\n" +"--\n" +"\n"); + +#define _WINAPI_GETLASTERROR_METHODDEF \ + {"GetLastError", (PyCFunction)_winapi_GetLastError, METH_NOARGS, _winapi_GetLastError__doc__}, + +static DWORD +_winapi_GetLastError_impl(PyObject *module); + +static PyObject * +_winapi_GetLastError(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + DWORD _return_value; + + _return_value = _winapi_GetLastError_impl(module); + if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { + goto exit; + } + return_value = Py_BuildValue("k", _return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetModuleFileName__doc__, +"GetModuleFileName($module, module_handle, /)\n" +"--\n" +"\n" +"Return the fully-qualified path for the file that contains module.\n" +"\n" +"The module must have been loaded by the current process.\n" +"\n" +"The module parameter should be a handle to the loaded module\n" +"whose path is being requested. If this parameter is 0,\n" +"GetModuleFileName retrieves the path of the executable file\n" +"of the current process."); + +#define _WINAPI_GETMODULEFILENAME_METHODDEF \ + {"GetModuleFileName", (PyCFunction)_winapi_GetModuleFileName, METH_O, _winapi_GetModuleFileName__doc__}, + +static PyObject * +_winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle); + +static PyObject * +_winapi_GetModuleFileName(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + HMODULE module_handle; + + if (!PyArg_Parse(arg, "" F_HANDLE ":GetModuleFileName", &module_handle)) { + goto exit; + } + return_value = _winapi_GetModuleFileName_impl(module, module_handle); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetStdHandle__doc__, +"GetStdHandle($module, std_handle, /)\n" +"--\n" +"\n" +"Return a handle to the specified standard device.\n" +"\n" +" std_handle\n" +" One of STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, or STD_ERROR_HANDLE.\n" +"\n" +"The integer associated with the handle object is returned."); + +#define _WINAPI_GETSTDHANDLE_METHODDEF \ + {"GetStdHandle", (PyCFunction)_winapi_GetStdHandle, METH_O, _winapi_GetStdHandle__doc__}, + +static HANDLE +_winapi_GetStdHandle_impl(PyObject *module, DWORD std_handle); + +static PyObject * +_winapi_GetStdHandle(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + DWORD std_handle; + HANDLE _return_value; + + if (!PyArg_Parse(arg, "k:GetStdHandle", &std_handle)) { + goto exit; + } + _return_value = _winapi_GetStdHandle_impl(module, std_handle); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetVersion__doc__, +"GetVersion($module, /)\n" +"--\n" +"\n" +"Return the version number of the current operating system."); + +#define _WINAPI_GETVERSION_METHODDEF \ + {"GetVersion", (PyCFunction)_winapi_GetVersion, METH_NOARGS, _winapi_GetVersion__doc__}, + +static long +_winapi_GetVersion_impl(PyObject *module); + +static PyObject * +_winapi_GetVersion(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + long _return_value; + + _return_value = _winapi_GetVersion_impl(module); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_OpenProcess__doc__, +"OpenProcess($module, desired_access, inherit_handle, process_id, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OPENPROCESS_METHODDEF \ + {"OpenProcess", (PyCFunction)_winapi_OpenProcess, METH_FASTCALL, _winapi_OpenProcess__doc__}, + +static HANDLE +_winapi_OpenProcess_impl(PyObject *module, DWORD desired_access, + BOOL inherit_handle, DWORD process_id); + +static PyObject * +_winapi_OpenProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + DWORD desired_access; + BOOL inherit_handle; + DWORD process_id; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "kik:OpenProcess", + &desired_access, &inherit_handle, &process_id)) { + goto exit; + } + _return_value = _winapi_OpenProcess_impl(module, desired_access, inherit_handle, process_id); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_PeekNamedPipe__doc__, +"PeekNamedPipe($module, handle, size=0, /)\n" +"--\n" +"\n"); + +#define _WINAPI_PEEKNAMEDPIPE_METHODDEF \ + {"PeekNamedPipe", (PyCFunction)_winapi_PeekNamedPipe, METH_FASTCALL, _winapi_PeekNamedPipe__doc__}, + +static PyObject * +_winapi_PeekNamedPipe_impl(PyObject *module, HANDLE handle, int size); + +static PyObject * +_winapi_PeekNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE handle; + int size = 0; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "|i:PeekNamedPipe", + &handle, &size)) { + goto exit; + } + return_value = _winapi_PeekNamedPipe_impl(module, handle, size); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_ReadFile__doc__, +"ReadFile($module, /, handle, size, overlapped=False)\n" +"--\n" +"\n"); + +#define _WINAPI_READFILE_METHODDEF \ + {"ReadFile", (PyCFunction)_winapi_ReadFile, METH_FASTCALL|METH_KEYWORDS, _winapi_ReadFile__doc__}, + +static PyObject * +_winapi_ReadFile_impl(PyObject *module, HANDLE handle, int size, + int use_overlapped); + +static PyObject * +_winapi_ReadFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", "size", "overlapped", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE "i|i:ReadFile", _keywords, 0}; + HANDLE handle; + int size; + int use_overlapped = 0; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle, &size, &use_overlapped)) { + goto exit; + } + return_value = _winapi_ReadFile_impl(module, handle, size, use_overlapped); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_SetNamedPipeHandleState__doc__, +"SetNamedPipeHandleState($module, named_pipe, mode,\n" +" max_collection_count, collect_data_timeout, /)\n" +"--\n" +"\n"); + +#define _WINAPI_SETNAMEDPIPEHANDLESTATE_METHODDEF \ + {"SetNamedPipeHandleState", (PyCFunction)_winapi_SetNamedPipeHandleState, METH_FASTCALL, _winapi_SetNamedPipeHandleState__doc__}, + +static PyObject * +_winapi_SetNamedPipeHandleState_impl(PyObject *module, HANDLE named_pipe, + PyObject *mode, + PyObject *max_collection_count, + PyObject *collect_data_timeout); + +static PyObject * +_winapi_SetNamedPipeHandleState(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE named_pipe; + PyObject *mode; + PyObject *max_collection_count; + PyObject *collect_data_timeout; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "OOO:SetNamedPipeHandleState", + &named_pipe, &mode, &max_collection_count, &collect_data_timeout)) { + goto exit; + } + return_value = _winapi_SetNamedPipeHandleState_impl(module, named_pipe, mode, max_collection_count, collect_data_timeout); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_TerminateProcess__doc__, +"TerminateProcess($module, handle, exit_code, /)\n" +"--\n" +"\n" +"Terminate the specified process and all of its threads."); + +#define _WINAPI_TERMINATEPROCESS_METHODDEF \ + {"TerminateProcess", (PyCFunction)_winapi_TerminateProcess, METH_FASTCALL, _winapi_TerminateProcess__doc__}, + +static PyObject * +_winapi_TerminateProcess_impl(PyObject *module, HANDLE handle, + UINT exit_code); + +static PyObject * +_winapi_TerminateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE handle; + UINT exit_code; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "I:TerminateProcess", + &handle, &exit_code)) { + goto exit; + } + return_value = _winapi_TerminateProcess_impl(module, handle, exit_code); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WaitNamedPipe__doc__, +"WaitNamedPipe($module, name, timeout, /)\n" +"--\n" +"\n"); + +#define _WINAPI_WAITNAMEDPIPE_METHODDEF \ + {"WaitNamedPipe", (PyCFunction)_winapi_WaitNamedPipe, METH_FASTCALL, _winapi_WaitNamedPipe__doc__}, + +static PyObject * +_winapi_WaitNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD timeout); + +static PyObject * +_winapi_WaitNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCTSTR name; + DWORD timeout; + + if (!_PyArg_ParseStack(args, nargs, "sk:WaitNamedPipe", + &name, &timeout)) { + goto exit; + } + return_value = _winapi_WaitNamedPipe_impl(module, name, timeout); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WaitForMultipleObjects__doc__, +"WaitForMultipleObjects($module, handle_seq, wait_flag,\n" +" milliseconds=_winapi.INFINITE, /)\n" +"--\n" +"\n"); + +#define _WINAPI_WAITFORMULTIPLEOBJECTS_METHODDEF \ + {"WaitForMultipleObjects", (PyCFunction)_winapi_WaitForMultipleObjects, METH_FASTCALL, _winapi_WaitForMultipleObjects__doc__}, + +static PyObject * +_winapi_WaitForMultipleObjects_impl(PyObject *module, PyObject *handle_seq, + BOOL wait_flag, DWORD milliseconds); + +static PyObject * +_winapi_WaitForMultipleObjects(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *handle_seq; + BOOL wait_flag; + DWORD milliseconds = INFINITE; + + if (!_PyArg_ParseStack(args, nargs, "Oi|k:WaitForMultipleObjects", + &handle_seq, &wait_flag, &milliseconds)) { + goto exit; + } + return_value = _winapi_WaitForMultipleObjects_impl(module, handle_seq, wait_flag, milliseconds); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WaitForSingleObject__doc__, +"WaitForSingleObject($module, handle, milliseconds, /)\n" +"--\n" +"\n" +"Wait for a single object.\n" +"\n" +"Wait until the specified object is in the signaled state or\n" +"the time-out interval elapses. The timeout value is specified\n" +"in milliseconds."); + +#define _WINAPI_WAITFORSINGLEOBJECT_METHODDEF \ + {"WaitForSingleObject", (PyCFunction)_winapi_WaitForSingleObject, METH_FASTCALL, _winapi_WaitForSingleObject__doc__}, + +static long +_winapi_WaitForSingleObject_impl(PyObject *module, HANDLE handle, + DWORD milliseconds); + +static PyObject * +_winapi_WaitForSingleObject(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE handle; + DWORD milliseconds; + long _return_value; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "k:WaitForSingleObject", + &handle, &milliseconds)) { + goto exit; + } + _return_value = _winapi_WaitForSingleObject_impl(module, handle, milliseconds); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WriteFile__doc__, +"WriteFile($module, /, handle, buffer, overlapped=False)\n" +"--\n" +"\n"); + +#define _WINAPI_WRITEFILE_METHODDEF \ + {"WriteFile", (PyCFunction)_winapi_WriteFile, METH_FASTCALL|METH_KEYWORDS, _winapi_WriteFile__doc__}, + +static PyObject * +_winapi_WriteFile_impl(PyObject *module, HANDLE handle, PyObject *buffer, + int use_overlapped); + +static PyObject * +_winapi_WriteFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", "buffer", "overlapped", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE "O|i:WriteFile", _keywords, 0}; + HANDLE handle; + PyObject *buffer; + int use_overlapped = 0; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle, &buffer, &use_overlapped)) { + goto exit; + } + return_value = _winapi_WriteFile_impl(module, handle, buffer, use_overlapped); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetACP__doc__, +"GetACP($module, /)\n" +"--\n" +"\n" +"Get the current Windows ANSI code page identifier."); + +#define _WINAPI_GETACP_METHODDEF \ + {"GetACP", (PyCFunction)_winapi_GetACP, METH_NOARGS, _winapi_GetACP__doc__}, + +static PyObject * +_winapi_GetACP_impl(PyObject *module); + +static PyObject * +_winapi_GetACP(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_GetACP_impl(module); +} + +PyDoc_STRVAR(_winapi_GetFileType__doc__, +"GetFileType($module, /, handle)\n" +"--\n" +"\n"); + +#define _WINAPI_GETFILETYPE_METHODDEF \ + {"GetFileType", (PyCFunction)_winapi_GetFileType, METH_FASTCALL|METH_KEYWORDS, _winapi_GetFileType__doc__}, + +static DWORD +_winapi_GetFileType_impl(PyObject *module, HANDLE handle); + +static PyObject * +_winapi_GetFileType(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE ":GetFileType", _keywords, 0}; + HANDLE handle; + DWORD _return_value; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle)) { + goto exit; + } + _return_value = _winapi_GetFileType_impl(module, handle); + if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { + goto exit; + } + return_value = Py_BuildValue("k", _return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CopyFileExW__doc__, +"CopyFileExW($module, src, dst, flags, /)\n" +"--\n" +"\n" +"Efficiently copy 2 files."); + +#define _WINAPI_COPYFILEEXW_METHODDEF \ + {"CopyFileExW", (PyCFunction)_winapi_CopyFileExW, METH_FASTCALL, _winapi_CopyFileExW__doc__}, + +static PyObject * +_winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, + DWORD flags); + +static PyObject * +_winapi_CopyFileExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCWSTR src; + LPCWSTR dst; + DWORD flags; + + if (!_PyArg_ParseStack(args, nargs, "uuk:CopyFileExW", + &src, &dst, &flags)) { + goto exit; + } + return_value = _winapi_CopyFileExW_impl(module, src, dst, flags); + +exit: + return return_value; +} +/*[clinic end generated code: output=1f3c6672a1f704d3 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 6f283129f337fc..d385e1689fd0b7 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3886,43 +3886,6 @@ os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #endif /* defined(__APPLE__) */ -#if defined(MS_WINDOWS) - -PyDoc_STRVAR(os__win32copyfile__doc__, -"_win32copyfile($module, src, dst, /)\n" -"--\n" -"\n" -"Efficiently copy 2 files (Windows only)."); - -#define OS__WIN32COPYFILE_METHODDEF \ - {"_win32copyfile", (PyCFunction)os__win32copyfile, METH_FASTCALL, os__win32copyfile__doc__}, - -static PyObject * -os__win32copyfile_impl(PyObject *module, path_t *src, path_t *dst); - -static PyObject * -os__win32copyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - path_t src = PATH_T_INITIALIZE("_win32copyfile", "src", 0, 0); - path_t dst = PATH_T_INITIALIZE("_win32copyfile", "dst", 0, 0); - - if (!_PyArg_ParseStack(args, nargs, "O&O&:_win32copyfile", - path_converter, &src, path_converter, &dst)) { - goto exit; - } - return_value = os__win32copyfile_impl(module, &src, &dst); - -exit: - /* Cleanup for src */ - path_cleanup(&src); - /* Cleanup for dst */ - path_cleanup(&dst); - - return return_value; -} - -#endif /* (defined MS_WINDOWS) */ PyDoc_STRVAR(os_fstat__doc__, "fstat($module, /, fd)\n" @@ -6489,10 +6452,6 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #define OS__FCOPYFILE_METHODDEF #endif /* !defined(OS__FCOPYFILE_METHODDEF) */ -#ifndef OS__WIN32COPYFILE_METHODDEF - #define OS__WIN32COPYFILE_METHODDEF -#endif /* !defined(OS__WIN32COPYFILE_METHODDEF) */ - #ifndef OS_PIPE_METHODDEF #define OS_PIPE_METHODDEF #endif /* !defined(OS_PIPE_METHODDEF) */ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 83580f1d8cd428..ef3455ebd50bda 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8778,33 +8778,6 @@ os__fcopyfile_impl(PyObject *module, int infd, int outfd) #endif -#if defined MS_WINDOWS -/*[clinic input] -os._win32copyfile - - src: path_t - dst: path_t - / - -Efficiently copy 2 files (Windows only). -[clinic start generated code]*/ - -static PyObject * -os__win32copyfile_impl(PyObject *module, path_t *src, path_t *dst) -/*[clinic end generated code: output=9df245926c468843 input=00817871f5770bdc]*/ -{ - int ret; - - Py_BEGIN_ALLOW_THREADS - ret = CopyFileW(src->wide, dst->wide, FALSE); - Py_END_ALLOW_THREADS - if (ret == 0) - return win32_error_object("_win32copyfile", src->object); - Py_RETURN_NONE; -} -#endif - - /*[clinic input] os.fstat @@ -12982,7 +12955,6 @@ static PyMethodDef posix_methods[] = { OS_TIMES_METHODDEF OS__EXIT_METHODDEF OS__FCOPYFILE_METHODDEF - OS__WIN32COPYFILE_METHODDEF OS_EXECV_METHODDEF OS_EXECVE_METHODDEF OS_SPAWNV_METHODDEF From 729dd23c307abf3c95ef4583daeb8c5ecaa1d0d2 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 30 May 2018 17:59:33 +0200 Subject: [PATCH 063/111] fix line endings --- Modules/clinic/_winapi.c.h | 1950 ++++++++++++++++++------------------ 1 file changed, 975 insertions(+), 975 deletions(-) diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index 9c1d8e7e3e1b8c..88b147564b9e50 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -1,975 +1,975 @@ -/*[clinic input] -preserve -[clinic start generated code]*/ - -PyDoc_STRVAR(_winapi_Overlapped_GetOverlappedResult__doc__, -"GetOverlappedResult($self, wait, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OVERLAPPED_GETOVERLAPPEDRESULT_METHODDEF \ - {"GetOverlappedResult", (PyCFunction)_winapi_Overlapped_GetOverlappedResult, METH_O, _winapi_Overlapped_GetOverlappedResult__doc__}, - -static PyObject * -_winapi_Overlapped_GetOverlappedResult_impl(OverlappedObject *self, int wait); - -static PyObject * -_winapi_Overlapped_GetOverlappedResult(OverlappedObject *self, PyObject *arg) -{ - PyObject *return_value = NULL; - int wait; - - if (!PyArg_Parse(arg, "p:GetOverlappedResult", &wait)) { - goto exit; - } - return_value = _winapi_Overlapped_GetOverlappedResult_impl(self, wait); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_Overlapped_getbuffer__doc__, -"getbuffer($self, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OVERLAPPED_GETBUFFER_METHODDEF \ - {"getbuffer", (PyCFunction)_winapi_Overlapped_getbuffer, METH_NOARGS, _winapi_Overlapped_getbuffer__doc__}, - -static PyObject * -_winapi_Overlapped_getbuffer_impl(OverlappedObject *self); - -static PyObject * -_winapi_Overlapped_getbuffer(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) -{ - return _winapi_Overlapped_getbuffer_impl(self); -} - -PyDoc_STRVAR(_winapi_Overlapped_cancel__doc__, -"cancel($self, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OVERLAPPED_CANCEL_METHODDEF \ - {"cancel", (PyCFunction)_winapi_Overlapped_cancel, METH_NOARGS, _winapi_Overlapped_cancel__doc__}, - -static PyObject * -_winapi_Overlapped_cancel_impl(OverlappedObject *self); - -static PyObject * -_winapi_Overlapped_cancel(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) -{ - return _winapi_Overlapped_cancel_impl(self); -} - -PyDoc_STRVAR(_winapi_CloseHandle__doc__, -"CloseHandle($module, handle, /)\n" -"--\n" -"\n" -"Close handle."); - -#define _WINAPI_CLOSEHANDLE_METHODDEF \ - {"CloseHandle", (PyCFunction)_winapi_CloseHandle, METH_O, _winapi_CloseHandle__doc__}, - -static PyObject * -_winapi_CloseHandle_impl(PyObject *module, HANDLE handle); - -static PyObject * -_winapi_CloseHandle(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - HANDLE handle; - - if (!PyArg_Parse(arg, "" F_HANDLE ":CloseHandle", &handle)) { - goto exit; - } - return_value = _winapi_CloseHandle_impl(module, handle); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_ConnectNamedPipe__doc__, -"ConnectNamedPipe($module, /, handle, overlapped=False)\n" -"--\n" -"\n"); - -#define _WINAPI_CONNECTNAMEDPIPE_METHODDEF \ - {"ConnectNamedPipe", (PyCFunction)_winapi_ConnectNamedPipe, METH_FASTCALL|METH_KEYWORDS, _winapi_ConnectNamedPipe__doc__}, - -static PyObject * -_winapi_ConnectNamedPipe_impl(PyObject *module, HANDLE handle, - int use_overlapped); - -static PyObject * -_winapi_ConnectNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", "overlapped", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE "|i:ConnectNamedPipe", _keywords, 0}; - HANDLE handle; - int use_overlapped = 0; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle, &use_overlapped)) { - goto exit; - } - return_value = _winapi_ConnectNamedPipe_impl(module, handle, use_overlapped); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateFile__doc__, -"CreateFile($module, file_name, desired_access, share_mode,\n" -" security_attributes, creation_disposition,\n" -" flags_and_attributes, template_file, /)\n" -"--\n" -"\n"); - -#define _WINAPI_CREATEFILE_METHODDEF \ - {"CreateFile", (PyCFunction)_winapi_CreateFile, METH_FASTCALL, _winapi_CreateFile__doc__}, - -static HANDLE -_winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, - DWORD desired_access, DWORD share_mode, - LPSECURITY_ATTRIBUTES security_attributes, - DWORD creation_disposition, - DWORD flags_and_attributes, HANDLE template_file); - -static PyObject * -_winapi_CreateFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCTSTR file_name; - DWORD desired_access; - DWORD share_mode; - LPSECURITY_ATTRIBUTES security_attributes; - DWORD creation_disposition; - DWORD flags_and_attributes; - HANDLE template_file; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "skk" F_POINTER "kk" F_HANDLE ":CreateFile", - &file_name, &desired_access, &share_mode, &security_attributes, &creation_disposition, &flags_and_attributes, &template_file)) { - goto exit; - } - _return_value = _winapi_CreateFile_impl(module, file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateJunction__doc__, -"CreateJunction($module, src_path, dst_path, /)\n" -"--\n" -"\n"); - -#define _WINAPI_CREATEJUNCTION_METHODDEF \ - {"CreateJunction", (PyCFunction)_winapi_CreateJunction, METH_FASTCALL, _winapi_CreateJunction__doc__}, - -static PyObject * -_winapi_CreateJunction_impl(PyObject *module, LPWSTR src_path, - LPWSTR dst_path); - -static PyObject * -_winapi_CreateJunction(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPWSTR src_path; - LPWSTR dst_path; - - if (!_PyArg_ParseStack(args, nargs, "uu:CreateJunction", - &src_path, &dst_path)) { - goto exit; - } - return_value = _winapi_CreateJunction_impl(module, src_path, dst_path); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateNamedPipe__doc__, -"CreateNamedPipe($module, name, open_mode, pipe_mode, max_instances,\n" -" out_buffer_size, in_buffer_size, default_timeout,\n" -" security_attributes, /)\n" -"--\n" -"\n"); - -#define _WINAPI_CREATENAMEDPIPE_METHODDEF \ - {"CreateNamedPipe", (PyCFunction)_winapi_CreateNamedPipe, METH_FASTCALL, _winapi_CreateNamedPipe__doc__}, - -static HANDLE -_winapi_CreateNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD open_mode, - DWORD pipe_mode, DWORD max_instances, - DWORD out_buffer_size, DWORD in_buffer_size, - DWORD default_timeout, - LPSECURITY_ATTRIBUTES security_attributes); - -static PyObject * -_winapi_CreateNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCTSTR name; - DWORD open_mode; - DWORD pipe_mode; - DWORD max_instances; - DWORD out_buffer_size; - DWORD in_buffer_size; - DWORD default_timeout; - LPSECURITY_ATTRIBUTES security_attributes; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "skkkkkk" F_POINTER ":CreateNamedPipe", - &name, &open_mode, &pipe_mode, &max_instances, &out_buffer_size, &in_buffer_size, &default_timeout, &security_attributes)) { - goto exit; - } - _return_value = _winapi_CreateNamedPipe_impl(module, name, open_mode, pipe_mode, max_instances, out_buffer_size, in_buffer_size, default_timeout, security_attributes); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreatePipe__doc__, -"CreatePipe($module, pipe_attrs, size, /)\n" -"--\n" -"\n" -"Create an anonymous pipe.\n" -"\n" -" pipe_attrs\n" -" Ignored internally, can be None.\n" -"\n" -"Returns a 2-tuple of handles, to the read and write ends of the pipe."); - -#define _WINAPI_CREATEPIPE_METHODDEF \ - {"CreatePipe", (PyCFunction)_winapi_CreatePipe, METH_FASTCALL, _winapi_CreatePipe__doc__}, - -static PyObject * -_winapi_CreatePipe_impl(PyObject *module, PyObject *pipe_attrs, DWORD size); - -static PyObject * -_winapi_CreatePipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - PyObject *pipe_attrs; - DWORD size; - - if (!_PyArg_ParseStack(args, nargs, "Ok:CreatePipe", - &pipe_attrs, &size)) { - goto exit; - } - return_value = _winapi_CreatePipe_impl(module, pipe_attrs, size); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateProcess__doc__, -"CreateProcess($module, application_name, command_line, proc_attrs,\n" -" thread_attrs, inherit_handles, creation_flags,\n" -" env_mapping, current_directory, startup_info, /)\n" -"--\n" -"\n" -"Create a new process and its primary thread.\n" -"\n" -" proc_attrs\n" -" Ignored internally, can be None.\n" -" thread_attrs\n" -" Ignored internally, can be None.\n" -"\n" -"The return value is a tuple of the process handle, thread handle,\n" -"process ID, and thread ID."); - -#define _WINAPI_CREATEPROCESS_METHODDEF \ - {"CreateProcess", (PyCFunction)_winapi_CreateProcess, METH_FASTCALL, _winapi_CreateProcess__doc__}, - -static PyObject * -_winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name, - Py_UNICODE *command_line, PyObject *proc_attrs, - PyObject *thread_attrs, BOOL inherit_handles, - DWORD creation_flags, PyObject *env_mapping, - Py_UNICODE *current_directory, - PyObject *startup_info); - -static PyObject * -_winapi_CreateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - Py_UNICODE *application_name; - Py_UNICODE *command_line; - PyObject *proc_attrs; - PyObject *thread_attrs; - BOOL inherit_handles; - DWORD creation_flags; - PyObject *env_mapping; - Py_UNICODE *current_directory; - PyObject *startup_info; - - if (!_PyArg_ParseStack(args, nargs, "ZZOOikOZO:CreateProcess", - &application_name, &command_line, &proc_attrs, &thread_attrs, &inherit_handles, &creation_flags, &env_mapping, ¤t_directory, &startup_info)) { - goto exit; - } - return_value = _winapi_CreateProcess_impl(module, application_name, command_line, proc_attrs, thread_attrs, inherit_handles, creation_flags, env_mapping, current_directory, startup_info); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_DuplicateHandle__doc__, -"DuplicateHandle($module, source_process_handle, source_handle,\n" -" target_process_handle, desired_access, inherit_handle,\n" -" options=0, /)\n" -"--\n" -"\n" -"Return a duplicate handle object.\n" -"\n" -"The duplicate handle refers to the same object as the original\n" -"handle. Therefore, any changes to the object are reflected\n" -"through both handles."); - -#define _WINAPI_DUPLICATEHANDLE_METHODDEF \ - {"DuplicateHandle", (PyCFunction)_winapi_DuplicateHandle, METH_FASTCALL, _winapi_DuplicateHandle__doc__}, - -static HANDLE -_winapi_DuplicateHandle_impl(PyObject *module, HANDLE source_process_handle, - HANDLE source_handle, - HANDLE target_process_handle, - DWORD desired_access, BOOL inherit_handle, - DWORD options); - -static PyObject * -_winapi_DuplicateHandle(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE source_process_handle; - HANDLE source_handle; - HANDLE target_process_handle; - DWORD desired_access; - BOOL inherit_handle; - DWORD options = 0; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "" F_HANDLE "" F_HANDLE "ki|k:DuplicateHandle", - &source_process_handle, &source_handle, &target_process_handle, &desired_access, &inherit_handle, &options)) { - goto exit; - } - _return_value = _winapi_DuplicateHandle_impl(module, source_process_handle, source_handle, target_process_handle, desired_access, inherit_handle, options); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_ExitProcess__doc__, -"ExitProcess($module, ExitCode, /)\n" -"--\n" -"\n"); - -#define _WINAPI_EXITPROCESS_METHODDEF \ - {"ExitProcess", (PyCFunction)_winapi_ExitProcess, METH_O, _winapi_ExitProcess__doc__}, - -static PyObject * -_winapi_ExitProcess_impl(PyObject *module, UINT ExitCode); - -static PyObject * -_winapi_ExitProcess(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - UINT ExitCode; - - if (!PyArg_Parse(arg, "I:ExitProcess", &ExitCode)) { - goto exit; - } - return_value = _winapi_ExitProcess_impl(module, ExitCode); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetCurrentProcess__doc__, -"GetCurrentProcess($module, /)\n" -"--\n" -"\n" -"Return a handle object for the current process."); - -#define _WINAPI_GETCURRENTPROCESS_METHODDEF \ - {"GetCurrentProcess", (PyCFunction)_winapi_GetCurrentProcess, METH_NOARGS, _winapi_GetCurrentProcess__doc__}, - -static HANDLE -_winapi_GetCurrentProcess_impl(PyObject *module); - -static PyObject * -_winapi_GetCurrentProcess(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - PyObject *return_value = NULL; - HANDLE _return_value; - - _return_value = _winapi_GetCurrentProcess_impl(module); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetExitCodeProcess__doc__, -"GetExitCodeProcess($module, process, /)\n" -"--\n" -"\n" -"Return the termination status of the specified process."); - -#define _WINAPI_GETEXITCODEPROCESS_METHODDEF \ - {"GetExitCodeProcess", (PyCFunction)_winapi_GetExitCodeProcess, METH_O, _winapi_GetExitCodeProcess__doc__}, - -static DWORD -_winapi_GetExitCodeProcess_impl(PyObject *module, HANDLE process); - -static PyObject * -_winapi_GetExitCodeProcess(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - HANDLE process; - DWORD _return_value; - - if (!PyArg_Parse(arg, "" F_HANDLE ":GetExitCodeProcess", &process)) { - goto exit; - } - _return_value = _winapi_GetExitCodeProcess_impl(module, process); - if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { - goto exit; - } - return_value = Py_BuildValue("k", _return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetLastError__doc__, -"GetLastError($module, /)\n" -"--\n" -"\n"); - -#define _WINAPI_GETLASTERROR_METHODDEF \ - {"GetLastError", (PyCFunction)_winapi_GetLastError, METH_NOARGS, _winapi_GetLastError__doc__}, - -static DWORD -_winapi_GetLastError_impl(PyObject *module); - -static PyObject * -_winapi_GetLastError(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - PyObject *return_value = NULL; - DWORD _return_value; - - _return_value = _winapi_GetLastError_impl(module); - if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { - goto exit; - } - return_value = Py_BuildValue("k", _return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetModuleFileName__doc__, -"GetModuleFileName($module, module_handle, /)\n" -"--\n" -"\n" -"Return the fully-qualified path for the file that contains module.\n" -"\n" -"The module must have been loaded by the current process.\n" -"\n" -"The module parameter should be a handle to the loaded module\n" -"whose path is being requested. If this parameter is 0,\n" -"GetModuleFileName retrieves the path of the executable file\n" -"of the current process."); - -#define _WINAPI_GETMODULEFILENAME_METHODDEF \ - {"GetModuleFileName", (PyCFunction)_winapi_GetModuleFileName, METH_O, _winapi_GetModuleFileName__doc__}, - -static PyObject * -_winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle); - -static PyObject * -_winapi_GetModuleFileName(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - HMODULE module_handle; - - if (!PyArg_Parse(arg, "" F_HANDLE ":GetModuleFileName", &module_handle)) { - goto exit; - } - return_value = _winapi_GetModuleFileName_impl(module, module_handle); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetStdHandle__doc__, -"GetStdHandle($module, std_handle, /)\n" -"--\n" -"\n" -"Return a handle to the specified standard device.\n" -"\n" -" std_handle\n" -" One of STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, or STD_ERROR_HANDLE.\n" -"\n" -"The integer associated with the handle object is returned."); - -#define _WINAPI_GETSTDHANDLE_METHODDEF \ - {"GetStdHandle", (PyCFunction)_winapi_GetStdHandle, METH_O, _winapi_GetStdHandle__doc__}, - -static HANDLE -_winapi_GetStdHandle_impl(PyObject *module, DWORD std_handle); - -static PyObject * -_winapi_GetStdHandle(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - DWORD std_handle; - HANDLE _return_value; - - if (!PyArg_Parse(arg, "k:GetStdHandle", &std_handle)) { - goto exit; - } - _return_value = _winapi_GetStdHandle_impl(module, std_handle); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetVersion__doc__, -"GetVersion($module, /)\n" -"--\n" -"\n" -"Return the version number of the current operating system."); - -#define _WINAPI_GETVERSION_METHODDEF \ - {"GetVersion", (PyCFunction)_winapi_GetVersion, METH_NOARGS, _winapi_GetVersion__doc__}, - -static long -_winapi_GetVersion_impl(PyObject *module); - -static PyObject * -_winapi_GetVersion(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - PyObject *return_value = NULL; - long _return_value; - - _return_value = _winapi_GetVersion_impl(module); - if ((_return_value == -1) && PyErr_Occurred()) { - goto exit; - } - return_value = PyLong_FromLong(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_OpenProcess__doc__, -"OpenProcess($module, desired_access, inherit_handle, process_id, /)\n" -"--\n" -"\n"); - -#define _WINAPI_OPENPROCESS_METHODDEF \ - {"OpenProcess", (PyCFunction)_winapi_OpenProcess, METH_FASTCALL, _winapi_OpenProcess__doc__}, - -static HANDLE -_winapi_OpenProcess_impl(PyObject *module, DWORD desired_access, - BOOL inherit_handle, DWORD process_id); - -static PyObject * -_winapi_OpenProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - DWORD desired_access; - BOOL inherit_handle; - DWORD process_id; - HANDLE _return_value; - - if (!_PyArg_ParseStack(args, nargs, "kik:OpenProcess", - &desired_access, &inherit_handle, &process_id)) { - goto exit; - } - _return_value = _winapi_OpenProcess_impl(module, desired_access, inherit_handle, process_id); - if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { - goto exit; - } - if (_return_value == NULL) { - Py_RETURN_NONE; - } - return_value = HANDLE_TO_PYNUM(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_PeekNamedPipe__doc__, -"PeekNamedPipe($module, handle, size=0, /)\n" -"--\n" -"\n"); - -#define _WINAPI_PEEKNAMEDPIPE_METHODDEF \ - {"PeekNamedPipe", (PyCFunction)_winapi_PeekNamedPipe, METH_FASTCALL, _winapi_PeekNamedPipe__doc__}, - -static PyObject * -_winapi_PeekNamedPipe_impl(PyObject *module, HANDLE handle, int size); - -static PyObject * -_winapi_PeekNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE handle; - int size = 0; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "|i:PeekNamedPipe", - &handle, &size)) { - goto exit; - } - return_value = _winapi_PeekNamedPipe_impl(module, handle, size); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_ReadFile__doc__, -"ReadFile($module, /, handle, size, overlapped=False)\n" -"--\n" -"\n"); - -#define _WINAPI_READFILE_METHODDEF \ - {"ReadFile", (PyCFunction)_winapi_ReadFile, METH_FASTCALL|METH_KEYWORDS, _winapi_ReadFile__doc__}, - -static PyObject * -_winapi_ReadFile_impl(PyObject *module, HANDLE handle, int size, - int use_overlapped); - -static PyObject * -_winapi_ReadFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", "size", "overlapped", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE "i|i:ReadFile", _keywords, 0}; - HANDLE handle; - int size; - int use_overlapped = 0; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle, &size, &use_overlapped)) { - goto exit; - } - return_value = _winapi_ReadFile_impl(module, handle, size, use_overlapped); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_SetNamedPipeHandleState__doc__, -"SetNamedPipeHandleState($module, named_pipe, mode,\n" -" max_collection_count, collect_data_timeout, /)\n" -"--\n" -"\n"); - -#define _WINAPI_SETNAMEDPIPEHANDLESTATE_METHODDEF \ - {"SetNamedPipeHandleState", (PyCFunction)_winapi_SetNamedPipeHandleState, METH_FASTCALL, _winapi_SetNamedPipeHandleState__doc__}, - -static PyObject * -_winapi_SetNamedPipeHandleState_impl(PyObject *module, HANDLE named_pipe, - PyObject *mode, - PyObject *max_collection_count, - PyObject *collect_data_timeout); - -static PyObject * -_winapi_SetNamedPipeHandleState(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE named_pipe; - PyObject *mode; - PyObject *max_collection_count; - PyObject *collect_data_timeout; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "OOO:SetNamedPipeHandleState", - &named_pipe, &mode, &max_collection_count, &collect_data_timeout)) { - goto exit; - } - return_value = _winapi_SetNamedPipeHandleState_impl(module, named_pipe, mode, max_collection_count, collect_data_timeout); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_TerminateProcess__doc__, -"TerminateProcess($module, handle, exit_code, /)\n" -"--\n" -"\n" -"Terminate the specified process and all of its threads."); - -#define _WINAPI_TERMINATEPROCESS_METHODDEF \ - {"TerminateProcess", (PyCFunction)_winapi_TerminateProcess, METH_FASTCALL, _winapi_TerminateProcess__doc__}, - -static PyObject * -_winapi_TerminateProcess_impl(PyObject *module, HANDLE handle, - UINT exit_code); - -static PyObject * -_winapi_TerminateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE handle; - UINT exit_code; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "I:TerminateProcess", - &handle, &exit_code)) { - goto exit; - } - return_value = _winapi_TerminateProcess_impl(module, handle, exit_code); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WaitNamedPipe__doc__, -"WaitNamedPipe($module, name, timeout, /)\n" -"--\n" -"\n"); - -#define _WINAPI_WAITNAMEDPIPE_METHODDEF \ - {"WaitNamedPipe", (PyCFunction)_winapi_WaitNamedPipe, METH_FASTCALL, _winapi_WaitNamedPipe__doc__}, - -static PyObject * -_winapi_WaitNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD timeout); - -static PyObject * -_winapi_WaitNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCTSTR name; - DWORD timeout; - - if (!_PyArg_ParseStack(args, nargs, "sk:WaitNamedPipe", - &name, &timeout)) { - goto exit; - } - return_value = _winapi_WaitNamedPipe_impl(module, name, timeout); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WaitForMultipleObjects__doc__, -"WaitForMultipleObjects($module, handle_seq, wait_flag,\n" -" milliseconds=_winapi.INFINITE, /)\n" -"--\n" -"\n"); - -#define _WINAPI_WAITFORMULTIPLEOBJECTS_METHODDEF \ - {"WaitForMultipleObjects", (PyCFunction)_winapi_WaitForMultipleObjects, METH_FASTCALL, _winapi_WaitForMultipleObjects__doc__}, - -static PyObject * -_winapi_WaitForMultipleObjects_impl(PyObject *module, PyObject *handle_seq, - BOOL wait_flag, DWORD milliseconds); - -static PyObject * -_winapi_WaitForMultipleObjects(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - PyObject *handle_seq; - BOOL wait_flag; - DWORD milliseconds = INFINITE; - - if (!_PyArg_ParseStack(args, nargs, "Oi|k:WaitForMultipleObjects", - &handle_seq, &wait_flag, &milliseconds)) { - goto exit; - } - return_value = _winapi_WaitForMultipleObjects_impl(module, handle_seq, wait_flag, milliseconds); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WaitForSingleObject__doc__, -"WaitForSingleObject($module, handle, milliseconds, /)\n" -"--\n" -"\n" -"Wait for a single object.\n" -"\n" -"Wait until the specified object is in the signaled state or\n" -"the time-out interval elapses. The timeout value is specified\n" -"in milliseconds."); - -#define _WINAPI_WAITFORSINGLEOBJECT_METHODDEF \ - {"WaitForSingleObject", (PyCFunction)_winapi_WaitForSingleObject, METH_FASTCALL, _winapi_WaitForSingleObject__doc__}, - -static long -_winapi_WaitForSingleObject_impl(PyObject *module, HANDLE handle, - DWORD milliseconds); - -static PyObject * -_winapi_WaitForSingleObject(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - HANDLE handle; - DWORD milliseconds; - long _return_value; - - if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "k:WaitForSingleObject", - &handle, &milliseconds)) { - goto exit; - } - _return_value = _winapi_WaitForSingleObject_impl(module, handle, milliseconds); - if ((_return_value == -1) && PyErr_Occurred()) { - goto exit; - } - return_value = PyLong_FromLong(_return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_WriteFile__doc__, -"WriteFile($module, /, handle, buffer, overlapped=False)\n" -"--\n" -"\n"); - -#define _WINAPI_WRITEFILE_METHODDEF \ - {"WriteFile", (PyCFunction)_winapi_WriteFile, METH_FASTCALL|METH_KEYWORDS, _winapi_WriteFile__doc__}, - -static PyObject * -_winapi_WriteFile_impl(PyObject *module, HANDLE handle, PyObject *buffer, - int use_overlapped); - -static PyObject * -_winapi_WriteFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", "buffer", "overlapped", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE "O|i:WriteFile", _keywords, 0}; - HANDLE handle; - PyObject *buffer; - int use_overlapped = 0; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle, &buffer, &use_overlapped)) { - goto exit; - } - return_value = _winapi_WriteFile_impl(module, handle, buffer, use_overlapped); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_GetACP__doc__, -"GetACP($module, /)\n" -"--\n" -"\n" -"Get the current Windows ANSI code page identifier."); - -#define _WINAPI_GETACP_METHODDEF \ - {"GetACP", (PyCFunction)_winapi_GetACP, METH_NOARGS, _winapi_GetACP__doc__}, - -static PyObject * -_winapi_GetACP_impl(PyObject *module); - -static PyObject * -_winapi_GetACP(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - return _winapi_GetACP_impl(module); -} - -PyDoc_STRVAR(_winapi_GetFileType__doc__, -"GetFileType($module, /, handle)\n" -"--\n" -"\n"); - -#define _WINAPI_GETFILETYPE_METHODDEF \ - {"GetFileType", (PyCFunction)_winapi_GetFileType, METH_FASTCALL|METH_KEYWORDS, _winapi_GetFileType__doc__}, - -static DWORD -_winapi_GetFileType_impl(PyObject *module, HANDLE handle); - -static PyObject * -_winapi_GetFileType(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"handle", NULL}; - static _PyArg_Parser _parser = {"" F_HANDLE ":GetFileType", _keywords, 0}; - HANDLE handle; - DWORD _return_value; - - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &handle)) { - goto exit; - } - _return_value = _winapi_GetFileType_impl(module, handle); - if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { - goto exit; - } - return_value = Py_BuildValue("k", _return_value); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CopyFileExW__doc__, -"CopyFileExW($module, src, dst, flags, /)\n" -"--\n" -"\n" -"Efficiently copy 2 files."); - -#define _WINAPI_COPYFILEEXW_METHODDEF \ - {"CopyFileExW", (PyCFunction)_winapi_CopyFileExW, METH_FASTCALL, _winapi_CopyFileExW__doc__}, - -static PyObject * -_winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, - DWORD flags); - -static PyObject * -_winapi_CopyFileExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCWSTR src; - LPCWSTR dst; - DWORD flags; - - if (!_PyArg_ParseStack(args, nargs, "uuk:CopyFileExW", - &src, &dst, &flags)) { - goto exit; - } - return_value = _winapi_CopyFileExW_impl(module, src, dst, flags); - -exit: - return return_value; -} -/*[clinic end generated code: output=1f3c6672a1f704d3 input=a9049054013a1b77]*/ +/*[clinic input] +preserve +[clinic start generated code]*/ + +PyDoc_STRVAR(_winapi_Overlapped_GetOverlappedResult__doc__, +"GetOverlappedResult($self, wait, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OVERLAPPED_GETOVERLAPPEDRESULT_METHODDEF \ + {"GetOverlappedResult", (PyCFunction)_winapi_Overlapped_GetOverlappedResult, METH_O, _winapi_Overlapped_GetOverlappedResult__doc__}, + +static PyObject * +_winapi_Overlapped_GetOverlappedResult_impl(OverlappedObject *self, int wait); + +static PyObject * +_winapi_Overlapped_GetOverlappedResult(OverlappedObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + int wait; + + if (!PyArg_Parse(arg, "p:GetOverlappedResult", &wait)) { + goto exit; + } + return_value = _winapi_Overlapped_GetOverlappedResult_impl(self, wait); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_Overlapped_getbuffer__doc__, +"getbuffer($self, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OVERLAPPED_GETBUFFER_METHODDEF \ + {"getbuffer", (PyCFunction)_winapi_Overlapped_getbuffer, METH_NOARGS, _winapi_Overlapped_getbuffer__doc__}, + +static PyObject * +_winapi_Overlapped_getbuffer_impl(OverlappedObject *self); + +static PyObject * +_winapi_Overlapped_getbuffer(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_Overlapped_getbuffer_impl(self); +} + +PyDoc_STRVAR(_winapi_Overlapped_cancel__doc__, +"cancel($self, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OVERLAPPED_CANCEL_METHODDEF \ + {"cancel", (PyCFunction)_winapi_Overlapped_cancel, METH_NOARGS, _winapi_Overlapped_cancel__doc__}, + +static PyObject * +_winapi_Overlapped_cancel_impl(OverlappedObject *self); + +static PyObject * +_winapi_Overlapped_cancel(OverlappedObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_Overlapped_cancel_impl(self); +} + +PyDoc_STRVAR(_winapi_CloseHandle__doc__, +"CloseHandle($module, handle, /)\n" +"--\n" +"\n" +"Close handle."); + +#define _WINAPI_CLOSEHANDLE_METHODDEF \ + {"CloseHandle", (PyCFunction)_winapi_CloseHandle, METH_O, _winapi_CloseHandle__doc__}, + +static PyObject * +_winapi_CloseHandle_impl(PyObject *module, HANDLE handle); + +static PyObject * +_winapi_CloseHandle(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + HANDLE handle; + + if (!PyArg_Parse(arg, "" F_HANDLE ":CloseHandle", &handle)) { + goto exit; + } + return_value = _winapi_CloseHandle_impl(module, handle); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_ConnectNamedPipe__doc__, +"ConnectNamedPipe($module, /, handle, overlapped=False)\n" +"--\n" +"\n"); + +#define _WINAPI_CONNECTNAMEDPIPE_METHODDEF \ + {"ConnectNamedPipe", (PyCFunction)_winapi_ConnectNamedPipe, METH_FASTCALL|METH_KEYWORDS, _winapi_ConnectNamedPipe__doc__}, + +static PyObject * +_winapi_ConnectNamedPipe_impl(PyObject *module, HANDLE handle, + int use_overlapped); + +static PyObject * +_winapi_ConnectNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", "overlapped", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE "|i:ConnectNamedPipe", _keywords, 0}; + HANDLE handle; + int use_overlapped = 0; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle, &use_overlapped)) { + goto exit; + } + return_value = _winapi_ConnectNamedPipe_impl(module, handle, use_overlapped); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateFile__doc__, +"CreateFile($module, file_name, desired_access, share_mode,\n" +" security_attributes, creation_disposition,\n" +" flags_and_attributes, template_file, /)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATEFILE_METHODDEF \ + {"CreateFile", (PyCFunction)_winapi_CreateFile, METH_FASTCALL, _winapi_CreateFile__doc__}, + +static HANDLE +_winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, + DWORD desired_access, DWORD share_mode, + LPSECURITY_ATTRIBUTES security_attributes, + DWORD creation_disposition, + DWORD flags_and_attributes, HANDLE template_file); + +static PyObject * +_winapi_CreateFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCTSTR file_name; + DWORD desired_access; + DWORD share_mode; + LPSECURITY_ATTRIBUTES security_attributes; + DWORD creation_disposition; + DWORD flags_and_attributes; + HANDLE template_file; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "skk" F_POINTER "kk" F_HANDLE ":CreateFile", + &file_name, &desired_access, &share_mode, &security_attributes, &creation_disposition, &flags_and_attributes, &template_file)) { + goto exit; + } + _return_value = _winapi_CreateFile_impl(module, file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateJunction__doc__, +"CreateJunction($module, src_path, dst_path, /)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATEJUNCTION_METHODDEF \ + {"CreateJunction", (PyCFunction)_winapi_CreateJunction, METH_FASTCALL, _winapi_CreateJunction__doc__}, + +static PyObject * +_winapi_CreateJunction_impl(PyObject *module, LPWSTR src_path, + LPWSTR dst_path); + +static PyObject * +_winapi_CreateJunction(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPWSTR src_path; + LPWSTR dst_path; + + if (!_PyArg_ParseStack(args, nargs, "uu:CreateJunction", + &src_path, &dst_path)) { + goto exit; + } + return_value = _winapi_CreateJunction_impl(module, src_path, dst_path); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateNamedPipe__doc__, +"CreateNamedPipe($module, name, open_mode, pipe_mode, max_instances,\n" +" out_buffer_size, in_buffer_size, default_timeout,\n" +" security_attributes, /)\n" +"--\n" +"\n"); + +#define _WINAPI_CREATENAMEDPIPE_METHODDEF \ + {"CreateNamedPipe", (PyCFunction)_winapi_CreateNamedPipe, METH_FASTCALL, _winapi_CreateNamedPipe__doc__}, + +static HANDLE +_winapi_CreateNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD open_mode, + DWORD pipe_mode, DWORD max_instances, + DWORD out_buffer_size, DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +static PyObject * +_winapi_CreateNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCTSTR name; + DWORD open_mode; + DWORD pipe_mode; + DWORD max_instances; + DWORD out_buffer_size; + DWORD in_buffer_size; + DWORD default_timeout; + LPSECURITY_ATTRIBUTES security_attributes; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "skkkkkk" F_POINTER ":CreateNamedPipe", + &name, &open_mode, &pipe_mode, &max_instances, &out_buffer_size, &in_buffer_size, &default_timeout, &security_attributes)) { + goto exit; + } + _return_value = _winapi_CreateNamedPipe_impl(module, name, open_mode, pipe_mode, max_instances, out_buffer_size, in_buffer_size, default_timeout, security_attributes); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreatePipe__doc__, +"CreatePipe($module, pipe_attrs, size, /)\n" +"--\n" +"\n" +"Create an anonymous pipe.\n" +"\n" +" pipe_attrs\n" +" Ignored internally, can be None.\n" +"\n" +"Returns a 2-tuple of handles, to the read and write ends of the pipe."); + +#define _WINAPI_CREATEPIPE_METHODDEF \ + {"CreatePipe", (PyCFunction)_winapi_CreatePipe, METH_FASTCALL, _winapi_CreatePipe__doc__}, + +static PyObject * +_winapi_CreatePipe_impl(PyObject *module, PyObject *pipe_attrs, DWORD size); + +static PyObject * +_winapi_CreatePipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *pipe_attrs; + DWORD size; + + if (!_PyArg_ParseStack(args, nargs, "Ok:CreatePipe", + &pipe_attrs, &size)) { + goto exit; + } + return_value = _winapi_CreatePipe_impl(module, pipe_attrs, size); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CreateProcess__doc__, +"CreateProcess($module, application_name, command_line, proc_attrs,\n" +" thread_attrs, inherit_handles, creation_flags,\n" +" env_mapping, current_directory, startup_info, /)\n" +"--\n" +"\n" +"Create a new process and its primary thread.\n" +"\n" +" proc_attrs\n" +" Ignored internally, can be None.\n" +" thread_attrs\n" +" Ignored internally, can be None.\n" +"\n" +"The return value is a tuple of the process handle, thread handle,\n" +"process ID, and thread ID."); + +#define _WINAPI_CREATEPROCESS_METHODDEF \ + {"CreateProcess", (PyCFunction)_winapi_CreateProcess, METH_FASTCALL, _winapi_CreateProcess__doc__}, + +static PyObject * +_winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name, + Py_UNICODE *command_line, PyObject *proc_attrs, + PyObject *thread_attrs, BOOL inherit_handles, + DWORD creation_flags, PyObject *env_mapping, + Py_UNICODE *current_directory, + PyObject *startup_info); + +static PyObject * +_winapi_CreateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_UNICODE *application_name; + Py_UNICODE *command_line; + PyObject *proc_attrs; + PyObject *thread_attrs; + BOOL inherit_handles; + DWORD creation_flags; + PyObject *env_mapping; + Py_UNICODE *current_directory; + PyObject *startup_info; + + if (!_PyArg_ParseStack(args, nargs, "ZZOOikOZO:CreateProcess", + &application_name, &command_line, &proc_attrs, &thread_attrs, &inherit_handles, &creation_flags, &env_mapping, ¤t_directory, &startup_info)) { + goto exit; + } + return_value = _winapi_CreateProcess_impl(module, application_name, command_line, proc_attrs, thread_attrs, inherit_handles, creation_flags, env_mapping, current_directory, startup_info); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_DuplicateHandle__doc__, +"DuplicateHandle($module, source_process_handle, source_handle,\n" +" target_process_handle, desired_access, inherit_handle,\n" +" options=0, /)\n" +"--\n" +"\n" +"Return a duplicate handle object.\n" +"\n" +"The duplicate handle refers to the same object as the original\n" +"handle. Therefore, any changes to the object are reflected\n" +"through both handles."); + +#define _WINAPI_DUPLICATEHANDLE_METHODDEF \ + {"DuplicateHandle", (PyCFunction)_winapi_DuplicateHandle, METH_FASTCALL, _winapi_DuplicateHandle__doc__}, + +static HANDLE +_winapi_DuplicateHandle_impl(PyObject *module, HANDLE source_process_handle, + HANDLE source_handle, + HANDLE target_process_handle, + DWORD desired_access, BOOL inherit_handle, + DWORD options); + +static PyObject * +_winapi_DuplicateHandle(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE source_process_handle; + HANDLE source_handle; + HANDLE target_process_handle; + DWORD desired_access; + BOOL inherit_handle; + DWORD options = 0; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "" F_HANDLE "" F_HANDLE "ki|k:DuplicateHandle", + &source_process_handle, &source_handle, &target_process_handle, &desired_access, &inherit_handle, &options)) { + goto exit; + } + _return_value = _winapi_DuplicateHandle_impl(module, source_process_handle, source_handle, target_process_handle, desired_access, inherit_handle, options); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_ExitProcess__doc__, +"ExitProcess($module, ExitCode, /)\n" +"--\n" +"\n"); + +#define _WINAPI_EXITPROCESS_METHODDEF \ + {"ExitProcess", (PyCFunction)_winapi_ExitProcess, METH_O, _winapi_ExitProcess__doc__}, + +static PyObject * +_winapi_ExitProcess_impl(PyObject *module, UINT ExitCode); + +static PyObject * +_winapi_ExitProcess(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + UINT ExitCode; + + if (!PyArg_Parse(arg, "I:ExitProcess", &ExitCode)) { + goto exit; + } + return_value = _winapi_ExitProcess_impl(module, ExitCode); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetCurrentProcess__doc__, +"GetCurrentProcess($module, /)\n" +"--\n" +"\n" +"Return a handle object for the current process."); + +#define _WINAPI_GETCURRENTPROCESS_METHODDEF \ + {"GetCurrentProcess", (PyCFunction)_winapi_GetCurrentProcess, METH_NOARGS, _winapi_GetCurrentProcess__doc__}, + +static HANDLE +_winapi_GetCurrentProcess_impl(PyObject *module); + +static PyObject * +_winapi_GetCurrentProcess(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + HANDLE _return_value; + + _return_value = _winapi_GetCurrentProcess_impl(module); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetExitCodeProcess__doc__, +"GetExitCodeProcess($module, process, /)\n" +"--\n" +"\n" +"Return the termination status of the specified process."); + +#define _WINAPI_GETEXITCODEPROCESS_METHODDEF \ + {"GetExitCodeProcess", (PyCFunction)_winapi_GetExitCodeProcess, METH_O, _winapi_GetExitCodeProcess__doc__}, + +static DWORD +_winapi_GetExitCodeProcess_impl(PyObject *module, HANDLE process); + +static PyObject * +_winapi_GetExitCodeProcess(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + HANDLE process; + DWORD _return_value; + + if (!PyArg_Parse(arg, "" F_HANDLE ":GetExitCodeProcess", &process)) { + goto exit; + } + _return_value = _winapi_GetExitCodeProcess_impl(module, process); + if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { + goto exit; + } + return_value = Py_BuildValue("k", _return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetLastError__doc__, +"GetLastError($module, /)\n" +"--\n" +"\n"); + +#define _WINAPI_GETLASTERROR_METHODDEF \ + {"GetLastError", (PyCFunction)_winapi_GetLastError, METH_NOARGS, _winapi_GetLastError__doc__}, + +static DWORD +_winapi_GetLastError_impl(PyObject *module); + +static PyObject * +_winapi_GetLastError(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + DWORD _return_value; + + _return_value = _winapi_GetLastError_impl(module); + if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { + goto exit; + } + return_value = Py_BuildValue("k", _return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetModuleFileName__doc__, +"GetModuleFileName($module, module_handle, /)\n" +"--\n" +"\n" +"Return the fully-qualified path for the file that contains module.\n" +"\n" +"The module must have been loaded by the current process.\n" +"\n" +"The module parameter should be a handle to the loaded module\n" +"whose path is being requested. If this parameter is 0,\n" +"GetModuleFileName retrieves the path of the executable file\n" +"of the current process."); + +#define _WINAPI_GETMODULEFILENAME_METHODDEF \ + {"GetModuleFileName", (PyCFunction)_winapi_GetModuleFileName, METH_O, _winapi_GetModuleFileName__doc__}, + +static PyObject * +_winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle); + +static PyObject * +_winapi_GetModuleFileName(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + HMODULE module_handle; + + if (!PyArg_Parse(arg, "" F_HANDLE ":GetModuleFileName", &module_handle)) { + goto exit; + } + return_value = _winapi_GetModuleFileName_impl(module, module_handle); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetStdHandle__doc__, +"GetStdHandle($module, std_handle, /)\n" +"--\n" +"\n" +"Return a handle to the specified standard device.\n" +"\n" +" std_handle\n" +" One of STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, or STD_ERROR_HANDLE.\n" +"\n" +"The integer associated with the handle object is returned."); + +#define _WINAPI_GETSTDHANDLE_METHODDEF \ + {"GetStdHandle", (PyCFunction)_winapi_GetStdHandle, METH_O, _winapi_GetStdHandle__doc__}, + +static HANDLE +_winapi_GetStdHandle_impl(PyObject *module, DWORD std_handle); + +static PyObject * +_winapi_GetStdHandle(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + DWORD std_handle; + HANDLE _return_value; + + if (!PyArg_Parse(arg, "k:GetStdHandle", &std_handle)) { + goto exit; + } + _return_value = _winapi_GetStdHandle_impl(module, std_handle); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetVersion__doc__, +"GetVersion($module, /)\n" +"--\n" +"\n" +"Return the version number of the current operating system."); + +#define _WINAPI_GETVERSION_METHODDEF \ + {"GetVersion", (PyCFunction)_winapi_GetVersion, METH_NOARGS, _winapi_GetVersion__doc__}, + +static long +_winapi_GetVersion_impl(PyObject *module); + +static PyObject * +_winapi_GetVersion(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + long _return_value; + + _return_value = _winapi_GetVersion_impl(module); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_OpenProcess__doc__, +"OpenProcess($module, desired_access, inherit_handle, process_id, /)\n" +"--\n" +"\n"); + +#define _WINAPI_OPENPROCESS_METHODDEF \ + {"OpenProcess", (PyCFunction)_winapi_OpenProcess, METH_FASTCALL, _winapi_OpenProcess__doc__}, + +static HANDLE +_winapi_OpenProcess_impl(PyObject *module, DWORD desired_access, + BOOL inherit_handle, DWORD process_id); + +static PyObject * +_winapi_OpenProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + DWORD desired_access; + BOOL inherit_handle; + DWORD process_id; + HANDLE _return_value; + + if (!_PyArg_ParseStack(args, nargs, "kik:OpenProcess", + &desired_access, &inherit_handle, &process_id)) { + goto exit; + } + _return_value = _winapi_OpenProcess_impl(module, desired_access, inherit_handle, process_id); + if ((_return_value == INVALID_HANDLE_VALUE) && PyErr_Occurred()) { + goto exit; + } + if (_return_value == NULL) { + Py_RETURN_NONE; + } + return_value = HANDLE_TO_PYNUM(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_PeekNamedPipe__doc__, +"PeekNamedPipe($module, handle, size=0, /)\n" +"--\n" +"\n"); + +#define _WINAPI_PEEKNAMEDPIPE_METHODDEF \ + {"PeekNamedPipe", (PyCFunction)_winapi_PeekNamedPipe, METH_FASTCALL, _winapi_PeekNamedPipe__doc__}, + +static PyObject * +_winapi_PeekNamedPipe_impl(PyObject *module, HANDLE handle, int size); + +static PyObject * +_winapi_PeekNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE handle; + int size = 0; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "|i:PeekNamedPipe", + &handle, &size)) { + goto exit; + } + return_value = _winapi_PeekNamedPipe_impl(module, handle, size); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_ReadFile__doc__, +"ReadFile($module, /, handle, size, overlapped=False)\n" +"--\n" +"\n"); + +#define _WINAPI_READFILE_METHODDEF \ + {"ReadFile", (PyCFunction)_winapi_ReadFile, METH_FASTCALL|METH_KEYWORDS, _winapi_ReadFile__doc__}, + +static PyObject * +_winapi_ReadFile_impl(PyObject *module, HANDLE handle, int size, + int use_overlapped); + +static PyObject * +_winapi_ReadFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", "size", "overlapped", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE "i|i:ReadFile", _keywords, 0}; + HANDLE handle; + int size; + int use_overlapped = 0; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle, &size, &use_overlapped)) { + goto exit; + } + return_value = _winapi_ReadFile_impl(module, handle, size, use_overlapped); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_SetNamedPipeHandleState__doc__, +"SetNamedPipeHandleState($module, named_pipe, mode,\n" +" max_collection_count, collect_data_timeout, /)\n" +"--\n" +"\n"); + +#define _WINAPI_SETNAMEDPIPEHANDLESTATE_METHODDEF \ + {"SetNamedPipeHandleState", (PyCFunction)_winapi_SetNamedPipeHandleState, METH_FASTCALL, _winapi_SetNamedPipeHandleState__doc__}, + +static PyObject * +_winapi_SetNamedPipeHandleState_impl(PyObject *module, HANDLE named_pipe, + PyObject *mode, + PyObject *max_collection_count, + PyObject *collect_data_timeout); + +static PyObject * +_winapi_SetNamedPipeHandleState(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE named_pipe; + PyObject *mode; + PyObject *max_collection_count; + PyObject *collect_data_timeout; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "OOO:SetNamedPipeHandleState", + &named_pipe, &mode, &max_collection_count, &collect_data_timeout)) { + goto exit; + } + return_value = _winapi_SetNamedPipeHandleState_impl(module, named_pipe, mode, max_collection_count, collect_data_timeout); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_TerminateProcess__doc__, +"TerminateProcess($module, handle, exit_code, /)\n" +"--\n" +"\n" +"Terminate the specified process and all of its threads."); + +#define _WINAPI_TERMINATEPROCESS_METHODDEF \ + {"TerminateProcess", (PyCFunction)_winapi_TerminateProcess, METH_FASTCALL, _winapi_TerminateProcess__doc__}, + +static PyObject * +_winapi_TerminateProcess_impl(PyObject *module, HANDLE handle, + UINT exit_code); + +static PyObject * +_winapi_TerminateProcess(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE handle; + UINT exit_code; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "I:TerminateProcess", + &handle, &exit_code)) { + goto exit; + } + return_value = _winapi_TerminateProcess_impl(module, handle, exit_code); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WaitNamedPipe__doc__, +"WaitNamedPipe($module, name, timeout, /)\n" +"--\n" +"\n"); + +#define _WINAPI_WAITNAMEDPIPE_METHODDEF \ + {"WaitNamedPipe", (PyCFunction)_winapi_WaitNamedPipe, METH_FASTCALL, _winapi_WaitNamedPipe__doc__}, + +static PyObject * +_winapi_WaitNamedPipe_impl(PyObject *module, LPCTSTR name, DWORD timeout); + +static PyObject * +_winapi_WaitNamedPipe(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCTSTR name; + DWORD timeout; + + if (!_PyArg_ParseStack(args, nargs, "sk:WaitNamedPipe", + &name, &timeout)) { + goto exit; + } + return_value = _winapi_WaitNamedPipe_impl(module, name, timeout); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WaitForMultipleObjects__doc__, +"WaitForMultipleObjects($module, handle_seq, wait_flag,\n" +" milliseconds=_winapi.INFINITE, /)\n" +"--\n" +"\n"); + +#define _WINAPI_WAITFORMULTIPLEOBJECTS_METHODDEF \ + {"WaitForMultipleObjects", (PyCFunction)_winapi_WaitForMultipleObjects, METH_FASTCALL, _winapi_WaitForMultipleObjects__doc__}, + +static PyObject * +_winapi_WaitForMultipleObjects_impl(PyObject *module, PyObject *handle_seq, + BOOL wait_flag, DWORD milliseconds); + +static PyObject * +_winapi_WaitForMultipleObjects(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *handle_seq; + BOOL wait_flag; + DWORD milliseconds = INFINITE; + + if (!_PyArg_ParseStack(args, nargs, "Oi|k:WaitForMultipleObjects", + &handle_seq, &wait_flag, &milliseconds)) { + goto exit; + } + return_value = _winapi_WaitForMultipleObjects_impl(module, handle_seq, wait_flag, milliseconds); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WaitForSingleObject__doc__, +"WaitForSingleObject($module, handle, milliseconds, /)\n" +"--\n" +"\n" +"Wait for a single object.\n" +"\n" +"Wait until the specified object is in the signaled state or\n" +"the time-out interval elapses. The timeout value is specified\n" +"in milliseconds."); + +#define _WINAPI_WAITFORSINGLEOBJECT_METHODDEF \ + {"WaitForSingleObject", (PyCFunction)_winapi_WaitForSingleObject, METH_FASTCALL, _winapi_WaitForSingleObject__doc__}, + +static long +_winapi_WaitForSingleObject_impl(PyObject *module, HANDLE handle, + DWORD milliseconds); + +static PyObject * +_winapi_WaitForSingleObject(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + HANDLE handle; + DWORD milliseconds; + long _return_value; + + if (!_PyArg_ParseStack(args, nargs, "" F_HANDLE "k:WaitForSingleObject", + &handle, &milliseconds)) { + goto exit; + } + _return_value = _winapi_WaitForSingleObject_impl(module, handle, milliseconds); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_WriteFile__doc__, +"WriteFile($module, /, handle, buffer, overlapped=False)\n" +"--\n" +"\n"); + +#define _WINAPI_WRITEFILE_METHODDEF \ + {"WriteFile", (PyCFunction)_winapi_WriteFile, METH_FASTCALL|METH_KEYWORDS, _winapi_WriteFile__doc__}, + +static PyObject * +_winapi_WriteFile_impl(PyObject *module, HANDLE handle, PyObject *buffer, + int use_overlapped); + +static PyObject * +_winapi_WriteFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", "buffer", "overlapped", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE "O|i:WriteFile", _keywords, 0}; + HANDLE handle; + PyObject *buffer; + int use_overlapped = 0; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle, &buffer, &use_overlapped)) { + goto exit; + } + return_value = _winapi_WriteFile_impl(module, handle, buffer, use_overlapped); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_GetACP__doc__, +"GetACP($module, /)\n" +"--\n" +"\n" +"Get the current Windows ANSI code page identifier."); + +#define _WINAPI_GETACP_METHODDEF \ + {"GetACP", (PyCFunction)_winapi_GetACP, METH_NOARGS, _winapi_GetACP__doc__}, + +static PyObject * +_winapi_GetACP_impl(PyObject *module); + +static PyObject * +_winapi_GetACP(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_GetACP_impl(module); +} + +PyDoc_STRVAR(_winapi_GetFileType__doc__, +"GetFileType($module, /, handle)\n" +"--\n" +"\n"); + +#define _WINAPI_GETFILETYPE_METHODDEF \ + {"GetFileType", (PyCFunction)_winapi_GetFileType, METH_FASTCALL|METH_KEYWORDS, _winapi_GetFileType__doc__}, + +static DWORD +_winapi_GetFileType_impl(PyObject *module, HANDLE handle); + +static PyObject * +_winapi_GetFileType(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"handle", NULL}; + static _PyArg_Parser _parser = {"" F_HANDLE ":GetFileType", _keywords, 0}; + HANDLE handle; + DWORD _return_value; + + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &handle)) { + goto exit; + } + _return_value = _winapi_GetFileType_impl(module, handle); + if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) { + goto exit; + } + return_value = Py_BuildValue("k", _return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_winapi_CopyFileExW__doc__, +"CopyFileExW($module, src, dst, flags, /)\n" +"--\n" +"\n" +"Efficiently copy 2 files."); + +#define _WINAPI_COPYFILEEXW_METHODDEF \ + {"CopyFileExW", (PyCFunction)_winapi_CopyFileExW, METH_FASTCALL, _winapi_CopyFileExW__doc__}, + +static PyObject * +_winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, + DWORD flags); + +static PyObject * +_winapi_CopyFileExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCWSTR src; + LPCWSTR dst; + DWORD flags; + + if (!_PyArg_ParseStack(args, nargs, "uuk:CopyFileExW", + &src, &dst, &flags)) { + goto exit; + } + return_value = _winapi_CopyFileExW_impl(module, src, dst, flags); + +exit: + return return_value; +} +/*[clinic end generated code: output=1f3c6672a1f704d3 input=a9049054013a1b77]*/ From 18238289976eef423304a28ec016b7f440176cae Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 30 May 2018 21:05:40 +0200 Subject: [PATCH 064/111] add tests for mode bits --- Lib/test/test_shutil.py | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7c23a288936796..e5f3a55dea9742 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2188,6 +2188,65 @@ def test_fallback(self): self.assertEqual(size.lines, 40) +class TestModeBits(unittest.TestCase): + + def setUp(self): + # /a + self.src = TESTFN + # /b + self.dst = TESTFN2 + # /dir-a + self.srcdir = TESTFN + '-dir' + # /dir-b + self.dstdir = TESTFN2 + '-dir' + # /dir-a/a + self.subfile_src = os.path.join(self.srcdir, TESTFN) + # /dir-a/b + self.subfile_dst = os.path.join(self.dstdir, TESTFN) + + # Create src tree /a, /dir-a/a. + self.tearDown() + write_file(self.src, 'foo') + os.mkdir(self.srcdir) + write_file(self.subfile_src, 'foo') + + # Set non-standard permission bits for the 2 files. + os.chmod(self.src, stat.S_IRWXU | stat.S_IRWXG) + os.chmod(self.subfile_src, stat.S_IRWXU | stat.S_IRWXG) + + def tearDown(self): + support.unlink(self.src) + support.unlink(self.dst) + shutil.rmtree(self.srcdir, ignore_errors=True) + shutil.rmtree(self.dstdir, ignore_errors=True) + + def assert_mode_equal(self, src, dst): + self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) + + def assert_mode_not_equal(self, src, dst): + self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode) + + # --- + + def test_copyfile_and_copymode(self): + shutil.copyfile(self.src, self.dst) + self.assert_mode_not_equal(self.src, self.dst) + shutil.copymode(self.src, self.dst) + self.assert_mode_equal(self.src, self.dst) + + def test_copy(self): + shutil.copy(self.src, self.dst) + self.assert_mode_equal(self.src, self.dst) + + def test_copy2(self): + shutil.copy2(self.src, self.dst) + self.assert_mode_equal(self.src, self.dst) + + def test_copytree(self): + shutil.copytree(self.srcdir, self.dstdir) + self.assert_mode_equal(self.subfile_src, self.subfile_dst) + + class PublicAPITests(unittest.TestCase): """Ensures that the correct values are exposed in the public API.""" From a9d6a073994a5e89da26b3e3d8de0ea9a87dca97 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 00:11:27 +0200 Subject: [PATCH 065/111] add docstring --- Lib/test/test_shutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index e5f3a55dea9742..bd333b9fe99a35 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2189,6 +2189,7 @@ def test_fallback(self): class TestModeBits(unittest.TestCase): + """Make sure all copy* functions update dst file mode.""" def setUp(self): # /a From e3ce9172e6b9d6f9c243bdb99785e79245211c51 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 00:13:59 +0200 Subject: [PATCH 066/111] remove test file mode class; let's keep it for later when Istart addressing OSX fcopyfile() specific copies --- Lib/test/test_shutil.py | 60 ----------------------------------------- 1 file changed, 60 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index bd333b9fe99a35..7c23a288936796 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2188,66 +2188,6 @@ def test_fallback(self): self.assertEqual(size.lines, 40) -class TestModeBits(unittest.TestCase): - """Make sure all copy* functions update dst file mode.""" - - def setUp(self): - # /a - self.src = TESTFN - # /b - self.dst = TESTFN2 - # /dir-a - self.srcdir = TESTFN + '-dir' - # /dir-b - self.dstdir = TESTFN2 + '-dir' - # /dir-a/a - self.subfile_src = os.path.join(self.srcdir, TESTFN) - # /dir-a/b - self.subfile_dst = os.path.join(self.dstdir, TESTFN) - - # Create src tree /a, /dir-a/a. - self.tearDown() - write_file(self.src, 'foo') - os.mkdir(self.srcdir) - write_file(self.subfile_src, 'foo') - - # Set non-standard permission bits for the 2 files. - os.chmod(self.src, stat.S_IRWXU | stat.S_IRWXG) - os.chmod(self.subfile_src, stat.S_IRWXU | stat.S_IRWXG) - - def tearDown(self): - support.unlink(self.src) - support.unlink(self.dst) - shutil.rmtree(self.srcdir, ignore_errors=True) - shutil.rmtree(self.dstdir, ignore_errors=True) - - def assert_mode_equal(self, src, dst): - self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) - - def assert_mode_not_equal(self, src, dst): - self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode) - - # --- - - def test_copyfile_and_copymode(self): - shutil.copyfile(self.src, self.dst) - self.assert_mode_not_equal(self.src, self.dst) - shutil.copymode(self.src, self.dst) - self.assert_mode_equal(self.src, self.dst) - - def test_copy(self): - shutil.copy(self.src, self.dst) - self.assert_mode_equal(self.src, self.dst) - - def test_copy2(self): - shutil.copy2(self.src, self.dst) - self.assert_mode_equal(self.src, self.dst) - - def test_copytree(self): - shutil.copytree(self.srcdir, self.dstdir) - self.assert_mode_equal(self.subfile_src, self.subfile_dst) - - class PublicAPITests(unittest.TestCase): """Ensures that the correct values are exposed in the public API.""" From f81a0ec9da517feeca8faaabb221edd9e70b9b74 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 01:14:10 +0200 Subject: [PATCH 067/111] update doc to reflect new changes --- Doc/library/shutil.rst | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 4aecde0184b235..783fed9c423dcb 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -77,7 +77,7 @@ Directory and files operations a subclass of the latter, this change is backward compatible. .. versionchanged:: 3.8 - Platform-specific zero-copy syscalls are used internally in order to copy + Platform-specific fast-copy syscalls are used internally in order to copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. @@ -170,7 +170,7 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Platform-specific zero-copy syscalls are used internally in order to copy + Platform-specific fast-copy syscalls are used internally in order to copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. @@ -197,7 +197,7 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Platform-specific zero-copy syscalls are used internally in order to copy + Platform-specific fast-copy syscalls are used internally in order to copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. @@ -258,7 +258,7 @@ Directory and files operations errors when *symlinks* is false. .. versionchanged:: 3.8 - Platform-specific zero-copy syscalls are used internally in order to copy + Platform-specific fast-copy syscalls are used internally in order to copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. @@ -335,7 +335,7 @@ Directory and files operations Added the *copy_function* keyword argument. .. versionchanged:: 3.8 - Platform-specific zero-copy syscalls are used internally in order to copy + Platform-specific fast-copy syscalls are used internally in order to copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. @@ -400,20 +400,21 @@ Directory and files operations Platform-dependent efficient copy operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Starting from Python 3.8 :func:`copyfile` uses platform-specific "fast-copy" -syscalls in order to copy the file more efficiently (see :issue:`33671`). +Starting from Python 3.8 `shutil.copy*` functions use platform-specific +"fast-copy" syscalls in order to copy the file more efficiently (see +:issue:`33671`). "fast-copy" means that the copying operation occurs within the kernel, avoiding the use of userspace buffers in Python as in "``outfd.write(infd.read())``". -On Linux and Solaris or other POSIX platforms where :func:`os.sendfile` allows -copies between regular file descriptors :func:`os.sendfile` is used. -On Windows and OSX `CopyFile`_ and `fcopyfile`_ are used respectively. + +On OSX `fcopyfile`_ is used to copy the file content (not metadata). +On Linux and Solaris or other POSIX platforms +where :func:`os.sendfile` allows copies between regular file descriptors +:func:`os.sendfile` is used. +On Windows `CopyFile`_ is used by all copy functions except :func:`copyfile`. + If the zero-copy operation fails and no data was written in the destination -file then :func:`copyfile` will silently fallback on using less efficient +file then shutil will silently fallback on using less efficient :func:`copyfileobj` function internally. -All functions relying on :func:`copyfile` internally will benefit from the same -speedup. -These are :func:`shutil.copy`, :func:`shutil.copy2`, :func:`shutil.copytree` -and :func:`shutil.move`. .. versionadded:: 3.8 From 3e7475bc42910108b9c5054156ec030684fdc6b8 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 01:38:36 +0200 Subject: [PATCH 068/111] update doc --- Doc/library/shutil.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 783fed9c423dcb..ec2d220f36e2ff 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -400,19 +400,20 @@ Directory and files operations Platform-dependent efficient copy operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Starting from Python 3.8 `shutil.copy*` functions use platform-specific -"fast-copy" syscalls in order to copy the file more efficiently (see -:issue:`33671`). +Starting from Python 3.8 all functions involving a file copy (:func:`copyfile`, +:func:`copy`, :func:`copy2`, :func:`copytree`, and :func:`move`) use +platform-specific "fast-copy" syscalls in order to copy the file more +efficiently (see :issue:`33671`). "fast-copy" means that the copying operation occurs within the kernel, avoiding the use of userspace buffers in Python as in "``outfd.write(infd.read())``". On OSX `fcopyfile`_ is used to copy the file content (not metadata). -On Linux and Solaris or other POSIX platforms -where :func:`os.sendfile` allows copies between regular file descriptors +On Linux, Solaris and other POSIX platforms +where :func:`os.sendfile` supports copies between 2 regular file descriptors :func:`os.sendfile` is used. On Windows `CopyFile`_ is used by all copy functions except :func:`copyfile`. -If the zero-copy operation fails and no data was written in the destination +If the fast-copy operation fails and no data was written in the destination file then shutil will silently fallback on using less efficient :func:`copyfileobj` function internally. From 05dd3cffb9f0e04fcdb4fa64812797f240f47503 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 08:25:40 -0700 Subject: [PATCH 069/111] adjust tests on win --- Lib/shutil.py | 7 +++---- Lib/test/test_shutil.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 7bd4b81d5b681f..98e74b49c2309a 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -229,10 +229,6 @@ def copyfile(src, dst, *, follow_symlinks=True): if not follow_symlinks and os.path.islink(src): os.symlink(os.readlink(src), dst) else: - if os.name == 'nt': - _fastcopy_win(src, dst) - return dst - with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: _fastcopy_fileobj(fsrc, fdst) @@ -370,6 +366,9 @@ def copy2(src, dst, *, follow_symlinks=True): """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) + if os.name == 'nt': + _fastcopy_win(src, dst) + return dst copyfile(src, dst, follow_symlinks=follow_symlinks) copystat(src, dst, follow_symlinks=follow_symlinks) return dst diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7c23a288936796..3ec0615beba6fc 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1926,6 +1926,12 @@ def test_regular_copy(self): with self.get_files() as (src, dst): self.zerocopy_fun(src, dst) self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + # Make sure the fallback function is not called. + with self.get_files() as (src, dst): + fun = shutil.copy2 if os.name == 'nt' else shutil.copyfile + with unittest.mock.patch('shutil.copyfileobj') as m: + fun(TESTFN, TESTFN2) + assert not m.called @unittest.skipIf(os.name == 'nt', 'POSIX only') def test_non_regular_file_src(self): @@ -1970,8 +1976,8 @@ def test_empty_file(self): def test_unhandled_exception(self): with unittest.mock.patch(self.PATCHPOINT, side_effect=ZeroDivisionError): - self.assertRaises(ZeroDivisionError, - shutil.copyfile, TESTFN, TESTFN2) + fun = shutil.copy2 if os.name == 'nt' else shutil.copyfile + self.assertRaises(ZeroDivisionError, fun, TESTFN, TESTFN2) @unittest.skipIf(os.name == 'nt', 'POSIX only') def test_exception_on_first_call(self): From 9b54930f24150b7242b968e4afff91d03c77d292 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 17:34:04 +0200 Subject: [PATCH 070/111] fix argument clinic error --- Modules/clinic/posixmodule.c.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index d385e1689fd0b7..1c98a1b2a6c49e 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3886,7 +3886,6 @@ os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #endif /* defined(__APPLE__) */ - PyDoc_STRVAR(os_fstat__doc__, "fstat($module, /, fd)\n" "--\n" @@ -6627,4 +6626,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=6e1b458924d141ed input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9071e1a8268b2f33 input=a9049054013a1b77]*/ From 2bec11c96561bded76cfa5695f309a626e270030 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 17:40:23 +0200 Subject: [PATCH 071/111] update doc --- Doc/library/shutil.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index ec2d220f36e2ff..b4fa0d6afc22b8 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -408,10 +408,9 @@ efficiently (see :issue:`33671`). the use of userspace buffers in Python as in "``outfd.write(infd.read())``". On OSX `fcopyfile`_ is used to copy the file content (not metadata). -On Linux, Solaris and other POSIX platforms -where :func:`os.sendfile` supports copies between 2 regular file descriptors -:func:`os.sendfile` is used. -On Windows `CopyFile`_ is used by all copy functions except :func:`copyfile`. +On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports +copies between 2 regular file descriptors :func:`os.sendfile` is used. +On Windows `CopyFileEx`_ is used by all copy functions except :func:`copyfile`. If the fast-copy operation fails and no data was written in the destination file then shutil will silently fallback on using less efficient @@ -702,8 +701,8 @@ Querying the size of the output terminal .. versionadded:: 3.3 -.. _`CopyFile`: - https://msdn.microsoft.com/en-us/library/windows/desktop/aa363851(v=vs.85).aspx +.. _`CopyFileEx`: + https://msdn.microsoft.com/en-us/library/windows/desktop/aa363852(v=vs.85).aspx .. _`fcopyfile`: http://www.manpagez.com/man/3/fcopyfile/ From c87648f8a4517c079912b31e584e3bdbc72853b4 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 18:20:30 +0200 Subject: [PATCH 072/111] OSX: expose copyfile(3) instead of fcopyfile(3); also expose flags arg to python --- Doc/library/shutil.rst | 6 ++-- Lib/shutil.py | 45 +++++++++++++--------------- Lib/test/test_shutil.py | 54 +++++++++++++++++----------------- Modules/clinic/posixmodule.c.h | 32 ++++++++++---------- Modules/posixmodule.c | 22 ++++++++------ 5 files changed, 80 insertions(+), 79 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index b4fa0d6afc22b8..1744855e959bb8 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -407,7 +407,7 @@ efficiently (see :issue:`33671`). "fast-copy" means that the copying operation occurs within the kernel, avoiding the use of userspace buffers in Python as in "``outfd.write(infd.read())``". -On OSX `fcopyfile`_ is used to copy the file content (not metadata). +On OSX `copyfile`_ is used to copy the file content (not metadata). On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports copies between 2 regular file descriptors :func:`os.sendfile` is used. On Windows `CopyFileEx`_ is used by all copy functions except :func:`copyfile`. @@ -704,8 +704,8 @@ Querying the size of the output terminal .. _`CopyFileEx`: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363852(v=vs.85).aspx -.. _`fcopyfile`: - http://www.manpagez.com/man/3/fcopyfile/ +.. _`copyfile`: + http://www.manpagez.com/man/3/copyfile/ .. _`Other Environment Variables`: http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html#tag_002_003 diff --git a/Lib/shutil.py b/Lib/shutil.py index 98e74b49c2309a..842c1fa22d3a82 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -50,7 +50,7 @@ import _winapi _HAS_SENDFILE = posix and hasattr(os, "sendfile") -_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") +_HAS_COPYFILE = posix and hasattr(posix, "_copyfile") # OSX __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", "copytree", "move", "rmtree", "Error", "SpecialFileError", @@ -95,18 +95,12 @@ def copyfileobj(fsrc, fdst, length=16*1024): break fdst.write(buf) -def _fastcopy_osx(fsrc, fdst): - """Copy 2 regular mmap-like files by using high-performance - fcopyfile() syscall (OSX only). +def _fastcopy_osx(src, dst, flags): + """Copy 2 regular mmap-like files content or metadata by using + high-performance copyfile(3) syscall (OSX only). """ try: - infd = fsrc.fileno() - outfd = fdst.fileno() - except Exception as err: - raise _GiveupOnFastCopy(err) # not a regular file - - try: - posix._fcopyfile(infd, outfd) + posix._copyfile(src, dst, flags) except OSError as err: if err.errno in {errno.EINVAL, errno.ENOTSUP}: raise _GiveupOnFastCopy(err) @@ -114,12 +108,14 @@ def _fastcopy_osx(fsrc, fdst): raise err from None def _fastcopy_win(fsrc, fdst): - """Copy 2 files by using high-performance CopyFileW (Windows only).""" + """Copy 2 files by using high-performance CopyFileExW (Windows only). + Note: this will also copy file metadata. + """ _winapi.CopyFileExW(fsrc, fdst, 0) def _fastcopy_sendfile(fsrc, fdst): """Copy data from one regular mmap-like fd to another by using - high-performance sendfile() method. + high-performance sendfile(2) syscall. This should work on Linux >= 2.6.33 and Solaris only. """ global _HAS_SENDFILE @@ -165,10 +161,9 @@ def _fastcopy_sendfile(fsrc, fdst): offset += sent def _fastcopy_fileobj(fsrc, fdst): - """Copy 2 regular mmap-like fds by using zero-copy sendfile(2) - (Linux) and fcopyfile(2) (OSX) syscalls. - In case of error fallback on using plain read()/write() if no - data was copied. + """Copy 2 regular mmap-like files by using zero-copy sendfile(2) + syscall first. In case of error and no data was written in fdst + fallback on using plain read()/write() method. """ # Note: copyfileobj() is left alone in order to not introduce any # unexpected breakage. Possible risks by using zero-copy calls @@ -178,19 +173,12 @@ def _fastcopy_fileobj(fsrc, fdst): # - fsrc may be a BufferedReader (which hides unread data in a buffer), # GzipFile (which decompresses data), HTTPResponse (which decodes # chunks). - # - possibly others + # - possibly others (e.g. encrypted fs/partition?) if _HAS_SENDFILE: try: return _fastcopy_sendfile(fsrc, fdst) except _GiveupOnFastCopy: pass - - if _HAS_FCOPYFILE: - try: - return _fastcopy_osx(fsrc, fdst) - except _GiveupOnFastCopy: - pass - return copyfileobj(fsrc, fdst) def _samefile(src, dst): @@ -229,6 +217,13 @@ def copyfile(src, dst, *, follow_symlinks=True): if not follow_symlinks and os.path.islink(src): os.symlink(os.readlink(src), dst) else: + if _HAS_COPYFILE: + try: + _fastcopy_osx(src, dst, posix._COPYFILE_DATA) + return dst + except _GiveupOnFastCopy: + pass + with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: _fastcopy_fileobj(fsrc, fdst) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 3ec0615beba6fc..8bd4e800c61bdb 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -33,7 +33,7 @@ from test.support import TESTFN, FakePath TESTFN2 = TESTFN + "2" -HAS_OSX_ZEROCOPY = hasattr(posix, "_fcopyfile") +HAS_OSX_ZEROCOPY = hasattr(posix, "_copyfile") try: import grp @@ -1807,7 +1807,7 @@ def _open(filename, mode='r'): self.assertRaises(OSError, shutil.copyfile, 'srcfile', 'destfile') - @unittest.skipIf(os.name == 'nt', "not POSIX") + @unittest.skipIf(HAS_OSX_ZEROCOPY, "skipped on OSX") def test_w_dest_open_fails(self): srcfile = self.Faux() @@ -1827,7 +1827,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot open "destfile"',)) - @unittest.skipIf(os.name == 'nt', "not POSIX") + @unittest.skipIf(HAS_OSX_ZEROCOPY, "skipped on OSX") def test_w_dest_close_fails(self): srcfile = self.Faux() @@ -1850,7 +1850,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot close',)) - @unittest.skipIf(os.name == 'nt', "not POSIX") + @unittest.skipIf(HAS_OSX_ZEROCOPY, "skipped on OSX") def test_w_source_close_fails(self): srcfile = self.Faux(True) @@ -1933,26 +1933,6 @@ def test_regular_copy(self): fun(TESTFN, TESTFN2) assert not m.called - @unittest.skipIf(os.name == 'nt', 'POSIX only') - def test_non_regular_file_src(self): - with io.BytesIO(self.FILEDATA) as src: - with open(TESTFN2, "wb") as dst: - with self.assertRaises(_GiveupOnFastCopy): - self.zerocopy_fun(src, dst) - shutil.copyfileobj(src, dst) - - self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) - - @unittest.skipIf(os.name == 'nt', 'POSIX only') - def test_non_regular_file_dst(self): - with open(TESTFN, "rb") as src: - with io.BytesIO() as dst: - with self.assertRaises(_GiveupOnFastCopy): - self.zerocopy_fun(src, dst) - shutil.copyfileobj(src, dst) - dst.seek(0) - self.assertEqual(dst.read(), self.FILEDATA) - def test_non_existent_src(self): name = tempfile.mktemp() with self.assertRaises(FileNotFoundError) as cm: @@ -2006,6 +1986,26 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): def zerocopy_fun(self, *args, **kwargs): return shutil._fastcopy_sendfile(*args, **kwargs) + @unittest.skipIf(os.name == 'nt', 'POSIX only') + def test_non_regular_file_src(self): + with io.BytesIO(self.FILEDATA) as src: + with open(TESTFN2, "wb") as dst: + with self.assertRaises(_GiveupOnFastCopy): + self.zerocopy_fun(src, dst) + shutil.copyfileobj(src, dst) + + self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) + + @unittest.skipIf(os.name == 'nt', 'POSIX only') + def test_non_regular_file_dst(self): + with open(TESTFN, "rb") as src: + with io.BytesIO() as dst: + with self.assertRaises(_GiveupOnFastCopy): + self.zerocopy_fun(src, dst) + shutil.copyfileobj(src, dst) + dst.seek(0) + self.assertEqual(dst.read(), self.FILEDATA) + def test_exception_on_second_call(self): def sendfile(*args, **kwargs): if not flag: @@ -2102,10 +2102,10 @@ def test_file2file_not_supported(self): @unittest.skipIf(not HAS_OSX_ZEROCOPY, 'OSX only') class TestZeroCopyOSX(_ZeroCopyFileTest, unittest.TestCase): - PATCHPOINT = "posix._fcopyfile" + PATCHPOINT = "posix._copyfile" - def zerocopy_fun(self, *args, **kwargs): - return shutil._fastcopy_osx(*args, **kwargs) + def zerocopy_fun(self, src, dst): + return shutil._fastcopy_osx(src.name, dst.name, posix._COPYFILE_DATA) @unittest.skipIf(not os.name == 'nt', 'Windows only') diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 1c98a1b2a6c49e..b740d016fab2bc 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3855,30 +3855,32 @@ os_write(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #if defined(__APPLE__) -PyDoc_STRVAR(os__fcopyfile__doc__, -"_fcopyfile($module, infd, outfd, /)\n" +PyDoc_STRVAR(os__copyfile__doc__, +"_copyfile($module, src, dst, flags, /)\n" "--\n" "\n" "Efficiently copy the content of 2 file descriptors (OSX only)."); -#define OS__FCOPYFILE_METHODDEF \ - {"_fcopyfile", (PyCFunction)os__fcopyfile, METH_FASTCALL, os__fcopyfile__doc__}, +#define OS__COPYFILE_METHODDEF \ + {"_copyfile", (PyCFunction)os__copyfile, METH_FASTCALL, os__copyfile__doc__}, static PyObject * -os__fcopyfile_impl(PyObject *module, int infd, int outfd); +os__copyfile_impl(PyObject *module, const char *src, const char *dst, + int flags); static PyObject * -os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +os__copyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - int infd; - int outfd; + const char *src; + const char *dst; + int flags; - if (!_PyArg_ParseStack(args, nargs, "ii:_fcopyfile", - &infd, &outfd)) { + if (!_PyArg_ParseStack(args, nargs, "ssi:_copyfile", + &src, &dst, &flags)) { goto exit; } - return_value = os__fcopyfile_impl(module, infd, outfd); + return_value = os__copyfile_impl(module, src, dst, flags); exit: return return_value; @@ -6447,9 +6449,9 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #define OS_PREADV_METHODDEF #endif /* !defined(OS_PREADV_METHODDEF) */ -#ifndef OS__FCOPYFILE_METHODDEF - #define OS__FCOPYFILE_METHODDEF -#endif /* !defined(OS__FCOPYFILE_METHODDEF) */ +#ifndef OS__COPYFILE_METHODDEF + #define OS__COPYFILE_METHODDEF +#endif /* !defined(OS__COPYFILE_METHODDEF) */ #ifndef OS_PIPE_METHODDEF #define OS_PIPE_METHODDEF @@ -6626,4 +6628,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=9071e1a8268b2f33 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7be32965983d4fd3 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index ef3455ebd50bda..428a5cc1f1888c 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8751,25 +8751,25 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) #if defined(__APPLE__) /*[clinic input] -os._fcopyfile +os._copyfile - infd: int - outfd: int + src: str + dst: str + flags: int / Efficiently copy the content of 2 file descriptors (OSX only). [clinic start generated code]*/ static PyObject * -os__fcopyfile_impl(PyObject *module, int infd, int outfd) -/*[clinic end generated code: output=3e629d5c50b33d04 input=ef4f7667f63d3e42]*/ +os__copyfile_impl(PyObject *module, const char *src, const char *dst, + int flags) +/*[clinic end generated code: output=c046031d7856e1e3 input=ef9a191e17624373]*/ { - // copyfile() source code: - // https://opensource.apple.com/source/copyfile/copyfile-42/copyfile.c int ret; Py_BEGIN_ALLOW_THREADS - ret = fcopyfile(infd, outfd, NULL, COPYFILE_DATA); + ret = copyfile(src, dst, NULL, flags); Py_END_ALLOW_THREADS if (ret < 0) return PyErr_SetFromErrno(PyExc_OSError); @@ -12954,7 +12954,7 @@ static PyMethodDef posix_methods[] = { OS_UTIME_METHODDEF OS_TIMES_METHODDEF OS__EXIT_METHODDEF - OS__FCOPYFILE_METHODDEF + OS__COPYFILE_METHODDEF OS_EXECV_METHODDEF OS_EXECVE_METHODDEF OS_SPAWNV_METHODDEF @@ -13574,6 +13574,10 @@ all_ins(PyObject *m) if (PyModule_AddIntMacro(m, GRND_NONBLOCK)) return -1; #endif +#if defined(__APPLE__) + if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1; +#endif + return 0; } From 941f7404902d361c39d93a678a04092cc972a011 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 18:29:48 +0200 Subject: [PATCH 073/111] osx / copyfile: use path_t instead of char --- Lib/test/test_shutil.py | 13 ++++++++----- Modules/clinic/posixmodule.c.h | 20 ++++++++++++-------- Modules/posixmodule.c | 13 ++++++------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 8bd4e800c61bdb..7ebcaf3511b081 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -33,7 +33,8 @@ from test.support import TESTFN, FakePath TESTFN2 = TESTFN + "2" -HAS_OSX_ZEROCOPY = hasattr(posix, "_copyfile") +WINDOWS = os.name == "nt" +OSX = sys.platform.startswith("darwin") try: import grp @@ -1807,7 +1808,7 @@ def _open(filename, mode='r'): self.assertRaises(OSError, shutil.copyfile, 'srcfile', 'destfile') - @unittest.skipIf(HAS_OSX_ZEROCOPY, "skipped on OSX") + @unittest.skipIf(OSX, "skipped on OSX") def test_w_dest_open_fails(self): srcfile = self.Faux() @@ -1827,7 +1828,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot open "destfile"',)) - @unittest.skipIf(HAS_OSX_ZEROCOPY, "skipped on OSX") + @unittest.skipIf(OSX, "skipped on OSX") def test_w_dest_close_fails(self): srcfile = self.Faux() @@ -1850,7 +1851,7 @@ def _open(filename, mode='r'): self.assertEqual(srcfile._exited_with[1].args, ('Cannot close',)) - @unittest.skipIf(HAS_OSX_ZEROCOPY, "skipped on OSX") + @unittest.skipIf(OSX, "skipped on OSX") def test_w_source_close_fails(self): srcfile = self.Faux(True) @@ -1938,6 +1939,8 @@ def test_non_existent_src(self): with self.assertRaises(FileNotFoundError) as cm: shutil.copyfile(name, "new") self.assertEqual(cm.exception.filename, name) + if OSX or WINDOWS: + self.assertEqual(cm.exception.filename2, "new") def test_empty_file(self): srcname = TESTFN + 'src' @@ -2100,7 +2103,7 @@ def test_file2file_not_supported(self): shutil._HAS_SENDFILE = True -@unittest.skipIf(not HAS_OSX_ZEROCOPY, 'OSX only') +@unittest.skipIf(not OSX, 'OSX only') class TestZeroCopyOSX(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "posix._copyfile" diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index b740d016fab2bc..56f0129b04ad72 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3865,24 +3865,28 @@ PyDoc_STRVAR(os__copyfile__doc__, {"_copyfile", (PyCFunction)os__copyfile, METH_FASTCALL, os__copyfile__doc__}, static PyObject * -os__copyfile_impl(PyObject *module, const char *src, const char *dst, - int flags); +os__copyfile_impl(PyObject *module, path_t *src, path_t *dst, int flags); static PyObject * os__copyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - const char *src; - const char *dst; + path_t src = PATH_T_INITIALIZE("_copyfile", "src", 0, 0); + path_t dst = PATH_T_INITIALIZE("_copyfile", "dst", 0, 0); int flags; - if (!_PyArg_ParseStack(args, nargs, "ssi:_copyfile", - &src, &dst, &flags)) { + if (!_PyArg_ParseStack(args, nargs, "O&O&i:_copyfile", + path_converter, &src, path_converter, &dst, &flags)) { goto exit; } - return_value = os__copyfile_impl(module, src, dst, flags); + return_value = os__copyfile_impl(module, &src, &dst, flags); exit: + /* Cleanup for src */ + path_cleanup(&src); + /* Cleanup for dst */ + path_cleanup(&dst); + return return_value; } @@ -6628,4 +6632,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=7be32965983d4fd3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=d245a974d050df80 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 428a5cc1f1888c..437bfe31dcf4bf 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8753,8 +8753,8 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) /*[clinic input] os._copyfile - src: str - dst: str + src: path_t + dst: path_t flags: int / @@ -8762,17 +8762,16 @@ Efficiently copy the content of 2 file descriptors (OSX only). [clinic start generated code]*/ static PyObject * -os__copyfile_impl(PyObject *module, const char *src, const char *dst, - int flags) -/*[clinic end generated code: output=c046031d7856e1e3 input=ef9a191e17624373]*/ +os__copyfile_impl(PyObject *module, path_t *src, path_t *dst, int flags) +/*[clinic end generated code: output=d9f64f7425e4174b input=7e0bf8e81908f871]*/ { int ret; Py_BEGIN_ALLOW_THREADS - ret = copyfile(src, dst, NULL, flags); + ret = copyfile(src->narrow, dst->narrow, NULL, flags); Py_END_ALLOW_THREADS if (ret < 0) - return PyErr_SetFromErrno(PyExc_OSError); + return path_error2(src, dst); Py_RETURN_NONE; } #endif From 4d28c12cae038b0041722303443f92c37ea9f0a0 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 18:46:39 +0200 Subject: [PATCH 074/111] do not set dst name in the OSError exception in order to remain consistent with platforms which cannot do that (e.g. linux) --- Lib/test/test_shutil.py | 4 ---- Modules/posixmodule.c | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7ebcaf3511b081..3fbef06f3ad6e9 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -33,9 +33,7 @@ from test.support import TESTFN, FakePath TESTFN2 = TESTFN + "2" -WINDOWS = os.name == "nt" OSX = sys.platform.startswith("darwin") - try: import grp import pwd @@ -1939,8 +1937,6 @@ def test_non_existent_src(self): with self.assertRaises(FileNotFoundError) as cm: shutil.copyfile(name, "new") self.assertEqual(cm.exception.filename, name) - if OSX or WINDOWS: - self.assertEqual(cm.exception.filename2, "new") def test_empty_file(self): srcname = TESTFN + 'src' diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 437bfe31dcf4bf..28db200a3418bd 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8771,7 +8771,7 @@ os__copyfile_impl(PyObject *module, path_t *src, path_t *dst, int flags) ret = copyfile(src->narrow, dst->narrow, NULL, flags); Py_END_ALLOW_THREADS if (ret < 0) - return path_error2(src, dst); + return path_error(src); Py_RETURN_NONE; } #endif From 2149b8b3e1caa76e492a5835530ffff5ebc8bee6 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 19:48:27 +0200 Subject: [PATCH 075/111] add same file test --- Lib/test/test_shutil.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 3fbef06f3ad6e9..3906e506980b42 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1919,6 +1919,12 @@ def get_files(self): def zerocopy_fun(self, *args, **kwargs): raise NotImplementedError("must be implemented in subclass") + def reset(self): + self.tearDown() + self.tearDownClass() + self.setUpClass() + self.setUp() + # --- def test_regular_copy(self): @@ -1932,6 +1938,14 @@ def test_regular_copy(self): fun(TESTFN, TESTFN2) assert not m.called + def test_same_file(self): + self.addCleanup(self.reset) + with self.get_files() as (src, dst): + with self.assertRaises(_GiveupOnFastCopy): + self.zerocopy_fun(src, src) + with self.assertRaises(shutil.SameFileError): + shutil.copyfile(TESTFN, TESTFN) + def test_non_existent_src(self): name = tempfile.mktemp() with self.assertRaises(FileNotFoundError) as cm: From 6a02a2a53e2942a524851dc15d4373604ca22de4 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 20:10:31 +0200 Subject: [PATCH 076/111] add test for same file --- Lib/test/test_shutil.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 3906e506980b42..8f1003abad46e2 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1458,6 +1458,8 @@ def test_copyfile_same_file(self): self.assertRaises(SameFileError, shutil.copyfile, src_file, src_file) # But Error should work too, to stay backward compatible. self.assertRaises(Error, shutil.copyfile, src_file, src_file) + # Make sure file is not corrupted. + self.assertEqual(read_file(src_file), 'foo') def test_copytree_return_value(self): # copytree returns its destination path. @@ -1941,10 +1943,10 @@ def test_regular_copy(self): def test_same_file(self): self.addCleanup(self.reset) with self.get_files() as (src, dst): - with self.assertRaises(_GiveupOnFastCopy): + with self.assertRaises(Exception): self.zerocopy_fun(src, src) - with self.assertRaises(shutil.SameFileError): - shutil.copyfile(TESTFN, TESTFN) + # Make sure src file is not corrupted. + self.assertEqual(read_file(TESTFN, binary=True), self.FILEDATA) def test_non_existent_src(self): name = tempfile.mktemp() From 22875087b59e359ebb8cfbe9cadaba9d88d7becf Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 20:15:14 +0200 Subject: [PATCH 077/111] have osx copyfile() pre-emptively check if src and dst are the same, otherwise it will return immedialtey and src file content gets deleted --- Lib/shutil.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/shutil.py b/Lib/shutil.py index 842c1fa22d3a82..e1221089e87de0 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -99,6 +99,10 @@ def _fastcopy_osx(src, dst, flags): """Copy 2 regular mmap-like files content or metadata by using high-performance copyfile(3) syscall (OSX only). """ + if _samefile(src, dst): + # ...or else copyfile() returns immediately and deletes src + # file content. + raise SameFileError("{!r} and {!r} are the same file".format(src, dst)) try: posix._copyfile(src, dst, flags) except OSError as err: From b9da5d59c42884438a89805ab740a480009580d9 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 11:34:35 -0700 Subject: [PATCH 078/111] turn PermissionError into appropriate SameFileError --- Lib/shutil.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index e1221089e87de0..811f50fd803f2a 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -111,11 +111,17 @@ def _fastcopy_osx(src, dst, flags): else: raise err from None -def _fastcopy_win(fsrc, fdst): +def _fastcopy_win(src, dst): """Copy 2 files by using high-performance CopyFileExW (Windows only). Note: this will also copy file metadata. """ - _winapi.CopyFileExW(fsrc, fdst, 0) + try: + _winapi.CopyFileExW(src, dst, 0) + except PermissionError as err: + if _samefile(src, dst): + raise SameFileError( + "{!r} and {!r} are the same file".format(src, dst)) + raise err from None def _fastcopy_sendfile(fsrc, fdst): """Copy data from one regular mmap-like fd to another by using From c921f46130738d89d62d1b0cb13053bc002ae973 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 12:07:55 -0700 Subject: [PATCH 079/111] expose ERROR_SHARING_VIOLATION in order to raise more appropriate SameFileError --- Lib/shutil.py | 12 +++++++----- Modules/_winapi.c | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 811f50fd803f2a..023705da898d5d 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -96,12 +96,13 @@ def copyfileobj(fsrc, fdst, length=16*1024): fdst.write(buf) def _fastcopy_osx(src, dst, flags): - """Copy 2 regular mmap-like files content or metadata by using - high-performance copyfile(3) syscall (OSX only). + """Copy a regular file content or metadata by using high-performance + copyfile(3) syscall (OSX only). """ if _samefile(src, dst): - # ...or else copyfile() returns immediately and deletes src - # file content. + # ...or else copyfile() would return success and delete src + # file content. This is stupid as it forces us to do 2 extra + # stat() calls. raise SameFileError("{!r} and {!r} are the same file".format(src, dst)) try: posix._copyfile(src, dst, flags) @@ -118,7 +119,8 @@ def _fastcopy_win(src, dst): try: _winapi.CopyFileExW(src, dst, 0) except PermissionError as err: - if _samefile(src, dst): + if err.winerror == _winapi.ERROR_SHARING_VIOLATION and \ + _samefile(src, dst): raise SameFileError( "{!r} and {!r} are the same file".format(src, dst)) raise err from None diff --git a/Modules/_winapi.c b/Modules/_winapi.c index c11c98ba896570..44c8d6ea681299 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1825,6 +1825,7 @@ PyInit__winapi(void) WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_BUSY); WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_CONNECTED); WINAPI_CONSTANT(F_DWORD, ERROR_SEM_TIMEOUT); + WINAPI_CONSTANT(F_DWORD, ERROR_SHARING_VIOLATION); WINAPI_CONSTANT(F_DWORD, FILE_FLAG_FIRST_PIPE_INSTANCE); WINAPI_CONSTANT(F_DWORD, FILE_FLAG_OVERLAPPED); WINAPI_CONSTANT(F_DWORD, FILE_GENERIC_READ); From bb24490bca6b3a9481372bdad4031d7a65bf8cc2 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 12:36:58 -0700 Subject: [PATCH 080/111] honour follow_symlinks arg when using CopyFileEx --- Lib/shutil.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 023705da898d5d..48f7428868e2a8 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -363,19 +363,27 @@ def copy(src, dst, *, follow_symlinks=True): def copy2(src, dst, *, follow_symlinks=True): """Copy data and all stat info ("cp -p src dst"). Return the file's - destination." + destination. The destination may be a directory. If follow_symlinks is false, symlinks won't be followed. This resembles GNU's "cp -P src dst". + On Windows this function uses CopyFileEx which preserves extended + attributes, OLE structured storage, NTFS file system alternate + data streams, security resource attributes and file attributes. """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) + if os.name == 'nt': - _fastcopy_win(src, dst) + if not follow_symlinks and os.path.islink(src): + os.symlink(os.readlink(src), dst) + else: + _fastcopy_win(src, dst) return dst + copyfile(src, dst, follow_symlinks=follow_symlinks) copystat(src, dst, follow_symlinks=follow_symlinks) return dst From fef8b32fd1148d9b91a086b7643ca70a0e690b24 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 31 May 2018 21:49:47 +0200 Subject: [PATCH 081/111] update Misc/NEWS --- Doc/library/shutil.rst | 2 +- Lib/test/test_shutil.py | 4 ++-- .../next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 1744855e959bb8..0c8ef8de9c3276 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -408,7 +408,7 @@ efficiently (see :issue:`33671`). the use of userspace buffers in Python as in "``outfd.write(infd.read())``". On OSX `copyfile`_ is used to copy the file content (not metadata). -On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports +On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports copies between 2 regular file descriptors :func:`os.sendfile` is used. On Windows `CopyFileEx`_ is used by all copy functions except :func:`copyfile`. diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 8f1003abad46e2..f169c7ddc35303 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1998,8 +1998,8 @@ def test_filesystem_full(self): class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "os.sendfile" - def zerocopy_fun(self, *args, **kwargs): - return shutil._fastcopy_sendfile(*args, **kwargs) + def zerocopy_fun(self, fsrc, fdst): + return shutil._fastcopy_sendfile(fsrc, fdst) @unittest.skipIf(os.name == 'nt', 'POSIX only') def test_non_regular_file_src(self): diff --git a/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst index ffaa38ba83312a..05eea7951889bd 100644 --- a/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst +++ b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst @@ -1,6 +1,6 @@ :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, -:func:`shutil.copytree` and :func:`shutil.move` use platform specific zero- -copy syscalls on Linux, OSX and Windows in order to copy the file more -efficiently. The speedup for copying a 512MB file is about +26% on Linux, +:func:`shutil.copytree` and :func:`shutil.move` use platform specific +fast-copy syscalls on Linux, Solaris, OSX and Windows in order to copy the file +more efficiently. The speedup for copying a 512MB file is about +26% on Linux, +50% on OSX and +48% on Windows. Also, much less CPU cycles are consumed (Contributed by Giampaolo Rodola' in :issue:`25427`.) From 71be4535c46ae520248078dea48e827f4e3904c4 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 5 Jun 2018 21:21:10 +0200 Subject: [PATCH 082/111] expose CreateDirectoryEx mock --- Modules/_winapi.c | 20 ++++++++++++++++++++ Modules/clinic/_winapi.c.h | 31 ++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 44c8d6ea681299..e91a8f2a232b2a 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1744,6 +1744,25 @@ _winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, Py_RETURN_NONE; } + +/*[clinic input] +_winapi.CreateDirectoryEx + + src: LPCTSTR + dst: LPCTSTR + / + +Creates a new directory with the attributes of a specified template directory. +[clinic start generated code]*/ + +static PyObject * +_winapi_CreateDirectoryEx_impl(PyObject *module, LPCTSTR src, LPCTSTR dst) +/*[clinic end generated code: output=b16d6292dc1e34bc input=cc240e28574b8da7]*/ +{ + return Py_BuildValue("i", 99); +} + + static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF @@ -1772,6 +1791,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI_GETACP_METHODDEF _WINAPI_GETFILETYPE_METHODDEF _WINAPI_COPYFILEEXW_METHODDEF + _WINAPI_CREATEDIRECTORYEX_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index 88b147564b9e50..25d2afeb3ad753 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -972,4 +972,33 @@ _winapi_CopyFileExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=1f3c6672a1f704d3 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_winapi_CreateDirectoryEx__doc__, +"CreateDirectoryEx($module, src, dst, /)\n" +"--\n" +"\n" +"Creates a new directory with the attributes of a specified template directory."); + +#define _WINAPI_CREATEDIRECTORYEX_METHODDEF \ + {"CreateDirectoryEx", (PyCFunction)_winapi_CreateDirectoryEx, METH_FASTCALL, _winapi_CreateDirectoryEx__doc__}, + +static PyObject * +_winapi_CreateDirectoryEx_impl(PyObject *module, LPCTSTR src, LPCTSTR dst); + +static PyObject * +_winapi_CreateDirectoryEx(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPCTSTR src; + LPCTSTR dst; + + if (!_PyArg_ParseStack(args, nargs, "ss:CreateDirectoryEx", + &src, &dst)) { + goto exit; + } + return_value = _winapi_CreateDirectoryEx_impl(module, src, dst); + +exit: + return return_value; +} +/*[clinic end generated code: output=553d7aa4aa582c05 input=a9049054013a1b77]*/ From 6035fe2d530a6d84cc55a5d9cedf2595fed3e14a Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 21:51:06 +0200 Subject: [PATCH 083/111] change C type --- Modules/_winapi.c | 10 +++++----- Modules/clinic/_winapi.c.h | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index e91a8f2a232b2a..9cea58995eb52f 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1746,18 +1746,18 @@ _winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, /*[clinic input] -_winapi.CreateDirectoryEx +_winapi.CreateDirectoryExW - src: LPCTSTR - dst: LPCTSTR + src: LPWSTR + dst: LPWSTR / Creates a new directory with the attributes of a specified template directory. [clinic start generated code]*/ static PyObject * -_winapi_CreateDirectoryEx_impl(PyObject *module, LPCTSTR src, LPCTSTR dst) -/*[clinic end generated code: output=b16d6292dc1e34bc input=cc240e28574b8da7]*/ +_winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) +/*[clinic end generated code: output=f41d941d73b1dac8 input=716a8ef620692466]*/ { return Py_BuildValue("i", 99); } diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index 25d2afeb3ad753..b240b66bc1eb9c 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -973,32 +973,32 @@ _winapi_CopyFileExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } -PyDoc_STRVAR(_winapi_CreateDirectoryEx__doc__, -"CreateDirectoryEx($module, src, dst, /)\n" +PyDoc_STRVAR(_winapi_CreateDirectoryExW__doc__, +"CreateDirectoryExW($module, src, dst, /)\n" "--\n" "\n" "Creates a new directory with the attributes of a specified template directory."); -#define _WINAPI_CREATEDIRECTORYEX_METHODDEF \ - {"CreateDirectoryEx", (PyCFunction)_winapi_CreateDirectoryEx, METH_FASTCALL, _winapi_CreateDirectoryEx__doc__}, +#define _WINAPI_CREATEDIRECTORYEXW_METHODDEF \ + {"CreateDirectoryExW", (PyCFunction)_winapi_CreateDirectoryExW, METH_FASTCALL, _winapi_CreateDirectoryExW__doc__}, static PyObject * -_winapi_CreateDirectoryEx_impl(PyObject *module, LPCTSTR src, LPCTSTR dst); +_winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst); static PyObject * -_winapi_CreateDirectoryEx(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +_winapi_CreateDirectoryExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - LPCTSTR src; - LPCTSTR dst; + LPWSTR src; + LPWSTR dst; - if (!_PyArg_ParseStack(args, nargs, "ss:CreateDirectoryEx", + if (!_PyArg_ParseStack(args, nargs, "uu:CreateDirectoryExW", &src, &dst)) { goto exit; } - return_value = _winapi_CreateDirectoryEx_impl(module, src, dst); + return_value = _winapi_CreateDirectoryExW_impl(module, src, dst); exit: return return_value; } -/*[clinic end generated code: output=553d7aa4aa582c05 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=03a7c7776f3a8587 input=a9049054013a1b77]*/ From 8dc651eb32dff4458d87479875536f2542b27fc9 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 22:18:15 +0200 Subject: [PATCH 084/111] CreateDirectoryExW actual implementation --- Modules/_winapi.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 9cea58995eb52f..f45d944f2c5509 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1759,7 +1759,19 @@ static PyObject * _winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) /*[clinic end generated code: output=f41d941d73b1dac8 input=716a8ef620692466]*/ { - return Py_BuildValue("i", 99); + int ret; + PyObject *py_srcname; + + Py_BEGIN_ALLOW_THREADS + ret = CreateDirectoryExW(src, dst, NULL); + Py_END_ALLOW_THREADS + if (ret == 0) { + py_srcname = Py_BuildValue("u", src); + win32_error_object("CreateDirectoryExW", py_srcname); + Py_CLEAR(py_srcname); + return NULL; + } + Py_RETURN_NONE; } From 5d0eada173c7d4641cadb5543504b8eab668a34c Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 13:19:01 -0700 Subject: [PATCH 085/111] provide specific makedirs() implementation for win --- Lib/shutil.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 48f7428868e2a8..88380ec0c87454 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -400,6 +400,27 @@ def _ignore_patterns(path, names): return set(ignored_names) return _ignore_patterns +def _win_makedirs(src, dst): + """Similar to os.makedirs but uses CreateDirectoryExW instead of + CreateDirectoryW which creates a directory with the attributes of + a specified template directory. + """ + head, tail = os.path.split(dst) + if not tail: + head, tail = os.path.split(head) + if head and tail and not os.path.exists(head): + try: + _win_makedirs(src, dst) + except FileExistsError: + # Defeats race condition when another thread created the path + pass + cdir = os.curdir + if isinstance(tail, bytes): + cdir = bytes(os.curdir, 'ASCII') + if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists + return + _winapi.CreateDirectoryExW(src, dst) + def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False): """Recursively copy a directory tree. @@ -442,7 +463,11 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, else: ignored_names = set() - os.makedirs(dst) + if os.name == 'nt' and copy_function is copy2: + _win_makedirs(src, dst) + else: + os.makedirs(dst) + errors = [] for name in names: if name in ignored_names: From f65c8aeabdcbed94a2529668bac6f23db9683457 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 13:25:19 -0700 Subject: [PATCH 086/111] fix typo --- Modules/_winapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index f45d944f2c5509..713fb7d72b5820 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1803,7 +1803,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI_GETACP_METHODDEF _WINAPI_GETFILETYPE_METHODDEF _WINAPI_COPYFILEEXW_METHODDEF - _WINAPI_CREATEDIRECTORYEX_METHODDEF + _WINAPI_CREATEDIRECTORYEXW_METHODDEF {NULL, NULL} }; From 9c4508ef5f2088dca6ba493a79852c10b178f9e6 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 22:33:43 +0200 Subject: [PATCH 087/111] skeleton for SetNamedSecurityInfo --- Modules/_winapi.c | 19 +++++++++++++++++++ Modules/clinic/_winapi.c.h | 31 ++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 713fb7d72b5820..9015f4b398279d 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1775,6 +1775,24 @@ _winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) } +/*[clinic input] +_winapi.copypathsecurityinfo + + src: LPWSTR + dst: LPWSTR + / + +Copy the security information of one path to another. +[clinic start generated code]*/ + +static PyObject * +_winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst) +/*[clinic end generated code: output=09045a3ba0244ff5 input=02ebe3bee4d04f75]*/ +{ + Py_RETURN_NONE; +} + + static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF @@ -1804,6 +1822,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI_GETFILETYPE_METHODDEF _WINAPI_COPYFILEEXW_METHODDEF _WINAPI_CREATEDIRECTORYEXW_METHODDEF + _WINAPI_COPYDIRSECURITYINFO_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index b240b66bc1eb9c..879e543b9ea565 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -1001,4 +1001,33 @@ _winapi_CreateDirectoryExW(PyObject *module, PyObject *const *args, Py_ssize_t n exit: return return_value; } -/*[clinic end generated code: output=03a7c7776f3a8587 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_winapi_copypathsecurityinfo__doc__, +"copypathsecurityinfo($module, src, dst, /)\n" +"--\n" +"\n" +"Copy the security information of one path to another."); + +#define _WINAPI_COPYPATHSECURITYINFO_METHODDEF \ + {"copypathsecurityinfo", (PyCFunction)_winapi_copypathsecurityinfo, METH_FASTCALL, _winapi_copypathsecurityinfo__doc__}, + +static PyObject * +_winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst); + +static PyObject * +_winapi_copypathsecurityinfo(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + LPWSTR src; + LPWSTR dst; + + if (!_PyArg_ParseStack(args, nargs, "uu:copypathsecurityinfo", + &src, &dst)) { + goto exit; + } + return_value = _winapi_copypathsecurityinfo_impl(module, src, dst); + +exit: + return return_value; +} +/*[clinic end generated code: output=c1713b770ffdfda1 input=a9049054013a1b77]*/ From bb1fee6a6704b86525dfb9c02abd651de277c50a Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 13:57:52 -0700 Subject: [PATCH 088/111] get security info for src path --- Lib/shutil.py | 1 + Modules/_winapi.c | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 88380ec0c87454..2efc052e2b37f7 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -420,6 +420,7 @@ def _win_makedirs(src, dst): if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists return _winapi.CreateDirectoryExW(src, dst) + _winapi.copypathsecurityinfo(src, dst) def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False): diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 9015f4b398279d..71c199a3b35c05 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -41,6 +41,7 @@ #include "windows.h" #include #include "winreparse.h" +#include #if defined(MS_WIN32) && !defined(MS_WIN64) #define HANDLE_TO_PYNUM(handle) \ @@ -1760,15 +1761,15 @@ _winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) /*[clinic end generated code: output=f41d941d73b1dac8 input=716a8ef620692466]*/ { int ret; - PyObject *py_srcname; + PyObject *pypath; Py_BEGIN_ALLOW_THREADS ret = CreateDirectoryExW(src, dst, NULL); Py_END_ALLOW_THREADS if (ret == 0) { - py_srcname = Py_BuildValue("u", src); - win32_error_object("CreateDirectoryExW", py_srcname); - Py_CLEAR(py_srcname); + pypath = Py_BuildValue("u", dst); + win32_error_object("CreateDirectoryExW", pypath); + Py_CLEAR(pypath); return NULL; } Py_RETURN_NONE; @@ -1789,6 +1790,25 @@ static PyObject * _winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst) /*[clinic end generated code: output=09045a3ba0244ff5 input=02ebe3bee4d04f75]*/ { + int ret; + PSECURITY_DESCRIPTOR pSd = NULL; + PyObject *pypath; + + /* Source path */ + Py_BEGIN_ALLOW_THREADS + ret = GetNamedSecurityInfoW( + src, SE_FILE_OBJECT, ATTRIBUTE_SECURITY_INFORMATION, + NULL, NULL, NULL, NULL, &pSd); + Py_END_ALLOW_THREADS + + if (ret != ERROR_SUCCESS) { + pypath = Py_BuildValue("u", src); + PyErr_SetExcFromWindowsErrWithFilenameObject( + PyExc_OSError, ret, pypath); + Py_CLEAR(pypath); + return NULL; + } + Py_RETURN_NONE; } @@ -1822,7 +1842,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI_GETFILETYPE_METHODDEF _WINAPI_COPYFILEEXW_METHODDEF _WINAPI_CREATEDIRECTORYEXW_METHODDEF - _WINAPI_COPYDIRSECURITYINFO_METHODDEF + _WINAPI_COPYPATHSECURITYINFO_METHODDEF {NULL, NULL} }; From 566898a4438cd4df3fe67fc8a7ad37f99e6d8f76 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 14:06:41 -0700 Subject: [PATCH 089/111] finally set security attrs --- Modules/_winapi.c | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 71c199a3b35c05..e265fdd752c86c 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -79,19 +79,6 @@ check_CancelIoEx() return has_CancelIoEx; } -static PyObject * -win32_error_object(const char* function, PyObject* filename) -{ - errno = GetLastError(); - if (filename) - return PyErr_SetExcFromWindowsErrWithFilenameObject( - PyExc_OSError, - errno, - filename); - else - return PyErr_SetFromWindowsErr(errno); -} - /* * A Python object wrapping an OVERLAPPED structure and other useful data @@ -1738,7 +1725,10 @@ _winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, Py_END_ALLOW_THREADS if (ret == 0) { py_srcname = Py_BuildValue("u", src); - win32_error_object("CopyFileExW", py_srcname); + if (!py_srcname) + return NULL; + PyErr_SetExcFromWindowsErrWithFilenameObject( + PyExc_OSError, 0, py_srcname); Py_CLEAR(py_srcname); return NULL; } @@ -1768,7 +1758,10 @@ _winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) Py_END_ALLOW_THREADS if (ret == 0) { pypath = Py_BuildValue("u", dst); - win32_error_object("CreateDirectoryExW", pypath); + if (!pypath) + return NULL; + PyErr_SetExcFromWindowsErrWithFilenameObject( + PyExc_OSError, 0, pypath); Py_CLEAR(pypath); return NULL; } @@ -1791,18 +1784,37 @@ _winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst) /*[clinic end generated code: output=09045a3ba0244ff5 input=02ebe3bee4d04f75]*/ { int ret; - PSECURITY_DESCRIPTOR pSd = NULL; + PSECURITY_DESCRIPTOR sacl = NULL; PyObject *pypath; /* Source path */ Py_BEGIN_ALLOW_THREADS ret = GetNamedSecurityInfoW( src, SE_FILE_OBJECT, ATTRIBUTE_SECURITY_INFORMATION, - NULL, NULL, NULL, NULL, &pSd); + NULL, NULL, NULL, NULL, &sacl); Py_END_ALLOW_THREADS if (ret != ERROR_SUCCESS) { pypath = Py_BuildValue("u", src); + if (!pypath) + return NULL; + PyErr_SetExcFromWindowsErrWithFilenameObject( + PyExc_OSError, ret, pypath); + Py_CLEAR(pypath); + return NULL; + } + + /* Destination path */ + Py_BEGIN_ALLOW_THREADS + ret = SetNamedSecurityInfoW( + dst, SE_FILE_OBJECT, ATTRIBUTE_SECURITY_INFORMATION, + NULL, NULL, NULL, sacl); + Py_END_ALLOW_THREADS + + if (ret != ERROR_SUCCESS) { + pypath = Py_BuildValue("u", dst); + if (!pypath) + return NULL; PyErr_SetExcFromWindowsErrWithFilenameObject( PyExc_OSError, ret, pypath); Py_CLEAR(pypath); From f4350539d06da15d6ebae5c2ba850eb03af118c9 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 15:18:56 -0700 Subject: [PATCH 090/111] add unit tests --- Lib/shutil.py | 24 ++++++++++++++------ Lib/test/test_shutil.py | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 2efc052e2b37f7..4d76083781eabc 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -401,16 +401,18 @@ def _ignore_patterns(path, names): return _ignore_patterns def _win_makedirs(src, dst): - """Similar to os.makedirs but uses CreateDirectoryExW instead of - CreateDirectoryW which creates a directory with the attributes of - a specified template directory. + """Similar to os.makedirs() but creates a directory tree copying + attributes and security info from a template "src" directory. + As such "src" directory must exist else this will fail with + FileNotFoundError. """ + # --- head, tail = os.path.split(dst) if not tail: head, tail = os.path.split(head) if head and tail and not os.path.exists(head): try: - _win_makedirs(src, dst) + _win_makedirs(src, head) except FileExistsError: # Defeats race condition when another thread created the path pass @@ -419,8 +421,16 @@ def _win_makedirs(src, dst): cdir = bytes(os.curdir, 'ASCII') if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists return - _winapi.CreateDirectoryExW(src, dst) - _winapi.copypathsecurityinfo(src, dst) + # --- + try: + _winapi.CreateDirectoryExW(src, dst) + _winapi.copypathsecurityinfo(src, dst) + except OSError: + # Cannot rely on checking for EEXIST, since the operating system + # could give priority to other errors like EACCES or EROFS + if not os.path.isdir(dst): + raise + def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False): @@ -464,7 +474,7 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, else: ignored_names = set() - if os.name == 'nt' and copy_function is copy2: + if os.name == 'nt' and copy_function is copy2 and os.path.exists(src): _win_makedirs(src, dst) else: os.makedirs(dst) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index f169c7ddc35303..6813ab78232489 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2131,6 +2131,56 @@ def zerocopy_fun(self, src, dst): return shutil._fastcopy_win(src.name, dst.name) +@unittest.skipIf(not os.name == 'nt', 'Windows only') +class TestWindowsMakedirs(unittest.TestCase): + """copytree() on Windows uses a dedicated version of os.makedirs() + using specific Windows APIs in order to copy directory attributes. + """ + + def setUp(self): + self.tearDown() + self.src_path = os.path.join(TESTFN, 'a', 'b', 'c') + os.makedirs(self.src_path) + + def tearDown(self): + shutil.rmtree(TESTFN, ignore_errors=True) + shutil.rmtree(TESTFN2, ignore_errors=True) + + def test_makedirs(self): + dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') + shutil._win_makedirs(self.src_path, dst_path) + assert os.path.isdir(os.path.join(TESTFN2)) + assert os.path.isdir(os.path.join(TESTFN2, "a")) + assert os.path.isdir(os.path.join(TESTFN2, "a", "b")) + assert os.path.isdir(os.path.join(TESTFN2, "a", "b", "c")) + + def test_makedirs_src_not_found(self): + src_path = tempfile.mktemp() + dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') + with self.assertRaises(FileNotFoundError): + shutil._win_makedirs(src_path, dst_path) + + def test_makedirs_dst_exists(self): + src_path = tempfile.mktemp() + dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') + os.makedirs(dst_path) + shutil._win_makedirs(src_path, dst_path) + + def test_CreateDirectoryExW_src_not_found(self): + import _winapi + src_path = tempfile.mktemp() + dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') + with self.assertRaises(FileNotFoundError): + _winapi.CreateDirectoryExW(src_path, dst_path) + + def test_copypathsecurityinfo_src_not_found(self): + import _winapi + src_path = tempfile.mktemp() + dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') + with self.assertRaises(FileNotFoundError): + _winapi.copypathsecurityinfo(src_path, dst_path) + + class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): """Check if get_terminal_size() returns a meaningful value. From 30c9a576b1c0418151f198dbb1cd63102a698037 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 15:32:03 -0700 Subject: [PATCH 091/111] mimick os.makedirs() behavior and raise if dst dir exists --- Lib/shutil.py | 12 +++--------- Lib/test/test_shutil.py | 8 ++++++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 4d76083781eabc..c987bd2bc682ec 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -422,14 +422,8 @@ def _win_makedirs(src, dst): if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists return # --- - try: - _winapi.CreateDirectoryExW(src, dst) - _winapi.copypathsecurityinfo(src, dst) - except OSError: - # Cannot rely on checking for EEXIST, since the operating system - # could give priority to other errors like EACCES or EROFS - if not os.path.isdir(dst): - raise + _winapi.CreateDirectoryExW(src, dst) + _winapi.copypathsecurityinfo(src, dst) def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, @@ -474,7 +468,7 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, else: ignored_names = set() - if os.name == 'nt' and copy_function is copy2 and os.path.exists(src): + if os.name == 'nt' and copy_function is copy2: _win_makedirs(src, dst) else: os.makedirs(dst) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 6813ab78232489..fb447d211dbc8e 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2159,12 +2159,16 @@ def test_makedirs_src_not_found(self): dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') with self.assertRaises(FileNotFoundError): shutil._win_makedirs(src_path, dst_path) + with self.assertRaises(FileNotFoundError): + shutil.copytree(src_path, dst_path) def test_makedirs_dst_exists(self): - src_path = tempfile.mktemp() dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') os.makedirs(dst_path) - shutil._win_makedirs(src_path, dst_path) + with self.assertRaises(FileExistsError): + shutil._win_makedirs(self.src_path, dst_path) + with self.assertRaises(FileExistsError): + shutil.copytree(self.src_path, dst_path) def test_CreateDirectoryExW_src_not_found(self): import _winapi From 33f362f15b001990f94efa5cf6b84859d9002bf2 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 15:54:01 -0700 Subject: [PATCH 092/111] set 2 paths for OSError object --- Modules/_winapi.c | 51 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index e265fdd752c86c..ed07f2f116d161 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1718,21 +1718,33 @@ _winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, /*[clinic end generated code: output=715613c8834b35f5 input=bea3e5c545b755be]*/ { int ret; - PyObject *py_srcname; + PyObject *pysrc = NULL; + PyObject *pydst = NULL; Py_BEGIN_ALLOW_THREADS ret = CopyFileExW(src, dst, NULL, NULL, NULL, flags); Py_END_ALLOW_THREADS + if (ret == 0) { - py_srcname = Py_BuildValue("u", src); - if (!py_srcname) - return NULL; - PyErr_SetExcFromWindowsErrWithFilenameObject( - PyExc_OSError, 0, py_srcname); - Py_CLEAR(py_srcname); + if (!(pysrc = Py_BuildValue("u", src))) + goto error; + if (!(pydst = Py_BuildValue("u", dst))) + goto error; + PyErr_SetExcFromWindowsErrWithFilenameObjects( + PyExc_OSError, 0, pysrc, pydst); + Py_CLEAR(pysrc); + Py_CLEAR(pydst); return NULL; } + Py_RETURN_NONE; + +error: + if (pysrc != NULL) + Py_CLEAR(pysrc); + if (pydst != NULL) + Py_CLEAR(pydst); + return NULL; } @@ -1751,21 +1763,32 @@ _winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) /*[clinic end generated code: output=f41d941d73b1dac8 input=716a8ef620692466]*/ { int ret; - PyObject *pypath; + PyObject *pysrc = NULL; + PyObject *pydst = NULL; Py_BEGIN_ALLOW_THREADS ret = CreateDirectoryExW(src, dst, NULL); Py_END_ALLOW_THREADS if (ret == 0) { - pypath = Py_BuildValue("u", dst); - if (!pypath) - return NULL; - PyErr_SetExcFromWindowsErrWithFilenameObject( - PyExc_OSError, 0, pypath); - Py_CLEAR(pypath); + if (!(pysrc = Py_BuildValue("u", src))) + goto error; + if (!(pydst = Py_BuildValue("u", dst))) + goto error; + PyErr_SetExcFromWindowsErrWithFilenameObjects( + PyExc_OSError, 0, pysrc, pydst); + Py_CLEAR(pysrc); + Py_CLEAR(pydst); return NULL; + } Py_RETURN_NONE; + +error: + if (pysrc != NULL) + Py_CLEAR(pysrc); + if (pydst != NULL) + Py_CLEAR(pydst); + return NULL; } From e17e729ef213c695b005a1a34d06ac055ffc27ec Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 16:14:11 -0700 Subject: [PATCH 093/111] set 2 paths for OSError object --- Lib/test/test_shutil.py | 20 ++++++++++++-------- Modules/_winapi.c | 40 +++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index fb447d211dbc8e..7a4264167f26b3 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2157,32 +2157,36 @@ def test_makedirs(self): def test_makedirs_src_not_found(self): src_path = tempfile.mktemp() dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - with self.assertRaises(FileNotFoundError): + with self.assertRaises(FileNotFoundError) as cm: shutil._win_makedirs(src_path, dst_path) - with self.assertRaises(FileNotFoundError): - shutil.copytree(src_path, dst_path) + self.assertEqual(cm.exception.filename, src_path) + self.assertEqual(cm.exception.filename2, dst_path.split('\\')[0]) def test_makedirs_dst_exists(self): dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') os.makedirs(dst_path) - with self.assertRaises(FileExistsError): + with self.assertRaises(FileExistsError) as cm: shutil._win_makedirs(self.src_path, dst_path) - with self.assertRaises(FileExistsError): - shutil.copytree(self.src_path, dst_path) + self.assertEqual(cm.exception.filename, self.src_path) + self.assertEqual(cm.exception.filename2, dst_path) def test_CreateDirectoryExW_src_not_found(self): import _winapi src_path = tempfile.mktemp() dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - with self.assertRaises(FileNotFoundError): + with self.assertRaises(FileNotFoundError) as cm: _winapi.CreateDirectoryExW(src_path, dst_path) + self.assertEqual(cm.exception.filename, src_path) + self.assertEqual(cm.exception.filename2, dst_path) def test_copypathsecurityinfo_src_not_found(self): import _winapi src_path = tempfile.mktemp() dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - with self.assertRaises(FileNotFoundError): + with self.assertRaises(FileNotFoundError) as cm: _winapi.copypathsecurityinfo(src_path, dst_path) + self.assertEqual(cm.exception.filename, src_path) + self.assertEqual(cm.exception.filename2, dst_path) class TermsizeTests(unittest.TestCase): diff --git a/Modules/_winapi.c b/Modules/_winapi.c index ed07f2f116d161..4f8a1169860ee3 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1779,8 +1779,8 @@ _winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) Py_CLEAR(pysrc); Py_CLEAR(pydst); return NULL; - } + Py_RETURN_NONE; error: @@ -1808,7 +1808,8 @@ _winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst) { int ret; PSECURITY_DESCRIPTOR sacl = NULL; - PyObject *pypath; + PyObject *pysrc = NULL; + PyObject *pydst = NULL; /* Source path */ Py_BEGIN_ALLOW_THREADS @@ -1816,16 +1817,8 @@ _winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst) src, SE_FILE_OBJECT, ATTRIBUTE_SECURITY_INFORMATION, NULL, NULL, NULL, NULL, &sacl); Py_END_ALLOW_THREADS - - if (ret != ERROR_SUCCESS) { - pypath = Py_BuildValue("u", src); - if (!pypath) - return NULL; - PyErr_SetExcFromWindowsErrWithFilenameObject( - PyExc_OSError, ret, pypath); - Py_CLEAR(pypath); - return NULL; - } + if (ret != ERROR_SUCCESS) + goto error; /* Destination path */ Py_BEGIN_ALLOW_THREADS @@ -1833,18 +1826,23 @@ _winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst) dst, SE_FILE_OBJECT, ATTRIBUTE_SECURITY_INFORMATION, NULL, NULL, NULL, sacl); Py_END_ALLOW_THREADS + if (ret != ERROR_SUCCESS) + goto error; - if (ret != ERROR_SUCCESS) { - pypath = Py_BuildValue("u", dst); - if (!pypath) - return NULL; - PyErr_SetExcFromWindowsErrWithFilenameObject( - PyExc_OSError, ret, pypath); - Py_CLEAR(pypath); + Py_RETURN_NONE; + +error: + if (!(pysrc = Py_BuildValue("u", src))) + return NULL; + if (!(pydst = Py_BuildValue("u", dst))) { + Py_CLEAR(pysrc); return NULL; } - - Py_RETURN_NONE; + PyErr_SetExcFromWindowsErrWithFilenameObjects( + PyExc_OSError, 0, pysrc, pydst); + Py_CLEAR(pysrc); + Py_CLEAR(pydst); + return NULL; } From bc46f75ae02f4ee886b4e0a41bf6125d94d8b927 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 16:29:50 -0700 Subject: [PATCH 094/111] expand windows test --- Lib/test/test_shutil.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7a4264167f26b3..1386ce53d70e9e 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1950,9 +1950,12 @@ def test_same_file(self): def test_non_existent_src(self): name = tempfile.mktemp() + fun = shutil.copy2 if os.name == 'nt' else shutil.copyfile with self.assertRaises(FileNotFoundError) as cm: - shutil.copyfile(name, "new") + fun(name, "new") self.assertEqual(cm.exception.filename, name) + if os.name == 'nt': + self.assertEqual(cm.exception.filename2, "new") def test_empty_file(self): srcname = TESTFN + 'src' From cabbc02127c528c0315e43b52aef3e4d826d9390 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 7 Jun 2018 01:42:09 +0200 Subject: [PATCH 095/111] in case of exception on os.sendfile() set filename and filename2 exception attributes --- Lib/shutil.py | 6 +++++- Lib/test/test_shutil.py | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index c987bd2bc682ec..12093adfc3812a 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -152,6 +152,10 @@ def _fastcopy_sendfile(fsrc, fdst): try: sent = os.sendfile(outfd, infd, offset, blocksize) except OSError as err: + # ...in oder to have a more informative exception. + err.filename = fsrc.name + err.filename2 = fdst.name + if err.errno == errno.ENOTSOCK: # sendfile() on this platform (probably Linux < 2.6.33) # does not support copies between regular files (only @@ -166,7 +170,7 @@ def _fastcopy_sendfile(fsrc, fdst): if offset == 0 and os.lseek(outfd, 0, os.SEEK_CUR) == 0: raise _GiveupOnFastCopy(err) - raise err from None + raise err else: if sent == 0: break # EOF diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 1386ce53d70e9e..6fadf68b66f4dc 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -34,6 +34,7 @@ TESTFN2 = TESTFN + "2" OSX = sys.platform.startswith("darwin") +WINDOWS = os.name == 'nt' try: import grp import pwd @@ -1935,7 +1936,7 @@ def test_regular_copy(self): self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) # Make sure the fallback function is not called. with self.get_files() as (src, dst): - fun = shutil.copy2 if os.name == 'nt' else shutil.copyfile + fun = shutil.copy2 if WINDOWS else shutil.copyfile with unittest.mock.patch('shutil.copyfileobj') as m: fun(TESTFN, TESTFN2) assert not m.called @@ -1950,11 +1951,11 @@ def test_same_file(self): def test_non_existent_src(self): name = tempfile.mktemp() - fun = shutil.copy2 if os.name == 'nt' else shutil.copyfile + fun = shutil.copy2 if WINDOWS else shutil.copyfile with self.assertRaises(FileNotFoundError) as cm: fun(name, "new") self.assertEqual(cm.exception.filename, name) - if os.name == 'nt': + if OSX or WINDOWS: self.assertEqual(cm.exception.filename2, "new") def test_empty_file(self): @@ -1974,10 +1975,10 @@ def test_empty_file(self): def test_unhandled_exception(self): with unittest.mock.patch(self.PATCHPOINT, side_effect=ZeroDivisionError): - fun = shutil.copy2 if os.name == 'nt' else shutil.copyfile + fun = shutil.copy2 if WINDOWS else shutil.copyfile self.assertRaises(ZeroDivisionError, fun, TESTFN, TESTFN2) - @unittest.skipIf(os.name == 'nt', 'POSIX only') + @unittest.skipIf(WINDOWS, 'POSIX only') def test_exception_on_first_call(self): # Emulate a case where the first call to the zero-copy # function raises an exception in which case the function is @@ -2004,7 +2005,7 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): def zerocopy_fun(self, fsrc, fdst): return shutil._fastcopy_sendfile(fsrc, fdst) - @unittest.skipIf(os.name == 'nt', 'POSIX only') + @unittest.skipIf(WINDOWS, 'POSIX only') def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: @@ -2014,7 +2015,7 @@ def test_non_regular_file_src(self): self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) - @unittest.skipIf(os.name == 'nt', 'POSIX only') + @unittest.skipIf(WINDOWS, 'POSIX only') def test_non_regular_file_dst(self): with open(TESTFN, "rb") as src: with io.BytesIO() as dst: @@ -2126,7 +2127,7 @@ def zerocopy_fun(self, src, dst): return shutil._fastcopy_osx(src.name, dst.name, posix._COPYFILE_DATA) -@unittest.skipIf(not os.name == 'nt', 'Windows only') +@unittest.skipIf(not WINDOWS, 'Windows only') class TestZeroCopyWindows(_ZeroCopyFileTest, unittest.TestCase): PATCHPOINT = "_winapi.CopyFileExW" @@ -2134,7 +2135,7 @@ def zerocopy_fun(self, src, dst): return shutil._fastcopy_win(src.name, dst.name) -@unittest.skipIf(not os.name == 'nt', 'Windows only') +@unittest.skipIf(not WINDOWS, 'Windows only') class TestWindowsMakedirs(unittest.TestCase): """copytree() on Windows uses a dedicated version of os.makedirs() using specific Windows APIs in order to copy directory attributes. From d22ee089c840d5f440a1821793f1bc4a72c28a3e Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 7 Jun 2018 01:48:15 +0200 Subject: [PATCH 096/111] set 2 filenames (src, dst) for OSError in case copyfile() fails on OSX --- Modules/posixmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 28db200a3418bd..437bfe31dcf4bf 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8771,7 +8771,7 @@ os__copyfile_impl(PyObject *module, path_t *src, path_t *dst, int flags) ret = copyfile(src->narrow, dst->narrow, NULL, flags); Py_END_ALLOW_THREADS if (ret < 0) - return path_error(src); + return path_error2(src, dst); Py_RETURN_NONE; } #endif From 7a0820331c754b4c12810714f21adcdf58b66273 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 7 Jun 2018 02:14:33 +0200 Subject: [PATCH 097/111] update doc --- Doc/library/shutil.rst | 16 ++++++++++++++-- Lib/shutil.py | 11 +++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 0c8ef8de9c3276..58f9861e915a89 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -408,15 +408,21 @@ efficiently (see :issue:`33671`). the use of userspace buffers in Python as in "``outfd.write(infd.read())``". On OSX `copyfile`_ is used to copy the file content (not metadata). + On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports copies between 2 regular file descriptors :func:`os.sendfile` is used. -On Windows `CopyFileEx`_ is used by all copy functions except :func:`copyfile`. + +On Windows `CopyFileEx`_ is used by all copy functions except :func:`copyfile` +preserving file's extended attributes, metadata and security information. +Also, when creating directories, :func:`copytree` uses `CreateDirectoryEx`_ and +`SetNamedSecurityInfo`_ preserving source directory attributes and security +information. If the fast-copy operation fails and no data was written in the destination file then shutil will silently fallback on using less efficient :func:`copyfileobj` function internally. -.. versionadded:: 3.8 +.. versionchanged:: 3.8 .. _shutil-copytree-example: @@ -704,6 +710,12 @@ Querying the size of the output terminal .. _`CopyFileEx`: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363852(v=vs.85).aspx +.. _`CreateDirectoryEx`: + https://msdn.microsoft.com/en-us/library/windows/desktop/aa363856(v=vs.85).aspx + +.. _`SetNamedSecurityInfo`: + https://msdn.microsoft.com/en-us/library/windows/desktop/aa379579(v=vs.85).aspx + .. _`copyfile`: http://www.manpagez.com/man/3/copyfile/ diff --git a/Lib/shutil.py b/Lib/shutil.py index 12093adfc3812a..4efb240e9e3764 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -114,7 +114,8 @@ def _fastcopy_osx(src, dst, flags): def _fastcopy_win(src, dst): """Copy 2 files by using high-performance CopyFileExW (Windows only). - Note: this will also copy file metadata. + Note: this will also copy file's extended attributes, metadata and + security information. """ try: _winapi.CopyFileExW(src, dst, 0) @@ -374,9 +375,8 @@ def copy2(src, dst, *, follow_symlinks=True): If follow_symlinks is false, symlinks won't be followed. This resembles GNU's "cp -P src dst". - On Windows this function uses CopyFileEx which preserves extended - attributes, OLE structured storage, NTFS file system alternate - data streams, security resource attributes and file attributes. + On Windows this function uses CopyFileEx which preserves file's + extended attributes, metadata and security information. """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) @@ -465,6 +465,9 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, destination path as arguments. By default, copy2() is used, but any function that supports the same signature (like copy()) can be used. + On Windows this function uses CreateDirectoryEx and SetNamedSecurityInfo + to create directories, preserving source directory attributes and + security information. """ names = os.listdir(src) if ignore is not None: From ab284e9b31b628ba38405e396accdcb358a35710 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Wed, 6 Jun 2018 18:27:38 -0700 Subject: [PATCH 098/111] do not use CreateDirectoryEx() in copytree() if source dir is a symlink (breaks test_copytree_symlink_dir); instead just create a plain dir and remain consistent with POSIX implementation --- Lib/shutil.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 4efb240e9e3764..ed5e540d32f2cc 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -426,8 +426,16 @@ def _win_makedirs(src, dst): if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists return # --- - _winapi.CreateDirectoryExW(src, dst) - _winapi.copypathsecurityinfo(src, dst) + if os.path.islink(src): + # On Windows if src dir is a symlink CreateDirectoryExW() also + # creates a symlink. Not on UNIX. For consistency across + # platforms and in order to avoid possible SameFileError + # exceptions later on we'll just create a plain dir. + os.mkdir(dst) + _winapi.copypathsecurityinfo(src, dst) + else: + _winapi.CreateDirectoryExW(src, dst) + _winapi.copypathsecurityinfo(src, dst) def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, From ac9479db29ba9cd742c44139fdb923b6a6a99821 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 7 Jun 2018 18:15:55 +0200 Subject: [PATCH 099/111] use bytearray() and readinto() --- Lib/shutil.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index ed5e540d32f2cc..6a3b4782d246bd 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -196,7 +196,17 @@ def _fastcopy_fileobj(fsrc, fdst): return _fastcopy_sendfile(fsrc, fdst) except _GiveupOnFastCopy: pass - return copyfileobj(fsrc, fdst) + + # Faster than copyfileobj() as it uses bytearray() and readinto(). + # Cannot use it in copyfileobj() as we're not sure files are + # open in binary mode. + bufsize = 16 * 1024 + while True: + buf = bytearray(bufsize) + buflen = fsrc.readinto(buf) + if not buflen: + break + fdst.write(buf[:buflen]) def _samefile(src, dst): # Macintosh, Unix. From fd77a7ede02d9c65b0b09ef50913004335630872 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 7 Jun 2018 22:35:46 +0200 Subject: [PATCH 100/111] use memoryview() with bytearray() --- Lib/shutil.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 6a3b4782d246bd..90c22f8f80a04c 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -200,13 +200,17 @@ def _fastcopy_fileobj(fsrc, fdst): # Faster than copyfileobj() as it uses bytearray() and readinto(). # Cannot use it in copyfileobj() as we're not sure files are # open in binary mode. - bufsize = 16 * 1024 - while True: - buf = bytearray(bufsize) - buflen = fsrc.readinto(buf) - if not buflen: - break - fdst.write(buf[:buflen]) + length = 1024 * 1024 # 1MB + with memoryview(bytearray(length)) as mv: + while True: + n = fsrc.readinto(mv) + if not n: + break + elif n < length: + with mv[:n] as smv: + fdst.write(smv) + else: + fdst.write(mv) def _samefile(src, dst): # Macintosh, Unix. From 42a597e2801631c0090cba53a37e235462d35b40 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 8 Jun 2018 21:17:34 +0200 Subject: [PATCH 101/111] refactoring + introduce a new _fastcopy_binfileobj() fun --- Lib/shutil.py | 87 +++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 90c22f8f80a04c..142da05490aa15 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -87,14 +87,6 @@ class _GiveupOnFastCopy(Exception): file copy when fast-copy functions fail to do so. """ -def copyfileobj(fsrc, fdst, length=16*1024): - """copy data from file-like object fsrc to file-like object fdst""" - while 1: - buf = fsrc.read(length) - if not buf: - break - fdst.write(buf) - def _fastcopy_osx(src, dst, flags): """Copy a regular file content or metadata by using high-performance copyfile(3) syscall (OSX only). @@ -131,6 +123,15 @@ def _fastcopy_sendfile(fsrc, fdst): high-performance sendfile(2) syscall. This should work on Linux >= 2.6.33 and Solaris only. """ + # Note: copyfileobj() is left alone in order to not introduce any + # unexpected breakage. Possible risks by using zero-copy calls + # in copyfileobj() are: + # - fdst cannot be open in "a"(ppend) mode + # - fsrc and fdst may be open in "t"(ext) mode + # - fsrc may be a BufferedReader (which hides unread data in a buffer), + # GzipFile (which decompresses data), HTTPResponse (which decodes + # chunks). + # - possibly others (e.g. encrypted fs/partition?) global _HAS_SENDFILE try: infd = fsrc.fileno() @@ -177,40 +178,31 @@ def _fastcopy_sendfile(fsrc, fdst): break # EOF offset += sent -def _fastcopy_fileobj(fsrc, fdst): - """Copy 2 regular mmap-like files by using zero-copy sendfile(2) - syscall first. In case of error and no data was written in fdst - fallback on using plain read()/write() method. - """ - # Note: copyfileobj() is left alone in order to not introduce any - # unexpected breakage. Possible risks by using zero-copy calls - # in copyfileobj() are: - # - fdst cannot be open in "a"(ppend) mode - # - fsrc and fdst may be open in "t"(ext) mode - # - fsrc may be a BufferedReader (which hides unread data in a buffer), - # GzipFile (which decompresses data), HTTPResponse (which decodes - # chunks). - # - possibly others (e.g. encrypted fs/partition?) - if _HAS_SENDFILE: - try: - return _fastcopy_sendfile(fsrc, fdst) - except _GiveupOnFastCopy: - pass +def _fastcopy_binfileobj(fsrc, fdst, length=1024 * 1024): + """Copy 2 regular file objects opened in binary mode.""" + # Localize variable access to minimize overhead. + fsrc_readinto = fsrc.readinto + fdst_write = fdst.write + mv = memoryview(bytearray(length)) + while True: + n = fsrc_readinto(mv) + if not n: + break + elif n < length: + fdst_write(mv[:n]) + else: + fdst_write(mv) - # Faster than copyfileobj() as it uses bytearray() and readinto(). - # Cannot use it in copyfileobj() as we're not sure files are - # open in binary mode. - length = 1024 * 1024 # 1MB - with memoryview(bytearray(length)) as mv: - while True: - n = fsrc.readinto(mv) - if not n: - break - elif n < length: - with mv[:n] as smv: - fdst.write(smv) - else: - fdst.write(mv) +def copyfileobj(fsrc, fdst, length=16*1024): + """copy data from file-like object fsrc to file-like object fdst""" + # Localize variable access to minimize overhead. + fsrc_read = fsrc.read + fdst_write = fdst.write + while 1: + buf = fsrc_read(length) + if not buf: + break + fdst_write(buf) def _samefile(src, dst): # Macintosh, Unix. @@ -255,9 +247,16 @@ def copyfile(src, dst, *, follow_symlinks=True): except _GiveupOnFastCopy: pass - with open(src, 'rb') as fsrc: - with open(dst, 'wb') as fdst: - _fastcopy_fileobj(fsrc, fdst) + with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst: + if _HAS_SENDFILE: + try: + _fastcopy_sendfile(fsrc, fdst) + return dst + except _GiveupOnFastCopy: + pass + + _fastcopy_binfileobj(fsrc, fdst) + return dst def copymode(src, dst, *, follow_symlinks=True): From 5008a8d79a8334bc658ac8bcbac9e743f48c33a8 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 8 Jun 2018 22:41:24 +0200 Subject: [PATCH 102/111] remove CopyFileEx and other C wrappers --- Modules/_winapi.c | 148 ------------------------------------- Modules/clinic/_winapi.c.h | 91 +---------------------- 2 files changed, 1 insertion(+), 238 deletions(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 4f8a1169860ee3..7b26f1973ba50b 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1701,151 +1701,6 @@ _winapi_GetFileType_impl(PyObject *module, HANDLE handle) } -/*[clinic input] -_winapi.CopyFileExW - - src: LPCWSTR - dst: LPCWSTR - flags: DWORD - / - -Efficiently copy 2 files. -[clinic start generated code]*/ - -static PyObject * -_winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, - DWORD flags) -/*[clinic end generated code: output=715613c8834b35f5 input=bea3e5c545b755be]*/ -{ - int ret; - PyObject *pysrc = NULL; - PyObject *pydst = NULL; - - Py_BEGIN_ALLOW_THREADS - ret = CopyFileExW(src, dst, NULL, NULL, NULL, flags); - Py_END_ALLOW_THREADS - - if (ret == 0) { - if (!(pysrc = Py_BuildValue("u", src))) - goto error; - if (!(pydst = Py_BuildValue("u", dst))) - goto error; - PyErr_SetExcFromWindowsErrWithFilenameObjects( - PyExc_OSError, 0, pysrc, pydst); - Py_CLEAR(pysrc); - Py_CLEAR(pydst); - return NULL; - } - - Py_RETURN_NONE; - -error: - if (pysrc != NULL) - Py_CLEAR(pysrc); - if (pydst != NULL) - Py_CLEAR(pydst); - return NULL; -} - - -/*[clinic input] -_winapi.CreateDirectoryExW - - src: LPWSTR - dst: LPWSTR - / - -Creates a new directory with the attributes of a specified template directory. -[clinic start generated code]*/ - -static PyObject * -_winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst) -/*[clinic end generated code: output=f41d941d73b1dac8 input=716a8ef620692466]*/ -{ - int ret; - PyObject *pysrc = NULL; - PyObject *pydst = NULL; - - Py_BEGIN_ALLOW_THREADS - ret = CreateDirectoryExW(src, dst, NULL); - Py_END_ALLOW_THREADS - if (ret == 0) { - if (!(pysrc = Py_BuildValue("u", src))) - goto error; - if (!(pydst = Py_BuildValue("u", dst))) - goto error; - PyErr_SetExcFromWindowsErrWithFilenameObjects( - PyExc_OSError, 0, pysrc, pydst); - Py_CLEAR(pysrc); - Py_CLEAR(pydst); - return NULL; - } - - Py_RETURN_NONE; - -error: - if (pysrc != NULL) - Py_CLEAR(pysrc); - if (pydst != NULL) - Py_CLEAR(pydst); - return NULL; -} - - -/*[clinic input] -_winapi.copypathsecurityinfo - - src: LPWSTR - dst: LPWSTR - / - -Copy the security information of one path to another. -[clinic start generated code]*/ - -static PyObject * -_winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst) -/*[clinic end generated code: output=09045a3ba0244ff5 input=02ebe3bee4d04f75]*/ -{ - int ret; - PSECURITY_DESCRIPTOR sacl = NULL; - PyObject *pysrc = NULL; - PyObject *pydst = NULL; - - /* Source path */ - Py_BEGIN_ALLOW_THREADS - ret = GetNamedSecurityInfoW( - src, SE_FILE_OBJECT, ATTRIBUTE_SECURITY_INFORMATION, - NULL, NULL, NULL, NULL, &sacl); - Py_END_ALLOW_THREADS - if (ret != ERROR_SUCCESS) - goto error; - - /* Destination path */ - Py_BEGIN_ALLOW_THREADS - ret = SetNamedSecurityInfoW( - dst, SE_FILE_OBJECT, ATTRIBUTE_SECURITY_INFORMATION, - NULL, NULL, NULL, sacl); - Py_END_ALLOW_THREADS - if (ret != ERROR_SUCCESS) - goto error; - - Py_RETURN_NONE; - -error: - if (!(pysrc = Py_BuildValue("u", src))) - return NULL; - if (!(pydst = Py_BuildValue("u", dst))) { - Py_CLEAR(pysrc); - return NULL; - } - PyErr_SetExcFromWindowsErrWithFilenameObjects( - PyExc_OSError, 0, pysrc, pydst); - Py_CLEAR(pysrc); - Py_CLEAR(pydst); - return NULL; -} - - static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF @@ -1873,9 +1728,6 @@ static PyMethodDef winapi_functions[] = { _WINAPI_WRITEFILE_METHODDEF _WINAPI_GETACP_METHODDEF _WINAPI_GETFILETYPE_METHODDEF - _WINAPI_COPYFILEEXW_METHODDEF - _WINAPI_CREATEDIRECTORYEXW_METHODDEF - _WINAPI_COPYPATHSECURITYINFO_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index 879e543b9ea565..b14f087732ef43 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -941,93 +941,4 @@ _winapi_GetFileType(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P exit: return return_value; } - -PyDoc_STRVAR(_winapi_CopyFileExW__doc__, -"CopyFileExW($module, src, dst, flags, /)\n" -"--\n" -"\n" -"Efficiently copy 2 files."); - -#define _WINAPI_COPYFILEEXW_METHODDEF \ - {"CopyFileExW", (PyCFunction)_winapi_CopyFileExW, METH_FASTCALL, _winapi_CopyFileExW__doc__}, - -static PyObject * -_winapi_CopyFileExW_impl(PyObject *module, LPCWSTR src, LPCWSTR dst, - DWORD flags); - -static PyObject * -_winapi_CopyFileExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPCWSTR src; - LPCWSTR dst; - DWORD flags; - - if (!_PyArg_ParseStack(args, nargs, "uuk:CopyFileExW", - &src, &dst, &flags)) { - goto exit; - } - return_value = _winapi_CopyFileExW_impl(module, src, dst, flags); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_CreateDirectoryExW__doc__, -"CreateDirectoryExW($module, src, dst, /)\n" -"--\n" -"\n" -"Creates a new directory with the attributes of a specified template directory."); - -#define _WINAPI_CREATEDIRECTORYEXW_METHODDEF \ - {"CreateDirectoryExW", (PyCFunction)_winapi_CreateDirectoryExW, METH_FASTCALL, _winapi_CreateDirectoryExW__doc__}, - -static PyObject * -_winapi_CreateDirectoryExW_impl(PyObject *module, LPWSTR src, LPWSTR dst); - -static PyObject * -_winapi_CreateDirectoryExW(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPWSTR src; - LPWSTR dst; - - if (!_PyArg_ParseStack(args, nargs, "uu:CreateDirectoryExW", - &src, &dst)) { - goto exit; - } - return_value = _winapi_CreateDirectoryExW_impl(module, src, dst); - -exit: - return return_value; -} - -PyDoc_STRVAR(_winapi_copypathsecurityinfo__doc__, -"copypathsecurityinfo($module, src, dst, /)\n" -"--\n" -"\n" -"Copy the security information of one path to another."); - -#define _WINAPI_COPYPATHSECURITYINFO_METHODDEF \ - {"copypathsecurityinfo", (PyCFunction)_winapi_copypathsecurityinfo, METH_FASTCALL, _winapi_copypathsecurityinfo__doc__}, - -static PyObject * -_winapi_copypathsecurityinfo_impl(PyObject *module, LPWSTR src, LPWSTR dst); - -static PyObject * -_winapi_copypathsecurityinfo(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - LPWSTR src; - LPWSTR dst; - - if (!_PyArg_ParseStack(args, nargs, "uu:copypathsecurityinfo", - &src, &dst)) { - goto exit; - } - return_value = _winapi_copypathsecurityinfo_impl(module, src, dst); - -exit: - return return_value; -} -/*[clinic end generated code: output=c1713b770ffdfda1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ec7f36eb7efc9d00 input=a9049054013a1b77]*/ From e89dd20518a80abf7971c01d8fc472ca7552082a Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 8 Jun 2018 15:14:51 -0700 Subject: [PATCH 103/111] remove code related to CopyFileEx --- Lib/shutil.py | 62 +------------------------------------- Lib/test/test_shutil.py | 66 ----------------------------------------- 2 files changed, 1 insertion(+), 127 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 142da05490aa15..2626c4f5ee4640 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -47,7 +47,6 @@ import posix elif os.name == 'nt': import nt - import _winapi _HAS_SENDFILE = posix and hasattr(os, "sendfile") _HAS_COPYFILE = posix and hasattr(posix, "_copyfile") # OSX @@ -104,20 +103,6 @@ def _fastcopy_osx(src, dst, flags): else: raise err from None -def _fastcopy_win(src, dst): - """Copy 2 files by using high-performance CopyFileExW (Windows only). - Note: this will also copy file's extended attributes, metadata and - security information. - """ - try: - _winapi.CopyFileExW(src, dst, 0) - except PermissionError as err: - if err.winerror == _winapi.ERROR_SHARING_VIOLATION and \ - _samefile(src, dst): - raise SameFileError( - "{!r} and {!r} are the same file".format(src, dst)) - raise err from None - def _fastcopy_sendfile(fsrc, fdst): """Copy data from one regular mmap-like fd to another by using high-performance sendfile(2) syscall. @@ -394,13 +379,6 @@ def copy2(src, dst, *, follow_symlinks=True): if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) - if os.name == 'nt': - if not follow_symlinks and os.path.islink(src): - os.symlink(os.readlink(src), dst) - else: - _fastcopy_win(src, dst) - return dst - copyfile(src, dst, follow_symlinks=follow_symlinks) copystat(src, dst, follow_symlinks=follow_symlinks) return dst @@ -417,40 +395,6 @@ def _ignore_patterns(path, names): return set(ignored_names) return _ignore_patterns -def _win_makedirs(src, dst): - """Similar to os.makedirs() but creates a directory tree copying - attributes and security info from a template "src" directory. - As such "src" directory must exist else this will fail with - FileNotFoundError. - """ - # --- - head, tail = os.path.split(dst) - if not tail: - head, tail = os.path.split(head) - if head and tail and not os.path.exists(head): - try: - _win_makedirs(src, head) - except FileExistsError: - # Defeats race condition when another thread created the path - pass - cdir = os.curdir - if isinstance(tail, bytes): - cdir = bytes(os.curdir, 'ASCII') - if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists - return - # --- - if os.path.islink(src): - # On Windows if src dir is a symlink CreateDirectoryExW() also - # creates a symlink. Not on UNIX. For consistency across - # platforms and in order to avoid possible SameFileError - # exceptions later on we'll just create a plain dir. - os.mkdir(dst) - _winapi.copypathsecurityinfo(src, dst) - else: - _winapi.CreateDirectoryExW(src, dst) - _winapi.copypathsecurityinfo(src, dst) - - def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False): """Recursively copy a directory tree. @@ -496,11 +440,7 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, else: ignored_names = set() - if os.name == 'nt' and copy_function is copy2: - _win_makedirs(src, dst) - else: - os.makedirs(dst) - + os.makedirs(dst) errors = [] for name in names: if name in ignored_names: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 6fadf68b66f4dc..245ddca5949c28 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2127,72 +2127,6 @@ def zerocopy_fun(self, src, dst): return shutil._fastcopy_osx(src.name, dst.name, posix._COPYFILE_DATA) -@unittest.skipIf(not WINDOWS, 'Windows only') -class TestZeroCopyWindows(_ZeroCopyFileTest, unittest.TestCase): - PATCHPOINT = "_winapi.CopyFileExW" - - def zerocopy_fun(self, src, dst): - return shutil._fastcopy_win(src.name, dst.name) - - -@unittest.skipIf(not WINDOWS, 'Windows only') -class TestWindowsMakedirs(unittest.TestCase): - """copytree() on Windows uses a dedicated version of os.makedirs() - using specific Windows APIs in order to copy directory attributes. - """ - - def setUp(self): - self.tearDown() - self.src_path = os.path.join(TESTFN, 'a', 'b', 'c') - os.makedirs(self.src_path) - - def tearDown(self): - shutil.rmtree(TESTFN, ignore_errors=True) - shutil.rmtree(TESTFN2, ignore_errors=True) - - def test_makedirs(self): - dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - shutil._win_makedirs(self.src_path, dst_path) - assert os.path.isdir(os.path.join(TESTFN2)) - assert os.path.isdir(os.path.join(TESTFN2, "a")) - assert os.path.isdir(os.path.join(TESTFN2, "a", "b")) - assert os.path.isdir(os.path.join(TESTFN2, "a", "b", "c")) - - def test_makedirs_src_not_found(self): - src_path = tempfile.mktemp() - dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - with self.assertRaises(FileNotFoundError) as cm: - shutil._win_makedirs(src_path, dst_path) - self.assertEqual(cm.exception.filename, src_path) - self.assertEqual(cm.exception.filename2, dst_path.split('\\')[0]) - - def test_makedirs_dst_exists(self): - dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - os.makedirs(dst_path) - with self.assertRaises(FileExistsError) as cm: - shutil._win_makedirs(self.src_path, dst_path) - self.assertEqual(cm.exception.filename, self.src_path) - self.assertEqual(cm.exception.filename2, dst_path) - - def test_CreateDirectoryExW_src_not_found(self): - import _winapi - src_path = tempfile.mktemp() - dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - with self.assertRaises(FileNotFoundError) as cm: - _winapi.CreateDirectoryExW(src_path, dst_path) - self.assertEqual(cm.exception.filename, src_path) - self.assertEqual(cm.exception.filename2, dst_path) - - def test_copypathsecurityinfo_src_not_found(self): - import _winapi - src_path = tempfile.mktemp() - dst_path = os.path.join(TESTFN2, 'a', 'b', 'c') - with self.assertRaises(FileNotFoundError) as cm: - _winapi.copypathsecurityinfo(src_path, dst_path) - self.assertEqual(cm.exception.filename, src_path) - self.assertEqual(cm.exception.filename2, dst_path) - - class TermsizeTests(unittest.TestCase): def test_does_not_crash(self): """Check if get_terminal_size() returns a meaningful value. From c0dc4b860cf05780c49f693e89cfb9f9d01d661c Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 9 Jun 2018 00:23:07 +0200 Subject: [PATCH 104/111] Recognize binary files in copyfileobj() ...and use fastest _fastcopy_binfileobj() when possible --- Lib/shutil.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/shutil.py b/Lib/shutil.py index 2626c4f5ee4640..513b4edaa374c7 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -10,6 +10,7 @@ import fnmatch import collections import errno +import io try: import zlib @@ -178,8 +179,15 @@ def _fastcopy_binfileobj(fsrc, fdst, length=1024 * 1024): else: fdst_write(mv) +def _is_binary_files_pair(fsrc, fdst): + return hasattr(fsrc, 'readinto') and \ + isinstance(fsrc, io.BytesIO) or 'b' in getattr(fsrc, 'mode', '') and \ + isinstance(fdst, io.BytesIO) or 'b' in getattr(fdst, 'mode', '') + def copyfileobj(fsrc, fdst, length=16*1024): """copy data from file-like object fsrc to file-like object fdst""" + if _is_binary_files_pair(fsrc, fdst): + return _fastcopy_binfileobj(fsrc, fdst, length=length) # Localize variable access to minimize overhead. fsrc_read = fsrc.read fdst_write = fdst.write From 29b97305f100e83680bcd4bc1f95571edba0671e Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 9 Jun 2018 00:42:19 +0200 Subject: [PATCH 105/111] set 1MB copy bufsize on win; also add a global _COPY_BUFSIZE variable --- Lib/shutil.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 513b4edaa374c7..68580aa83ebddf 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -49,6 +49,8 @@ elif os.name == 'nt': import nt +# 1MB bufsize on Windows results in sensitive better performances +_COPY_BUFSIZE = 1024 * 1024 if os.name == 'nt' else 16 * 1024 _HAS_SENDFILE = posix and hasattr(os, "sendfile") _HAS_COPYFILE = posix and hasattr(posix, "_copyfile") # OSX @@ -164,7 +166,7 @@ def _fastcopy_sendfile(fsrc, fdst): break # EOF offset += sent -def _fastcopy_binfileobj(fsrc, fdst, length=1024 * 1024): +def _fastcopy_binfileobj(fsrc, fdst, length=_COPY_BUFSIZE): """Copy 2 regular file objects opened in binary mode.""" # Localize variable access to minimize overhead. fsrc_readinto = fsrc.readinto @@ -184,18 +186,19 @@ def _is_binary_files_pair(fsrc, fdst): isinstance(fsrc, io.BytesIO) or 'b' in getattr(fsrc, 'mode', '') and \ isinstance(fdst, io.BytesIO) or 'b' in getattr(fdst, 'mode', '') -def copyfileobj(fsrc, fdst, length=16*1024): +def copyfileobj(fsrc, fdst, length=_COPY_BUFSIZE): """copy data from file-like object fsrc to file-like object fdst""" if _is_binary_files_pair(fsrc, fdst): - return _fastcopy_binfileobj(fsrc, fdst, length=length) - # Localize variable access to minimize overhead. - fsrc_read = fsrc.read - fdst_write = fdst.write - while 1: - buf = fsrc_read(length) - if not buf: - break - fdst_write(buf) + _fastcopy_binfileobj(fsrc, fdst, length=length) + else: + # Localize variable access to minimize overhead. + fsrc_read = fsrc.read + fdst_write = fdst.write + while 1: + buf = fsrc_read(length) + if not buf: + break + fdst_write(buf) def _samefile(src, dst): # Macintosh, Unix. From a1bed32440c860e39add7b1ceb70001ae8303c40 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 9 Jun 2018 01:38:25 +0200 Subject: [PATCH 106/111] use ctx manager for memoryview() --- Lib/shutil.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 68580aa83ebddf..f640369085613a 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -49,7 +49,6 @@ elif os.name == 'nt': import nt -# 1MB bufsize on Windows results in sensitive better performances _COPY_BUFSIZE = 1024 * 1024 if os.name == 'nt' else 16 * 1024 _HAS_SENDFILE = posix and hasattr(os, "sendfile") _HAS_COPYFILE = posix and hasattr(posix, "_copyfile") # OSX @@ -166,20 +165,20 @@ def _fastcopy_sendfile(fsrc, fdst): break # EOF offset += sent -def _fastcopy_binfileobj(fsrc, fdst, length=_COPY_BUFSIZE): +def _copybinfileobj(fsrc, fdst, length=_COPY_BUFSIZE): """Copy 2 regular file objects opened in binary mode.""" # Localize variable access to minimize overhead. fsrc_readinto = fsrc.readinto fdst_write = fdst.write - mv = memoryview(bytearray(length)) - while True: - n = fsrc_readinto(mv) - if not n: - break - elif n < length: - fdst_write(mv[:n]) - else: - fdst_write(mv) + with memoryview(bytearray(length)) as mv: + while True: + n = fsrc_readinto(mv) + if not n: + break + elif n < length: + fdst_write(mv[:n]) + else: + fdst_write(mv) def _is_binary_files_pair(fsrc, fdst): return hasattr(fsrc, 'readinto') and \ @@ -189,7 +188,7 @@ def _is_binary_files_pair(fsrc, fdst): def copyfileobj(fsrc, fdst, length=_COPY_BUFSIZE): """copy data from file-like object fsrc to file-like object fdst""" if _is_binary_files_pair(fsrc, fdst): - _fastcopy_binfileobj(fsrc, fdst, length=length) + _copybinfileobj(fsrc, fdst, length=length) else: # Localize variable access to minimize overhead. fsrc_read = fsrc.read @@ -251,7 +250,7 @@ def copyfile(src, dst, *, follow_symlinks=True): except _GiveupOnFastCopy: pass - _fastcopy_binfileobj(fsrc, fdst) + _copybinfileobj(fsrc, fdst) return dst From d9d27a7be7a4aed4bce8782af098e07a648d244c Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 9 Jun 2018 02:49:03 +0200 Subject: [PATCH 107/111] update doc --- Doc/library/shutil.rst | 37 ++++++------------- Doc/whatsnew/3.8.rst | 13 +++++-- Lib/shutil.py | 2 + .../2018-05-28-23-25-17.bpo-33671.GIdKKi.rst | 13 +++++-- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 58f9861e915a89..c2a35d6bcbe706 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -77,8 +77,8 @@ Directory and files operations a subclass of the latter, this change is backward compatible. .. versionchanged:: 3.8 - Platform-specific fast-copy syscalls are used internally in order to copy - the file more efficiently. See + Platform-specific fast-copy syscalls may be used internally in order to + copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. exception:: SameFileError @@ -170,8 +170,8 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Platform-specific fast-copy syscalls are used internally in order to copy - the file more efficiently. See + Platform-specific fast-copy syscalls may be used internally in order to + copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: copy2(src, dst, *, follow_symlinks=True) @@ -197,8 +197,8 @@ Directory and files operations Now returns path to the newly created file. .. versionchanged:: 3.8 - Platform-specific fast-copy syscalls are used internally in order to copy - the file more efficiently. See + Platform-specific fast-copy syscalls may be used internally in order to + copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: ignore_patterns(\*patterns) @@ -258,8 +258,8 @@ Directory and files operations errors when *symlinks* is false. .. versionchanged:: 3.8 - Platform-specific fast-copy syscalls are used internally in order to copy - the file more efficiently. See + Platform-specific fast-copy syscalls may be used internally in order to + copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: rmtree(path, ignore_errors=False, onerror=None) @@ -335,8 +335,8 @@ Directory and files operations Added the *copy_function* keyword argument. .. versionchanged:: 3.8 - Platform-specific fast-copy syscalls are used internally in order to copy - the file more efficiently. See + Platform-specific fast-copy syscalls may be used internally in order to + copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. function:: disk_usage(path) @@ -401,7 +401,7 @@ Platform-dependent efficient copy operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Starting from Python 3.8 all functions involving a file copy (:func:`copyfile`, -:func:`copy`, :func:`copy2`, :func:`copytree`, and :func:`move`) use +:func:`copy`, :func:`copy2`, :func:`copytree`, and :func:`move`) may use platform-specific "fast-copy" syscalls in order to copy the file more efficiently (see :issue:`33671`). "fast-copy" means that the copying operation occurs within the kernel, avoiding @@ -412,12 +412,6 @@ On OSX `copyfile`_ is used to copy the file content (not metadata). On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports copies between 2 regular file descriptors :func:`os.sendfile` is used. -On Windows `CopyFileEx`_ is used by all copy functions except :func:`copyfile` -preserving file's extended attributes, metadata and security information. -Also, when creating directories, :func:`copytree` uses `CreateDirectoryEx`_ and -`SetNamedSecurityInfo`_ preserving source directory attributes and security -information. - If the fast-copy operation fails and no data was written in the destination file then shutil will silently fallback on using less efficient :func:`copyfileobj` function internally. @@ -707,15 +701,6 @@ Querying the size of the output terminal .. versionadded:: 3.3 -.. _`CopyFileEx`: - https://msdn.microsoft.com/en-us/library/windows/desktop/aa363852(v=vs.85).aspx - -.. _`CreateDirectoryEx`: - https://msdn.microsoft.com/en-us/library/windows/desktop/aa363856(v=vs.85).aspx - -.. _`SetNamedSecurityInfo`: - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379579(v=vs.85).aspx - .. _`copyfile`: http://www.manpagez.com/man/3/copyfile/ diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index c8f2bc600e4a1a..2dd855febf385a 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -94,13 +94,20 @@ Optimizations ============= * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, - :func:`shutil.copytree` and :func:`shutil.move` use platform specific - "fast-copy" syscalls in order to copy the file more efficiently. + :func:`shutil.copytree` and :func:`shutil.move` use platform-specific + "fast-copy" syscalls on Linux, OSX and Solaris in order to copy the file more + efficiently. "fast-copy" means that the copying operation occurs within the kernel, avoiding the use of userspace buffers in Python as in "``outfd.write(infd.read())``". + All other platforms not using such technique will rely on a faster + :func:`shutil.copyfile` implementation using :func:`memoryview`, + :class:`bytearray` and + :meth:`BufferedIOBase.readinto() `. + Finally, :func:`shutil.copyfile` default buffer size on Windows was increased + from 16KB to 1MB. The speedup for copying a 512MB file within the same partition is about +26% - on Linux, +50% on OSX and +48% on Windows. Also, much less CPU cycles are + on Linux, +50% on OSX and +38% on Windows. Also, much less CPU cycles are consumed. (Contributed by Giampaolo Rodola' in :issue:`25427`.) diff --git a/Lib/shutil.py b/Lib/shutil.py index f640369085613a..0dbc7c7cd626c7 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -49,6 +49,8 @@ elif os.name == 'nt': import nt +# On Windows using 1MB instead of 16KB results in significantly better +# performances. _COPY_BUFSIZE = 1024 * 1024 if os.name == 'nt' else 16 * 1024 _HAS_SENDFILE = posix and hasattr(os, "sendfile") _HAS_COPYFILE = posix and hasattr(posix, "_copyfile") # OSX diff --git a/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst index 05eea7951889bd..5fd7e1f1e217fd 100644 --- a/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst +++ b/Misc/NEWS.d/next/Library/2018-05-28-23-25-17.bpo-33671.GIdKKi.rst @@ -1,6 +1,11 @@ :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, -:func:`shutil.copytree` and :func:`shutil.move` use platform specific -fast-copy syscalls on Linux, Solaris, OSX and Windows in order to copy the file -more efficiently. The speedup for copying a 512MB file is about +26% on Linux, -+50% on OSX and +48% on Windows. Also, much less CPU cycles are consumed +:func:`shutil.copytree` and :func:`shutil.move` use platform-specific +fast-copy syscalls on Linux, Solaris and OSX in order to copy the file +more efficiently. All other platforms not using such technique will rely on a +faster :func:`shutil.copyfile` implementation using :func:`memoryview`, +:class:`bytearray` and +:meth:`BufferedIOBase.readinto() `. +Finally, :func:`shutil.copyfile` default buffer size on Windows was increased +from 16KB to 1MB. The speedup for copying a 512MB file is about +26% on Linux, ++50% on OSX and +38% on Windows. Also, much less CPU cycles are consumed (Contributed by Giampaolo Rodola' in :issue:`25427`.) From 17bd78bdae564ce854e2c8b1de60f92d580d0668 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 9 Jun 2018 02:56:17 +0200 Subject: [PATCH 108/111] remove outdated doc --- Lib/shutil.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 0dbc7c7cd626c7..cf39800a5a1e11 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -384,9 +384,6 @@ def copy2(src, dst, *, follow_symlinks=True): If follow_symlinks is false, symlinks won't be followed. This resembles GNU's "cp -P src dst". - - On Windows this function uses CopyFileEx which preserves file's - extended attributes, metadata and security information. """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) @@ -442,9 +439,6 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, destination path as arguments. By default, copy2() is used, but any function that supports the same signature (like copy()) can be used. - On Windows this function uses CreateDirectoryEx and SetNamedSecurityInfo - to create directories, preserving source directory attributes and - security information. """ names = os.listdir(src) if ignore is not None: From b1d4917b25846da2d85762ed5487d431ba8d596c Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sat, 9 Jun 2018 03:34:06 +0200 Subject: [PATCH 109/111] remove last CopyFileEx remnants --- Lib/shutil.py | 13 +++++-------- Lib/test/test_shutil.py | 16 +++++----------- Modules/_winapi.c | 2 -- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index cf39800a5a1e11..6c3c4803205c7b 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -49,9 +49,7 @@ elif os.name == 'nt': import nt -# On Windows using 1MB instead of 16KB results in significantly better -# performances. -_COPY_BUFSIZE = 1024 * 1024 if os.name == 'nt' else 16 * 1024 +COPY_BUFSIZE = 1024 * 1024 if os.name == 'nt' else 16 * 1024 _HAS_SENDFILE = posix and hasattr(os, "sendfile") _HAS_COPYFILE = posix and hasattr(posix, "_copyfile") # OSX @@ -92,7 +90,7 @@ class _GiveupOnFastCopy(Exception): def _fastcopy_osx(src, dst, flags): """Copy a regular file content or metadata by using high-performance - copyfile(3) syscall (OSX only). + copyfile(3) syscall (OSX). """ if _samefile(src, dst): # ...or else copyfile() would return success and delete src @@ -167,8 +165,8 @@ def _fastcopy_sendfile(fsrc, fdst): break # EOF offset += sent -def _copybinfileobj(fsrc, fdst, length=_COPY_BUFSIZE): - """Copy 2 regular file objects opened in binary mode.""" +def _copybinfileobj(fsrc, fdst, length=COPY_BUFSIZE): + """Copy 2 regular file objects open in binary mode.""" # Localize variable access to minimize overhead. fsrc_readinto = fsrc.readinto fdst_write = fdst.write @@ -187,7 +185,7 @@ def _is_binary_files_pair(fsrc, fdst): isinstance(fsrc, io.BytesIO) or 'b' in getattr(fsrc, 'mode', '') and \ isinstance(fdst, io.BytesIO) or 'b' in getattr(fdst, 'mode', '') -def copyfileobj(fsrc, fdst, length=_COPY_BUFSIZE): +def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE): """copy data from file-like object fsrc to file-like object fdst""" if _is_binary_files_pair(fsrc, fdst): _copybinfileobj(fsrc, fdst, length=length) @@ -387,7 +385,6 @@ def copy2(src, dst, *, follow_symlinks=True): """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) - copyfile(src, dst, follow_symlinks=follow_symlinks) copystat(src, dst, follow_symlinks=follow_symlinks) return dst diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 245ddca5949c28..70b9a82b75230d 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -34,7 +34,6 @@ TESTFN2 = TESTFN + "2" OSX = sys.platform.startswith("darwin") -WINDOWS = os.name == 'nt' try: import grp import pwd @@ -1936,9 +1935,8 @@ def test_regular_copy(self): self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) # Make sure the fallback function is not called. with self.get_files() as (src, dst): - fun = shutil.copy2 if WINDOWS else shutil.copyfile with unittest.mock.patch('shutil.copyfileobj') as m: - fun(TESTFN, TESTFN2) + shutil.copyfile(TESTFN, TESTFN2) assert not m.called def test_same_file(self): @@ -1951,11 +1949,10 @@ def test_same_file(self): def test_non_existent_src(self): name = tempfile.mktemp() - fun = shutil.copy2 if WINDOWS else shutil.copyfile with self.assertRaises(FileNotFoundError) as cm: - fun(name, "new") + shutil.copyfile(name, "new") self.assertEqual(cm.exception.filename, name) - if OSX or WINDOWS: + if OSX: self.assertEqual(cm.exception.filename2, "new") def test_empty_file(self): @@ -1975,10 +1972,9 @@ def test_empty_file(self): def test_unhandled_exception(self): with unittest.mock.patch(self.PATCHPOINT, side_effect=ZeroDivisionError): - fun = shutil.copy2 if WINDOWS else shutil.copyfile - self.assertRaises(ZeroDivisionError, fun, TESTFN, TESTFN2) + self.assertRaises(ZeroDivisionError, + shutil.copyfile, TESTFN, TESTFN2) - @unittest.skipIf(WINDOWS, 'POSIX only') def test_exception_on_first_call(self): # Emulate a case where the first call to the zero-copy # function raises an exception in which case the function is @@ -2005,7 +2001,6 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): def zerocopy_fun(self, fsrc, fdst): return shutil._fastcopy_sendfile(fsrc, fdst) - @unittest.skipIf(WINDOWS, 'POSIX only') def test_non_regular_file_src(self): with io.BytesIO(self.FILEDATA) as src: with open(TESTFN2, "wb") as dst: @@ -2015,7 +2010,6 @@ def test_non_regular_file_src(self): self.assertEqual(read_file(TESTFN2, binary=True), self.FILEDATA) - @unittest.skipIf(WINDOWS, 'POSIX only') def test_non_regular_file_dst(self): with open(TESTFN, "rb") as src: with io.BytesIO() as dst: diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 7b26f1973ba50b..75d1f0678effe6 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -41,7 +41,6 @@ #include "windows.h" #include #include "winreparse.h" -#include #if defined(MS_WIN32) && !defined(MS_WIN64) #define HANDLE_TO_PYNUM(handle) \ @@ -1781,7 +1780,6 @@ PyInit__winapi(void) WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_BUSY); WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_CONNECTED); WINAPI_CONSTANT(F_DWORD, ERROR_SEM_TIMEOUT); - WINAPI_CONSTANT(F_DWORD, ERROR_SHARING_VIOLATION); WINAPI_CONSTANT(F_DWORD, FILE_FLAG_FIRST_PIPE_INSTANCE); WINAPI_CONSTANT(F_DWORD, FILE_FLAG_OVERLAPPED); WINAPI_CONSTANT(F_DWORD, FILE_GENERIC_READ); From 5ce94e4e74237e8d79e5416c26128ee678e7a929 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 12 Jun 2018 16:15:06 +0200 Subject: [PATCH 110/111] OSX - use fcopyfile(3) instead of copyfile(3) ...as an extra safety measure: in case src/dst are "exotic" files (non regular or living on a network fs etc.) we better fail on open() instead of copyfile(3) as we're not quite sure what's gonna happen in that case. --- Lib/shutil.py | 33 ++++++++++++++++-------------- Lib/test/test_shutil.py | 6 ++---- Modules/clinic/posixmodule.c.h | 37 +++++++++++++++------------------- Modules/posixmodule.c | 18 ++++++++--------- 4 files changed, 45 insertions(+), 49 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 6c3c4803205c7b..c1f61b21bb06d8 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -51,7 +51,7 @@ COPY_BUFSIZE = 1024 * 1024 if os.name == 'nt' else 16 * 1024 _HAS_SENDFILE = posix and hasattr(os, "sendfile") -_HAS_COPYFILE = posix and hasattr(posix, "_copyfile") # OSX +_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # OSX __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", "copytree", "move", "rmtree", "Error", "SpecialFileError", @@ -88,18 +88,21 @@ class _GiveupOnFastCopy(Exception): file copy when fast-copy functions fail to do so. """ -def _fastcopy_osx(src, dst, flags): +def _fastcopy_osx(fsrc, fdst, flags): """Copy a regular file content or metadata by using high-performance copyfile(3) syscall (OSX). """ - if _samefile(src, dst): - # ...or else copyfile() would return success and delete src - # file content. This is stupid as it forces us to do 2 extra - # stat() calls. - raise SameFileError("{!r} and {!r} are the same file".format(src, dst)) try: - posix._copyfile(src, dst, flags) + infd = fsrc.fileno() + outfd = fdst.fileno() + except Exception as err: + raise _GiveupOnFastCopy(err) # not a regular file + + try: + posix._fcopyfile(infd, outfd, flags) except OSError as err: + err.filename = fsrc.name + err.filename2 = fdst.name if err.errno in {errno.EINVAL, errno.ENOTSUP}: raise _GiveupOnFastCopy(err) else: @@ -235,13 +238,6 @@ def copyfile(src, dst, *, follow_symlinks=True): if not follow_symlinks and os.path.islink(src): os.symlink(os.readlink(src), dst) else: - if _HAS_COPYFILE: - try: - _fastcopy_osx(src, dst, posix._COPYFILE_DATA) - return dst - except _GiveupOnFastCopy: - pass - with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst: if _HAS_SENDFILE: try: @@ -250,6 +246,13 @@ def copyfile(src, dst, *, follow_symlinks=True): except _GiveupOnFastCopy: pass + if _HAS_FCOPYFILE: + try: + _fastcopy_osx(fsrc, fdst, posix._COPYFILE_DATA) + return dst + except _GiveupOnFastCopy: + pass + _copybinfileobj(fsrc, fdst) return dst diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 70b9a82b75230d..8d519944191084 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1952,8 +1952,6 @@ def test_non_existent_src(self): with self.assertRaises(FileNotFoundError) as cm: shutil.copyfile(name, "new") self.assertEqual(cm.exception.filename, name) - if OSX: - self.assertEqual(cm.exception.filename2, "new") def test_empty_file(self): srcname = TESTFN + 'src' @@ -2115,10 +2113,10 @@ def test_file2file_not_supported(self): @unittest.skipIf(not OSX, 'OSX only') class TestZeroCopyOSX(_ZeroCopyFileTest, unittest.TestCase): - PATCHPOINT = "posix._copyfile" + PATCHPOINT = "posix._fcopyfile" def zerocopy_fun(self, src, dst): - return shutil._fastcopy_osx(src.name, dst.name, posix._COPYFILE_DATA) + return shutil._fastcopy_osx(src, dst, posix._COPYFILE_DATA) class TermsizeTests(unittest.TestCase): diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 56f0129b04ad72..c41d1314037831 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3855,38 +3855,33 @@ os_write(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #if defined(__APPLE__) -PyDoc_STRVAR(os__copyfile__doc__, -"_copyfile($module, src, dst, flags, /)\n" +PyDoc_STRVAR(os__fcopyfile__doc__, +"_fcopyfile($module, infd, outfd, flags, /)\n" "--\n" "\n" -"Efficiently copy the content of 2 file descriptors (OSX only)."); +"Efficiently copy content or metadata of 2 regular file descriptors (OSX)."); -#define OS__COPYFILE_METHODDEF \ - {"_copyfile", (PyCFunction)os__copyfile, METH_FASTCALL, os__copyfile__doc__}, +#define OS__FCOPYFILE_METHODDEF \ + {"_fcopyfile", (PyCFunction)os__fcopyfile, METH_FASTCALL, os__fcopyfile__doc__}, static PyObject * -os__copyfile_impl(PyObject *module, path_t *src, path_t *dst, int flags); +os__fcopyfile_impl(PyObject *module, int infd, int outfd, int flags); static PyObject * -os__copyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - path_t src = PATH_T_INITIALIZE("_copyfile", "src", 0, 0); - path_t dst = PATH_T_INITIALIZE("_copyfile", "dst", 0, 0); + int infd; + int outfd; int flags; - if (!_PyArg_ParseStack(args, nargs, "O&O&i:_copyfile", - path_converter, &src, path_converter, &dst, &flags)) { + if (!_PyArg_ParseStack(args, nargs, "iii:_fcopyfile", + &infd, &outfd, &flags)) { goto exit; } - return_value = os__copyfile_impl(module, &src, &dst, flags); + return_value = os__fcopyfile_impl(module, infd, outfd, flags); exit: - /* Cleanup for src */ - path_cleanup(&src); - /* Cleanup for dst */ - path_cleanup(&dst); - return return_value; } @@ -6453,9 +6448,9 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #define OS_PREADV_METHODDEF #endif /* !defined(OS_PREADV_METHODDEF) */ -#ifndef OS__COPYFILE_METHODDEF - #define OS__COPYFILE_METHODDEF -#endif /* !defined(OS__COPYFILE_METHODDEF) */ +#ifndef OS__FCOPYFILE_METHODDEF + #define OS__FCOPYFILE_METHODDEF +#endif /* !defined(OS__FCOPYFILE_METHODDEF) */ #ifndef OS_PIPE_METHODDEF #define OS_PIPE_METHODDEF @@ -6632,4 +6627,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=d245a974d050df80 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b5d1ec71bc6f0651 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 437bfe31dcf4bf..bdf576f1388e06 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8751,27 +8751,27 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) #if defined(__APPLE__) /*[clinic input] -os._copyfile +os._fcopyfile - src: path_t - dst: path_t + infd: int + outfd: int flags: int / -Efficiently copy the content of 2 file descriptors (OSX only). +Efficiently copy content or metadata of 2 regular file descriptors (OSX). [clinic start generated code]*/ static PyObject * -os__copyfile_impl(PyObject *module, path_t *src, path_t *dst, int flags) -/*[clinic end generated code: output=d9f64f7425e4174b input=7e0bf8e81908f871]*/ +os__fcopyfile_impl(PyObject *module, int infd, int outfd, int flags) +/*[clinic end generated code: output=8e8885c721ec38e3 input=aeb9456804eec879]*/ { int ret; Py_BEGIN_ALLOW_THREADS - ret = copyfile(src->narrow, dst->narrow, NULL, flags); + ret = fcopyfile(infd, outfd, NULL, flags); Py_END_ALLOW_THREADS if (ret < 0) - return path_error2(src, dst); + return posix_error(); Py_RETURN_NONE; } #endif @@ -12953,7 +12953,7 @@ static PyMethodDef posix_methods[] = { OS_UTIME_METHODDEF OS_TIMES_METHODDEF OS__EXIT_METHODDEF - OS__COPYFILE_METHODDEF + OS__FCOPYFILE_METHODDEF OS_EXECV_METHODDEF OS_EXECVE_METHODDEF OS_SPAWNV_METHODDEF From 07bcef5df6766454980f83c4668d0f2bbfece891 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 12 Jun 2018 16:29:33 +0200 Subject: [PATCH 111/111] update doc --- Doc/library/shutil.rst | 4 ++-- Lib/shutil.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index c2a35d6bcbe706..a3b87ee61a3d33 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -407,7 +407,7 @@ efficiently (see :issue:`33671`). "fast-copy" means that the copying operation occurs within the kernel, avoiding the use of userspace buffers in Python as in "``outfd.write(infd.read())``". -On OSX `copyfile`_ is used to copy the file content (not metadata). +On OSX `fcopyfile`_ is used to copy the file content (not metadata). On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports copies between 2 regular file descriptors :func:`os.sendfile` is used. @@ -701,7 +701,7 @@ Querying the size of the output terminal .. versionadded:: 3.3 -.. _`copyfile`: +.. _`fcopyfile`: http://www.manpagez.com/man/3/copyfile/ .. _`Other Environment Variables`: diff --git a/Lib/shutil.py b/Lib/shutil.py index c1f61b21bb06d8..09a5727ab4645f 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -90,7 +90,7 @@ class _GiveupOnFastCopy(Exception): def _fastcopy_osx(fsrc, fdst, flags): """Copy a regular file content or metadata by using high-performance - copyfile(3) syscall (OSX). + fcopyfile(3) syscall (OSX). """ try: infd = fsrc.fileno()