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
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
# 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
import os.path
import errno
import functools
+import socket
import subprocess
import random
import string
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"
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()