Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Doc/library/shutil.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ Directory and files operations

The destination location must be writable; otherwise, an :exc:`OSError`
exception will be raised. If *dst* already exists, it will be replaced.
Special files such as character or block devices and pipes cannot be
copied with this function.
Special files such as character or block devices, pipes, and sockets cannot
be copied with this function.

If *follow_symlinks* is false and *src* is a symbolic link,
a new symbolic link will be created instead of copying the
Expand All @@ -90,10 +90,13 @@ Directory and files operations
copy the file more efficiently. See
:ref:`shutil-platform-dependent-efficient-copy-operations` section.

.. versionchanged:: 3.15
:exc:`SpecialFileError` is now also raised for sockets and device files.

.. exception:: SpecialFileError

This exception is raised when :func:`copyfile` or :func:`copytree` attempt
to copy a named pipe.
to copy a named pipe, socket, or device file.

.. versionadded:: 2.7

Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,14 @@ shelve
(Contributed by Andrea Oliveri in :gh:`134004`.)


shutil
------

* :func:`shutil.copyfile` now also raises :exc:`~shutil.SpecialFileError` for
sockets and device files.
(Contributed by Savannah Ostrowski in :gh:`142693`.)


socket
------

Expand Down
10 changes: 9 additions & 1 deletion Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,18 @@ def copyfile(src, dst, *, follow_symlinks=True):
# File most likely does not exist
pass
else:
# XXX What about other special files? (sockets, 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)
elif stat.S_ISBLK(st.st_mode):
fn = fn.path if isinstance(fn, os.DirEntry) else fn
raise SpecialFileError("`%s` is a block device" % fn)
elif stat.S_ISCHR(st.st_mode):
fn = fn.path if isinstance(fn, os.DirEntry) else fn
raise SpecialFileError("`%s` is a character device" % fn)
if _WINDOWS and i == 0:
file_size = st.st_size

Expand Down
48 changes: 43 additions & 5 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os.path
import errno
import functools
import socket
import subprocess
import random
import string
Expand All @@ -29,7 +30,7 @@
posix = None

from test import support
from test.support import os_helper
from test.support import os_helper, socket_helper
from test.support.os_helper import TESTFN, FakePath

TESTFN2 = TESTFN + "2"
Expand Down Expand Up @@ -1550,13 +1551,50 @@ def test_copyfile_named_pipe(self):
except PermissionError as e:
self.skipTest('os.mkfifo(): %s' % e)
try:
self.assertRaises(shutil.SpecialFileError,
shutil.copyfile, TESTFN, TESTFN2)
self.assertRaises(shutil.SpecialFileError,
shutil.copyfile, __file__, TESTFN)
self.assertRaisesRegex(shutil.SpecialFileError, 'is a named pipe',
shutil.copyfile, TESTFN, TESTFN2)
self.assertRaisesRegex(shutil.SpecialFileError, 'is a named pipe',
shutil.copyfile, __file__, TESTFN)
finally:
os.remove(TESTFN)

@socket_helper.skip_unless_bind_unix_socket
def test_copyfile_socket(self):
sock_path = os.path.join(self.mkdtemp(), 'sock')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.addCleanup(sock.close)
try:
socket_helper.bind_unix_socket(sock, sock_path)
except OSError as e:
# AF_UNIX path too long (e.g. on iOS)
self.skipTest(f'cannot bind AF_UNIX socket: {e}')
self.addCleanup(os_helper.unlink, sock_path)
self.assertRaisesRegex(shutil.SpecialFileError, 'is a socket',
shutil.copyfile, sock_path, sock_path + '.copy')
self.assertRaisesRegex(shutil.SpecialFileError, 'is a socket',
shutil.copyfile, __file__, sock_path)

@unittest.skipUnless(os.path.exists('/dev/null'), 'requires /dev/null')
def test_copyfile_character_device(self):
self.assertRaisesRegex(shutil.SpecialFileError, 'is a character device',
shutil.copyfile, '/dev/null', TESTFN)
src_file = os.path.join(self.mkdtemp(), 'src')
create_file(src_file, 'foo')
self.assertRaisesRegex(shutil.SpecialFileError, 'is a character device',
shutil.copyfile, src_file, '/dev/null')

def test_copyfile_block_device(self):
block_dev = None
for dev in ['/dev/loop0', '/dev/sda', '/dev/vda', '/dev/disk0']:
if os.path.exists(dev) and stat.S_ISBLK(os.stat(dev).st_mode):
if os.access(dev, os.R_OK):
block_dev = dev
break
if block_dev is None:
self.skipTest('no accessible block device found')
self.assertRaisesRegex(shutil.SpecialFileError, 'is a block device',
shutil.copyfile, block_dev, TESTFN)

def test_copyfile_return_value(self):
# copytree returns its destination path.
src_dir = self.mkdtemp()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`shutil.copyfile` now raises :exc:`~shutil.SpecialFileError` for sockets and device files.
Loading