From b0011a1ce35a42a5412c876a74787dba3f7270c7 Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Thu, 3 Oct 2019 17:38:43 -0700 Subject: [PATCH 1/5] bpo-37700: Raise a shutil.SpecialFileError when copying a Unix socket This used to raise an OSError with a platform dependent message. This change always raises a SpecialFileError with a consistent message no matter the platform. --- Doc/library/shutil.rst | 8 ++++ Lib/shutil.py | 5 ++- Lib/test/test_shutil.py | 38 +++++++++++++++++++ .../2019-10-03-17-26-27.bpo-37700.pec3ne.rst | 3 ++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 59390d0e907eb9e..f2d4df8138b6e8a 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -54,6 +54,9 @@ Directory and files operations *dst* and return *dst* in the most efficient way possible. *src* and *dst* are path-like objects or path names given as strings. + When *src* is a named pipe or a Unix socket, a :exc:`SpecialFileError` + is raised. + *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. @@ -81,6 +84,11 @@ Directory and files operations copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. + .. versionchanged:: 3.9 + Raise :exc:`SpecialFileError` instead of :exc:`OSError` when copying a + Unix socket. Since the former is a subclass of the latter, this change is + backward compatible. + .. exception:: SameFileError This exception is raised if source and destination in :func:`copyfile` diff --git a/Lib/shutil.py b/Lib/shutil.py index f97de788d9d406d..af75c4810564fde 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -246,10 +246,13 @@ def copyfile(src, dst, *, follow_symlinks=True): # File most likely does not exist pass else: - # XXX What about other special files? (sockets, devices...) + # XXX What about other special files? (devices...) if stat.S_ISFIFO(st.st_mode): fn = fn.path if isinstance(fn, os.DirEntry) else fn raise SpecialFileError("`%s` is a named pipe" % fn) + elif stat.S_ISSOCK(st.st_mode): + fn = fn.path if isinstance(fn, os.DirEntry) else fn + raise SpecialFileError("`%s` is a socket" % fn) if _WINDOWS and i == 0: file_size = st.st_size diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 428d4f341715c69..9a2d8e2f12a57a3 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -13,6 +13,7 @@ import pathlib import subprocess import random +import socket import string import contextlib import io @@ -663,6 +664,29 @@ def test_copytree_named_pipe(self): shutil.rmtree(TESTFN, ignore_errors=True) shutil.rmtree(TESTFN2, ignore_errors=True) + @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'requires socket.AF_UNIX') + def test_copytree_socket(self): + os.mkdir(TESTFN) + self.addCleanup(shutil.rmtree, TESTFN, ignore_errors=True) + sock_path = tempfile.mktemp(dir=TESTFN) + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(sock.close) + support.bind_unix_socket(sock, sock_path) + self.addCleanup(support.unlink, sock_path) + + try: + shutil.copytree(TESTFN, TESTFN2) + except shutil.Error as e: + errors = e.args[0] + self.assertEqual(len(errors), 1) + src, dst, error_msg = errors[0] + self.assertEqual("`%s` is a socket" % sock_path, error_msg) + else: + self.fail("shutil.Error should have been raised") + finally: + shutil.rmtree(TESTFN2, ignore_errors=True) + def test_copytree_special_func(self): src_dir = self.mkdtemp() dst_dir = os.path.join(self.mkdtemp(), 'destination') @@ -1165,6 +1189,20 @@ def test_copyfile_named_pipe(self): finally: os.remove(TESTFN) + @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'requires socket.AF_UNIX') + def test_copyfile_socket(self): + tmp_dir = self.mkdtemp() + src = tempfile.mktemp(dir=tmp_dir) + dst = os.path.join(tmp_dir, 'dst') + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.addCleanup(sock.close) + support.bind_unix_socket(sock, src) + self.addCleanup(support.unlink, src) + + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, src, dst) + def test_copyfile_return_value(self): # copytree returns its destination path. src_dir = self.mkdtemp() diff --git a/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst b/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst new file mode 100644 index 000000000000000..d096d2b69b47cf2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst @@ -0,0 +1,3 @@ +Fix raising a different exception depending on platform when copying a Unix +socket using :func:`shutil.copyfile` by always raising a +:exc:`shutil.SpecialFileError` instead. From 5fd5dc6009b3e2a009667f590cfedb825f58c34f Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Fri, 4 Oct 2019 09:25:59 -0700 Subject: [PATCH 2/5] fixup! bpo-37700: Raise a shutil.SpecialFileError when copying a Unix socket --- .../next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst b/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst index d096d2b69b47cf2..c50fa7b2271d4eb 100644 --- a/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst +++ b/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst @@ -1,3 +1,2 @@ -Fix raising a different exception depending on platform when copying a Unix -socket using :func:`shutil.copyfile` by always raising a -:exc:`shutil.SpecialFileError` instead. +:func:`shutil.copyfile` always raises a :exc:`shutil.SpecialFileError` +when trying to copy a Unix socket. From 5c4fec998e1da4ab71ff1ae63d16d10fc7ca4650 Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Mon, 30 Dec 2019 12:32:57 -0800 Subject: [PATCH 3/5] fixup! bpo-37700: Raise a shutil.SpecialFileError when copying a Unix socket --- Doc/library/shutil.rst | 2 +- Lib/test/test_shutil.py | 3 +-- .../next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index f2d4df8138b6e8a..cc3d53a54e91d13 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -54,7 +54,7 @@ Directory and files operations *dst* and return *dst* in the most efficient way possible. *src* and *dst* are path-like objects or path names given as strings. - When *src* is a named pipe or a Unix socket, a :exc:`SpecialFileError` + When *src* is a named pipe or a Unix socket, :exc:`SpecialFileError` is raised. *dst* must be the complete target file name; look at :func:`~shutil.copy` diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 9a2d8e2f12a57a3..28881a851373f5c 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -668,6 +668,7 @@ def test_copytree_named_pipe(self): def test_copytree_socket(self): os.mkdir(TESTFN) self.addCleanup(shutil.rmtree, TESTFN, ignore_errors=True) + self.addCleanup(shutil.rmtree, TESTFN2, ignore_errors=True) sock_path = tempfile.mktemp(dir=TESTFN) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) @@ -684,8 +685,6 @@ def test_copytree_socket(self): self.assertEqual("`%s` is a socket" % sock_path, error_msg) else: self.fail("shutil.Error should have been raised") - finally: - shutil.rmtree(TESTFN2, ignore_errors=True) def test_copytree_special_func(self): src_dir = self.mkdtemp() diff --git a/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst b/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst index c50fa7b2271d4eb..dfdaa7ad574eea9 100644 --- a/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst +++ b/Misc/NEWS.d/next/Library/2019-10-03-17-26-27.bpo-37700.pec3ne.rst @@ -1,2 +1,2 @@ -:func:`shutil.copyfile` always raises a :exc:`shutil.SpecialFileError` +:func:`shutil.copyfile` always raises :exc:`shutil.SpecialFileError` when trying to copy a Unix socket. From 69c326093860865a529964f71e101278b5595c0d Mon Sep 17 00:00:00 2001 From: Oleg Iarygin Date: Fri, 31 Mar 2023 23:50:14 +0400 Subject: [PATCH 4/5] Update the versionchanged note --- Doc/library/shutil.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 2c4835ac6f1e452..944a540b381ac4f 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -86,7 +86,7 @@ Directory and files operations copy the file more efficiently. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. - .. versionchanged:: 3.9 + .. versionchanged:: 3.12 Raise :exc:`SpecialFileError` instead of :exc:`OSError` when copying a Unix socket. Since the former is a subclass of the latter, this change is backward compatible. From 15e3a5267518dfba8ce40a265363aa65fdcada03 Mon Sep 17 00:00:00 2001 From: Oleg Iarygin Date: Fri, 31 Mar 2023 23:55:11 +0400 Subject: [PATCH 5/5] Fix `GitHub Actions/Docs` warnings --- Doc/library/shutil.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 944a540b381ac4f..82a5abd657a4d66 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -87,9 +87,9 @@ Directory and files operations :ref:`shutil-platform-dependent-efficient-copy-operations` section. .. versionchanged:: 3.12 - Raise :exc:`SpecialFileError` instead of :exc:`OSError` when copying a - Unix socket. Since the former is a subclass of the latter, this change is - backward compatible. + Raise :exc:`~shutil.SpecialFileError` instead of :exc:`OSError` when + copying a Unix socket. Since the former is a subclass of the latter, this + change is backward compatible. .. exception:: SameFileError @@ -377,7 +377,7 @@ Directory and files operations If *copy_function* is given, it must be a callable that takes two arguments *src* and *dst*, and will be used to copy *src* to *dst* if :func:`os.rename` cannot be used. If the source is a directory, - :func:`copytree` is called, passing it the :func:`copy_function`. The + :func:`copytree` is called, passing it the *copy_function*. The default *copy_function* is :func:`copy2`. Using :func:`~shutil.copy` as the *copy_function* allows the move to succeed when it is not possible to also copy the metadata, at the expense of not copying any of the metadata.