55"""
66
77import os
8- import fcntl
98import sys
109import stat
1110import fnmatch
1211import collections
1312import errno
13+ try :
14+ import fcntl
15+ except ImportError :
16+ fcntl = None
1417
1518try :
1619 import zlib
@@ -84,6 +87,11 @@ class SpecialFileError(OSError):
8487 not supported on a special file (e.g. a named pipe)"""
8588
8689
90+ class MissingFeatureError (Error ):
91+ """Raised when a platform does not have all of the features it needs to
92+ perform an operation."""
93+
94+
8795class ReadError (OSError ):
8896 """Raised when an archive cannot be read"""
8997
@@ -299,6 +307,8 @@ def copyfile(src, dst, *, follow_symlinks=True):
299307 raise SameFileError ("{!r} and {!r} are the same file" .format (src , dst ))
300308
301309 file_size = 0
310+
311+ ### Move this into the opener?
302312 for i , fn in enumerate ([src , dst ]):
303313 try :
304314 st = _stat (fn )
@@ -322,6 +332,17 @@ def copyfile(src, dst, *, follow_symlinks=True):
322332 # In Unix, we must open the files non-blocking initially as opening a
323333 # FIFO without data present in it will block.
324334 if os .name == 'posix' :
335+ # We will need these features in order to set the filehandle back to
336+ # blocking mode later.
337+ if not fcntl or not hasattr (fcntl , 'F_SETFL' ) or not hasattr (fcntl , 'F_GETFL' ):
338+ # Which exception to raise?
339+ # The needed module or feature does not exist
340+ # We would be unable to set the file to non-blocking later
341+ # Cannot reset the file handle to defaults
342+ raise MissingFeatureError ("Cannot use copyfiles on systems that support"
343+ " socket files unless `fcntl`, `fcntl.F_SETFL`,"
344+ " and `fcntl.F_GETFL` are available." )
345+
325346 src_flags = os .O_RDONLY | os .O_NONBLOCK
326347 ### Do we need os.O_EXCL?
327348 dst_flags = os .O_CREAT | os .O_WRONLY | os .O_NONBLOCK
@@ -330,7 +351,7 @@ def copyfile(src, dst, *, follow_symlinks=True):
330351 dst_flags = os .O_WRONLY
331352
332353 def _src_opener (path , flags ):
333- return os .open (path , flags )
354+ return os .open (path , flags | src_flags )
334355
335356 # dst's opener is more complex. We need to atomically check and open the
336357 # destination if it doesn't exist. If it does exist, we need to detect so
@@ -344,8 +365,10 @@ def _dst_opener(path, flags):
344365 except FileExistsError :
345366 dst_was_created = False
346367 try :
347- # Open the already existing file in as non-blocking
348- ### We have O_CREATE and O_NONBLOCK in dst_flags. Is that intended?
368+ # Open the already existing file as non-blocking
369+ ### We have O_CREAT and O_NONBLOCK in dst_flags. Even though
370+ ### we know that we are not creating the file at this point. Is
371+ ### that intended?
349372 fd = os .open (path , flags | dst_flags )
350373 # If the destination is already a FIFO for some reason, we'll get an
351374 # OSError here as we've opened a FIFO for a non-blocking write with
@@ -379,7 +402,7 @@ def _dst_opener(path, flags):
379402 if dst_was_created :
380403 os .unlink (dst )
381404 raise SpecialFileError ("`%s` is a named pipe" % src )
382- if _WINDOWS and i == 0 :
405+ if _WINDOWS :
383406 file_size = st .st_size
384407
385408 # In Unix, we must set the file descriptors back to blocking as that's
0 commit comments