Skip to content

Commit 582968a

Browse files
Make socket and devices return SpecialFileError
1 parent f893e8f commit 582968a

File tree

4 files changed

+62
-9
lines changed

4 files changed

+62
-9
lines changed

Doc/library/shutil.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ Directory and files operations
6767

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

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

93+
.. versionchanged:: 3.15
94+
:exc:`SpecialFileError` is now also raised for sockets and device files.
95+
9396
.. exception:: SpecialFileError
9497

9598
This exception is raised when :func:`copyfile` or :func:`copytree` attempt
96-
to copy a named pipe.
99+
to copy a named pipe, socket, or device file.
97100

98101
.. versionadded:: 2.7
99102

Doc/whatsnew/3.15.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,14 @@ shelve
630630
(Contributed by Andrea Oliveri in :gh:`134004`.)
631631

632632

633+
shutil
634+
------
635+
636+
* :func:`shutil.copyfile` now also raises :exc:`~shutil.SpecialFileError` for
637+
sockets and device files.
638+
(Contributed by Savannah Ostrowski in :gh:`81881`.)
639+
640+
633641
socket
634642
------
635643

Lib/shutil.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,18 @@ def copyfile(src, dst, *, follow_symlinks=True):
300300
# File most likely does not exist
301301
pass
302302
else:
303-
# XXX What about other special files? (sockets, devices...)
304303
if stat.S_ISFIFO(st.st_mode):
305304
fn = fn.path if isinstance(fn, os.DirEntry) else fn
306305
raise SpecialFileError("`%s` is a named pipe" % fn)
306+
elif stat.S_ISSOCK(st.st_mode):
307+
fn = fn.path if isinstance(fn, os.DirEntry) else fn
308+
raise SpecialFileError("`%s` is a socket" % fn)
309+
elif stat.S_ISBLK(st.st_mode):
310+
fn = fn.path if isinstance(fn, os.DirEntry) else fn
311+
raise SpecialFileError("`%s` is a block device" % fn)
312+
elif stat.S_ISCHR(st.st_mode):
313+
fn = fn.path if isinstance(fn, os.DirEntry) else fn
314+
raise SpecialFileError("`%s` is a character device" % fn)
307315
if _WINDOWS and i == 0:
308316
file_size = st.st_size
309317

Lib/test/test_shutil.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import os.path
1111
import errno
1212
import functools
13+
import socket
1314
import subprocess
1415
import random
1516
import string
@@ -29,7 +30,7 @@
2930
posix = None
3031

3132
from test import support
32-
from test.support import os_helper
33+
from test.support import os_helper, socket_helper
3334
from test.support.os_helper import TESTFN, FakePath
3435

3536
TESTFN2 = TESTFN + "2"
@@ -1550,13 +1551,46 @@ def test_copyfile_named_pipe(self):
15501551
except PermissionError as e:
15511552
self.skipTest('os.mkfifo(): %s' % e)
15521553
try:
1553-
self.assertRaises(shutil.SpecialFileError,
1554-
shutil.copyfile, TESTFN, TESTFN2)
1555-
self.assertRaises(shutil.SpecialFileError,
1556-
shutil.copyfile, __file__, TESTFN)
1554+
self.assertRaisesRegex(shutil.SpecialFileError, 'is a named pipe',
1555+
shutil.copyfile, TESTFN, TESTFN2)
1556+
self.assertRaisesRegex(shutil.SpecialFileError, 'is a named pipe',
1557+
shutil.copyfile, __file__, TESTFN)
15571558
finally:
15581559
os.remove(TESTFN)
15591560

1561+
@socket_helper.skip_unless_bind_unix_socket
1562+
def test_copyfile_socket(self):
1563+
sock_path = os.path.join(self.mkdtemp(), 'sock')
1564+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1565+
self.addCleanup(sock.close)
1566+
socket_helper.bind_unix_socket(sock, sock_path)
1567+
self.addCleanup(os_helper.unlink, sock_path)
1568+
self.assertRaisesRegex(shutil.SpecialFileError, 'is a socket',
1569+
shutil.copyfile, sock_path, sock_path + '.copy')
1570+
self.assertRaisesRegex(shutil.SpecialFileError, 'is a socket',
1571+
shutil.copyfile, __file__, sock_path)
1572+
1573+
@unittest.skipIf(os.name == 'nt', 'requires /dev/null')
1574+
def test_copyfile_character_device(self):
1575+
self.assertRaisesRegex(shutil.SpecialFileError, 'is a character device',
1576+
shutil.copyfile, '/dev/null', TESTFN)
1577+
src_file = os.path.join(self.mkdtemp(), 'src')
1578+
create_file(src_file, 'foo')
1579+
self.assertRaisesRegex(shutil.SpecialFileError, 'is a character device',
1580+
shutil.copyfile, src_file, '/dev/null')
1581+
1582+
def test_copyfile_block_device(self):
1583+
block_dev = None
1584+
for dev in ['/dev/loop0', '/dev/sda', '/dev/vda', '/dev/disk0']:
1585+
if os.path.exists(dev) and stat.S_ISBLK(os.stat(dev).st_mode):
1586+
if os.access(dev, os.R_OK):
1587+
block_dev = dev
1588+
break
1589+
if block_dev is None:
1590+
self.skipTest('no accessible block device found')
1591+
self.assertRaisesRegex(shutil.SpecialFileError, 'is a block device',
1592+
shutil.copyfile, block_dev, TESTFN)
1593+
15601594
def test_copyfile_return_value(self):
15611595
# copytree returns its destination path.
15621596
src_dir = self.mkdtemp()

0 commit comments

Comments
 (0)