4343import shutil as _shutil
4444import errno as _errno
4545from random import Random as _Random
46+ import stat as _stat
4647import sys as _sys
4748import types as _types
4849import weakref as _weakref
@@ -276,15 +277,68 @@ def _dont_follow_symlinks(func, path, *args):
276277 elif _os .name == 'nt' or not _os .path .islink (path ):
277278 func (path , * args )
278279
279- def _resetperms (path ):
280+ def _resetflags (path ):
280281 try :
281282 chflags = _os .chflags
282283 except AttributeError :
283284 pass
284285 else :
285286 _dont_follow_symlinks (chflags , path , 0 )
287+
288+ def _resetperms (path ):
289+ _resetflags (path )
286290 _dont_follow_symlinks (_os .chmod , path , 0o700 )
287291
292+ # True if TemporaryDirectory._rmtree() can work relative to open directories
293+ # instead of resolving paths again.
294+ _rmtree_use_dir_fd = (
295+ {_os .chmod , _os .unlink , _os .lstat } <= _os .supports_dir_fd
296+ and _os .chmod in _os .supports_fd
297+ )
298+
299+ def _resetperms_fd (dir_fd , path ):
300+ # Same as _resetperms(), but for the directory referred to by dir_fd.
301+ if dir_fd is None :
302+ _resetperms (path )
303+ return
304+ _resetflags (path )
305+ _os .chmod (dir_fd , 0o700 )
306+
307+ try :
308+ _nofollow_mode = _os .O_RDONLY | _os .O_NONBLOCK | _os .O_NOFOLLOW
309+ except AttributeError :
310+ _nofollow_mode = None
311+
312+ def _resetperms_at (name , dir_fd , path ):
313+ # Same as _resetperms(), but name is resolved relative to the directory
314+ # file descriptor dir_fd. path is only used for os.chflags(), which
315+ # doesn't support dir_fd or file descriptors.
316+ if dir_fd is None :
317+ _resetperms (path )
318+ return
319+ _resetflags (path )
320+ if _os .chmod in _os .supports_follow_symlinks :
321+ _os .chmod (name , 0o700 , dir_fd = dir_fd , follow_symlinks = False )
322+ else :
323+ # dir_fd & follow_symlinks is not supported on this platform.
324+ # Try chmod opening the file with O_NOFOLLOW.
325+ if _nofollow_mode is not None :
326+ try :
327+ fd = _os .open (name , _nofollow_mode , dir_fd = dir_fd )
328+ except OSError :
329+ pass
330+ else :
331+ try :
332+ _os .chmod (fd , 0o700 )
333+ finally :
334+ _os .close (fd )
335+ return
336+ # If that did not work, we change by name, which is subject to a race
337+ # condition.
338+ stat = _os .lstat (name , dir_fd = dir_fd )
339+ if not _stat .S_ISLNK (stat .st_mode ):
340+ _os .chmod (name , 0o700 , dir_fd = dir_fd )
341+
288342
289343# User visible interfaces.
290344
@@ -892,23 +946,42 @@ def __init__(self, suffix=None, prefix=None, dir=None,
892946 ignore_errors = self ._ignore_cleanup_errors , delete = self ._delete )
893947
894948 @classmethod
895- def _rmtree (cls , name , ignore_errors = False , repeated = False ):
896- def onexc (func , path , exc ):
949+ def _rmtree (cls , name , ignore_errors = False , repeated = False , dir_fd = None ,
950+ fullname = None ):
951+ if fullname is None :
952+ fullname = name
953+
954+ def onexc (func , path , exc , direntry = None , dir_fd = None ):
897955 if isinstance (exc , PermissionError ):
898956 if repeated and path == name :
899957 if ignore_errors :
900958 return
901959 raise
902960
961+ # fullpath is path as seen from the working directory
962+ fullpath = fullname + path [len (name ):]
963+ # base is path relative to dir_fd, the directory rmtree()
964+ # reached it through, or the whole path when there is none
965+ if dir_fd is None or not _rmtree_use_dir_fd :
966+ base , dir_fd = path , None
967+ elif direntry is None :
968+ base = path
969+ else :
970+ base = direntry .name
971+
903972 try :
904973 if path != name :
905- _resetperms (_os .path .dirname (path ))
906- _resetperms (path )
974+ # The parent directory of path is the one referred to
975+ # by dir_fd.
976+ _resetperms_fd (dir_fd , _os .path .dirname (fullpath ))
977+ _resetperms_at (base , dir_fd , fullpath )
907978
908979 try :
909- _os .unlink (path )
980+ _os .unlink (base , dir_fd = dir_fd )
910981 except IsADirectoryError :
911- cls ._rmtree (path , ignore_errors = ignore_errors )
982+ cls ._rmtree (base , ignore_errors = ignore_errors ,
983+ repeated = (path == name ),
984+ dir_fd = dir_fd , fullname = fullpath )
912985 except PermissionError :
913986 # The PermissionError handler was originally added for
914987 # FreeBSD in directories, but it seems that it is raised
@@ -917,21 +990,27 @@ def onexc(func, path, exc):
917990 # raise NotADirectoryError and mask the PermissionError.
918991 # So we must re-raise the current PermissionError if
919992 # path is not a directory.
920- if not _os .path .isdir (path ) or _os .path .isjunction (path ):
993+ if (not _os .path .isdir (fullpath )
994+ or _os .path .isjunction (fullpath )):
921995 if ignore_errors :
922996 return
923997 raise
924- cls ._rmtree (path , ignore_errors = ignore_errors ,
925- repeated = (path == name ))
998+ cls ._rmtree (base , ignore_errors = ignore_errors ,
999+ repeated = (path == name ),
1000+ dir_fd = dir_fd , fullname = fullpath )
9261001 except FileNotFoundError :
9271002 pass
1003+ except OSError :
1004+ if ignore_errors :
1005+ return
1006+ raise
9281007 elif isinstance (exc , FileNotFoundError ):
9291008 pass
9301009 else :
9311010 if not ignore_errors :
9321011 raise
9331012
934- _shutil .rmtree (name , onexc = onexc )
1013+ _shutil .rmtree (name , onexc = onexc , dir_fd = dir_fd , _onexc_kwargs = True )
9351014
9361015 @classmethod
9371016 def _cleanup (cls , name , warn_message , ignore_errors = False , delete = True ):
0 commit comments