1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
import errno
import logging
import os
import shutil
import stat
import time
"""
File system utilities, copied from mozfile.
"""
LOG = logging.getLogger(__name__)
def _call_windows_retry(func, args=(), retry_max=5, retry_delay=0.5):
"""
It's possible to see spurious errors on Windows due to various things
keeping a handle to the directory open (explorer, virus scanners, etc)
So we try a few times if it fails with a known error.
"""
retry_count = 0
while True:
try:
func(*args)
except OSError as e:
# Error codes are defined in:
# http://docs.python.org/2/library/errno.html#module-errno
if e.errno not in (errno.EACCES, errno.ENOTEMPTY):
raise
if retry_count == retry_max:
raise
retry_count += 1
LOG.info('%s() failed for "%s". Reason: %s (%s). Retrying...',
func.__name__, args, e.strerror, e.errno)
time.sleep(retry_delay)
else:
# If no exception has been thrown it should be done
break
def remove(path):
"""Removes the specified file, link, or directory tree.
This is a replacement for shutil.rmtree that works better under
windows. It does the following things:
- check path access for the current user before trying to remove
- retry operations on some known errors due to various things keeping
a handle on file paths - like explorer, virus scanners, etc. The
known errors are errno.EACCES and errno.ENOTEMPTY, and it will
retry up to 5 five times with a delay of 0.5 seconds between each
attempt.
Note that no error will be raised if the given path does not exists.
:param path: path to be removed
"""
def _call_with_windows_retry(*args, **kwargs):
try:
_call_windows_retry(*args, **kwargs)
except OSError as e:
# The file or directory to be removed doesn't exist anymore
if e.errno != errno.ENOENT:
raise
def _update_permissions(path):
"""Sets specified pemissions depending on filetype"""
if os.path.islink(path):
# Path is a symlink which we don't have to modify
# because it should already have all the needed permissions
return
stats = os.stat(path)
if os.path.isfile(path):
mode = stats.st_mode | stat.S_IWUSR
elif os.path.isdir(path):
mode = stats.st_mode | stat.S_IWUSR | stat.S_IXUSR
else:
# Not supported type
return
_call_with_windows_retry(os.chmod, (path, mode))
if not os.path.exists(path):
return
if os.path.isfile(path) or os.path.islink(path):
# Verify the file or link is read/write for the current user
_update_permissions(path)
_call_with_windows_retry(os.remove, (path,))
elif os.path.isdir(path):
# Verify the directory is read/write/execute for the current user
_update_permissions(path)
# We're ensuring that every nested item has writable permission.
for root, dirs, files in os.walk(path):
for entry in dirs + files:
_update_permissions(os.path.join(root, entry))
_call_with_windows_retry(shutil.rmtree, (path,))
def move(src, dst):
"""
Move a file or directory path.
This is a replacement for shutil.move that works better under windows,
retrying operations on some known errors due to various things keeping
a handle on file paths.
"""
_call_windows_retry(shutil.move, (src, dst))
|