summaryrefslogtreecommitdiffstats
path: root/third_party/python/dlmanager/dlmanager/fs.py
blob: 8908b5efce2d97a0fc867667516b0ab7ad81f5be (plain)
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))