]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/fileutil.py
import fileutil, some of which came from amdlib.util and some of which came from...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / util / fileutil.py
1 #  Copyright (c) 2000 Autonomous Zone Industries
2 #  Copyright (c) 2002-2007 Bryce "Zooko" Wilcox-O'Hearn
3 #  This file is licensed under the
4 #    GNU Lesser General Public License v2.1.
5 #    See the file COPYING or visit http://www.gnu.org/ for details.
6 # Portions snarfed out of the Python standard library.
7 # The du part is due to Jim McCoy.
8
9 """
10 Futz with files like a pro.
11 """
12
13 import exceptions, os, stat, tempfile, time
14
15 from twisted.python import log
16
17 def rename(src, dst, tries=4, basedelay=0.1):
18     """ Here is a superkludge to workaround the fact that occasionally on
19     Windows some other process (e.g. an anti-virus scanner, a local search
20     engine, etc.) is looking at your file when you want to delete or move it,
21     and hence you can't.  The horrible workaround is to sit and spin, trying
22     to delete it, for a short time and then give up.
23
24     With the default values of tries and basedelay this can block for less
25     than a second.
26
27     @param tries: number of tries -- each time after the first we wait twice
28     as long as the previous wait
29     @param basedelay: how long to wait before the second try
30     """
31     for i in range(tries-1):
32         try:
33             return os.rename(src, dst)
34         except EnvironmentError, le:
35             # XXX Tighten this to check if this is a permission denied error (possibly due to another Windows process having the file open and execute the superkludge only in this case.
36             log.msg("XXX KLUDGE Attempting to move file %s => %s; got %s; sleeping %s seconds" % (src, dst, le, basedelay,))
37             time.sleep(basedelay)
38             basedelay *= 2
39     return os.rename(src, dst) # The last try.
40
41 def remove(f, tries=4, basedelay=0.1):
42     """ Here is a superkludge to workaround the fact that occasionally on
43     Windows some other process (e.g. an anti-virus scanner, a local search
44     engine, etc.) is looking at your file when you want to delete or move it,
45     and hence you can't.  The horrible workaround is to sit and spin, trying
46     to delete it, for a short time and then give up.
47
48     With the default values of tries and basedelay this can block for less
49     than a second.
50
51     @param tries: number of tries -- each time after the first we wait twice
52     as long as the previous wait
53     @param basedelay: how long to wait before the second try
54     """
55     try:
56         os.chmod(f, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
57     except:
58         pass
59     for i in range(tries-1):
60         try:
61             return os.remove(f)
62         except EnvironmentError, le:
63             # XXX Tighten this to check if this is a permission denied error (possibly due to another Windows process having the file open and execute the superkludge only in this case.
64             if not os.path.exists(f):
65                 return
66             log.msg("XXX KLUDGE Attempting to remove file %s; got %s; sleeping %s seconds" % (f, le, basedelay,))
67             time.sleep(basedelay)
68             basedelay *= 2
69     return os.remove(f) # The last try.
70
71 class NamedTemporaryDirectory:
72     """
73     This calls tempfile.mkdtemp(), stores the name of the dir in
74     self.name, and rmrf's the dir when it gets garbage collected or
75     "shutdown()".
76     """
77     def __init__(self, cleanup=True, *args, **kwargs):
78         """ If cleanup, then the directory will be rmrf'ed when the object is shutdown. """
79         self.cleanup = cleanup
80         self.name = tempfile.mkdtemp(*args, **kwargs)
81
82     def __repr__(self):
83         return "<%s instance at %x %s>" % (self.__class__.__name__, id(self), self.name)
84
85     def __str__(self):
86         return self.__repr__()
87
88     def __del__(self):
89         try:
90             self.shutdown()
91         except:
92             import traceback
93             traceback.print_exc()
94
95     def shutdown(self):
96         if self.cleanup and hasattr(self, 'name'):
97             rm_dir(self.name)
98
99 def make_dirs(dirname, mode=0777, strictmode=False):
100     """
101     A threadsafe and idempotent version of os.makedirs().  If the dir already
102     exists, do nothing and return without raising an exception.  If this call
103     creates the dir, return without raising an exception.  If there is an
104     error that prevents creation or if the directory gets deleted after
105     make_dirs() creates it and before make_dirs() checks that it exists, raise
106     an exception.
107
108     @param strictmode if true, then make_dirs() will raise an exception if the
109         directory doesn't have the desired mode.  For example, if the
110         directory already exists, and has a different mode than the one
111         specified by the mode parameter, then if strictmode is true,
112         make_dirs() will raise an exception, else it will ignore the
113         discrepancy.
114     """
115     tx = None
116     try:
117         os.makedirs(dirname, mode)
118     except OSError, x:
119         tx = x
120
121     if not os.path.isdir(dirname):
122         if tx:
123             raise tx
124         raise exceptions.IOError, "unknown error prevented creation of directory, or deleted the directory immediately after creation: %s" % dirname # careful not to construct an IOError with a 2-tuple, as that has a special meaning...
125
126     tx = None
127     if hasattr(os, 'chmod'):
128         try:
129             os.chmod(dirname, mode)
130         except OSError, x:
131             tx = x
132
133     if strictmode and hasattr(os, 'stat'):
134         s = os.stat(dirname)
135         resmode = stat.S_IMODE(s.st_mode)
136         if resmode != mode:
137             if tx:
138                 raise tx
139             raise exceptions.IOError, "unknown error prevented setting correct mode of directory, or changed mode of the directory immediately after creation.  dirname: %s, mode: %04o, resmode: %04o" % (dirname, mode, resmode,)  # careful not to construct an IOError with a 2-tuple, as that has a special meaning...
140
141 def rm_dir(dirname):
142     """
143     A threadsafe and idempotent version of shutil.rmtree().  If the dir is
144     already gone, do nothing and return without raising an exception.  If this
145     call removes the dir, return without raising an exception.  If there is an
146     error that prevents deletion or if the directory gets created again after
147     rm_dir() deletes it and before rm_dir() checks that it is gone, raise an
148     exception.
149     """
150     excs = []
151     try:
152         os.chmod(dirname, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
153         for f in os.listdir(dirname):
154             fullname = os.path.join(dirname, f)
155             if os.path.isdir(fullname):
156                 rm_dir(fullname)
157             else:
158                 remove(fullname)
159         os.rmdir(dirname)
160     except Exception, le:
161         # Ignore "No such file or directory"
162         if (not isinstance(le, OSError)) or le.args[0] != 2:
163             excs.append(le)
164
165     # Okay, now we've recursively removed everything, ignoring any "No
166     # such file or directory" errors, and collecting any other errors.
167
168     if os.path.exists(dirname):
169         if len(excs) == 1:
170             raise excs[0]
171         if len(excs) == 0:
172             raise OSError, "Failed to remove dir for unknown reason."
173         raise OSError, excs
174
175
176 def remove_if_possible(f):
177     try:
178         remove(f)
179     except:
180         pass