##// END OF EJS Templates
vfs: also audit rename...
Boris Feld -
r41124:9e593db5 default
parent child Browse files
Show More
@@ -1,670 +1,671 b''
1 # vfs.py - Mercurial 'vfs' classes
1 # vfs.py - Mercurial 'vfs' classes
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import errno
10 import errno
11 import os
11 import os
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import threading
14 import threading
15
15
16 from .i18n import _
16 from .i18n import _
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 pathutil,
20 pathutil,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24
24
25 def _avoidambig(path, oldstat):
25 def _avoidambig(path, oldstat):
26 """Avoid file stat ambiguity forcibly
26 """Avoid file stat ambiguity forcibly
27
27
28 This function causes copying ``path`` file, if it is owned by
28 This function causes copying ``path`` file, if it is owned by
29 another (see issue5418 and issue5584 for detail).
29 another (see issue5418 and issue5584 for detail).
30 """
30 """
31 def checkandavoid():
31 def checkandavoid():
32 newstat = util.filestat.frompath(path)
32 newstat = util.filestat.frompath(path)
33 # return whether file stat ambiguity is (already) avoided
33 # return whether file stat ambiguity is (already) avoided
34 return (not newstat.isambig(oldstat) or
34 return (not newstat.isambig(oldstat) or
35 newstat.avoidambig(path, oldstat))
35 newstat.avoidambig(path, oldstat))
36 if not checkandavoid():
36 if not checkandavoid():
37 # simply copy to change owner of path to get privilege to
37 # simply copy to change owner of path to get privilege to
38 # advance mtime (see issue5418)
38 # advance mtime (see issue5418)
39 util.rename(util.mktempcopy(path), path)
39 util.rename(util.mktempcopy(path), path)
40 checkandavoid()
40 checkandavoid()
41
41
42 class abstractvfs(object):
42 class abstractvfs(object):
43 """Abstract base class; cannot be instantiated"""
43 """Abstract base class; cannot be instantiated"""
44
44
45 def __init__(self, *args, **kwargs):
45 def __init__(self, *args, **kwargs):
46 '''Prevent instantiation; don't call this from subclasses.'''
46 '''Prevent instantiation; don't call this from subclasses.'''
47 raise NotImplementedError('attempted instantiating ' + str(type(self)))
47 raise NotImplementedError('attempted instantiating ' + str(type(self)))
48
48
49 def _auditpath(self, path, mode):
49 def _auditpath(self, path, mode):
50 pass
50 pass
51
51
52 def tryread(self, path):
52 def tryread(self, path):
53 '''gracefully return an empty string for missing files'''
53 '''gracefully return an empty string for missing files'''
54 try:
54 try:
55 return self.read(path)
55 return self.read(path)
56 except IOError as inst:
56 except IOError as inst:
57 if inst.errno != errno.ENOENT:
57 if inst.errno != errno.ENOENT:
58 raise
58 raise
59 return ""
59 return ""
60
60
61 def tryreadlines(self, path, mode='rb'):
61 def tryreadlines(self, path, mode='rb'):
62 '''gracefully return an empty array for missing files'''
62 '''gracefully return an empty array for missing files'''
63 try:
63 try:
64 return self.readlines(path, mode=mode)
64 return self.readlines(path, mode=mode)
65 except IOError as inst:
65 except IOError as inst:
66 if inst.errno != errno.ENOENT:
66 if inst.errno != errno.ENOENT:
67 raise
67 raise
68 return []
68 return []
69
69
70 @util.propertycache
70 @util.propertycache
71 def open(self):
71 def open(self):
72 '''Open ``path`` file, which is relative to vfs root.
72 '''Open ``path`` file, which is relative to vfs root.
73
73
74 Newly created directories are marked as "not to be indexed by
74 Newly created directories are marked as "not to be indexed by
75 the content indexing service", if ``notindexed`` is specified
75 the content indexing service", if ``notindexed`` is specified
76 for "write" mode access.
76 for "write" mode access.
77 '''
77 '''
78 return self.__call__
78 return self.__call__
79
79
80 def read(self, path):
80 def read(self, path):
81 with self(path, 'rb') as fp:
81 with self(path, 'rb') as fp:
82 return fp.read()
82 return fp.read()
83
83
84 def readlines(self, path, mode='rb'):
84 def readlines(self, path, mode='rb'):
85 with self(path, mode=mode) as fp:
85 with self(path, mode=mode) as fp:
86 return fp.readlines()
86 return fp.readlines()
87
87
88 def write(self, path, data, backgroundclose=False, **kwargs):
88 def write(self, path, data, backgroundclose=False, **kwargs):
89 with self(path, 'wb', backgroundclose=backgroundclose, **kwargs) as fp:
89 with self(path, 'wb', backgroundclose=backgroundclose, **kwargs) as fp:
90 return fp.write(data)
90 return fp.write(data)
91
91
92 def writelines(self, path, data, mode='wb', notindexed=False):
92 def writelines(self, path, data, mode='wb', notindexed=False):
93 with self(path, mode=mode, notindexed=notindexed) as fp:
93 with self(path, mode=mode, notindexed=notindexed) as fp:
94 return fp.writelines(data)
94 return fp.writelines(data)
95
95
96 def append(self, path, data):
96 def append(self, path, data):
97 with self(path, 'ab') as fp:
97 with self(path, 'ab') as fp:
98 return fp.write(data)
98 return fp.write(data)
99
99
100 def basename(self, path):
100 def basename(self, path):
101 """return base element of a path (as os.path.basename would do)
101 """return base element of a path (as os.path.basename would do)
102
102
103 This exists to allow handling of strange encoding if needed."""
103 This exists to allow handling of strange encoding if needed."""
104 return os.path.basename(path)
104 return os.path.basename(path)
105
105
106 def chmod(self, path, mode):
106 def chmod(self, path, mode):
107 return os.chmod(self.join(path), mode)
107 return os.chmod(self.join(path), mode)
108
108
109 def dirname(self, path):
109 def dirname(self, path):
110 """return dirname element of a path (as os.path.dirname would do)
110 """return dirname element of a path (as os.path.dirname would do)
111
111
112 This exists to allow handling of strange encoding if needed."""
112 This exists to allow handling of strange encoding if needed."""
113 return os.path.dirname(path)
113 return os.path.dirname(path)
114
114
115 def exists(self, path=None):
115 def exists(self, path=None):
116 return os.path.exists(self.join(path))
116 return os.path.exists(self.join(path))
117
117
118 def fstat(self, fp):
118 def fstat(self, fp):
119 return util.fstat(fp)
119 return util.fstat(fp)
120
120
121 def isdir(self, path=None):
121 def isdir(self, path=None):
122 return os.path.isdir(self.join(path))
122 return os.path.isdir(self.join(path))
123
123
124 def isfile(self, path=None):
124 def isfile(self, path=None):
125 return os.path.isfile(self.join(path))
125 return os.path.isfile(self.join(path))
126
126
127 def islink(self, path=None):
127 def islink(self, path=None):
128 return os.path.islink(self.join(path))
128 return os.path.islink(self.join(path))
129
129
130 def isfileorlink(self, path=None):
130 def isfileorlink(self, path=None):
131 '''return whether path is a regular file or a symlink
131 '''return whether path is a regular file or a symlink
132
132
133 Unlike isfile, this doesn't follow symlinks.'''
133 Unlike isfile, this doesn't follow symlinks.'''
134 try:
134 try:
135 st = self.lstat(path)
135 st = self.lstat(path)
136 except OSError:
136 except OSError:
137 return False
137 return False
138 mode = st.st_mode
138 mode = st.st_mode
139 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
139 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
140
140
141 def reljoin(self, *paths):
141 def reljoin(self, *paths):
142 """join various elements of a path together (as os.path.join would do)
142 """join various elements of a path together (as os.path.join would do)
143
143
144 The vfs base is not injected so that path stay relative. This exists
144 The vfs base is not injected so that path stay relative. This exists
145 to allow handling of strange encoding if needed."""
145 to allow handling of strange encoding if needed."""
146 return os.path.join(*paths)
146 return os.path.join(*paths)
147
147
148 def split(self, path):
148 def split(self, path):
149 """split top-most element of a path (as os.path.split would do)
149 """split top-most element of a path (as os.path.split would do)
150
150
151 This exists to allow handling of strange encoding if needed."""
151 This exists to allow handling of strange encoding if needed."""
152 return os.path.split(path)
152 return os.path.split(path)
153
153
154 def lexists(self, path=None):
154 def lexists(self, path=None):
155 return os.path.lexists(self.join(path))
155 return os.path.lexists(self.join(path))
156
156
157 def lstat(self, path=None):
157 def lstat(self, path=None):
158 return os.lstat(self.join(path))
158 return os.lstat(self.join(path))
159
159
160 def listdir(self, path=None):
160 def listdir(self, path=None):
161 return os.listdir(self.join(path))
161 return os.listdir(self.join(path))
162
162
163 def makedir(self, path=None, notindexed=True):
163 def makedir(self, path=None, notindexed=True):
164 return util.makedir(self.join(path), notindexed)
164 return util.makedir(self.join(path), notindexed)
165
165
166 def makedirs(self, path=None, mode=None):
166 def makedirs(self, path=None, mode=None):
167 return util.makedirs(self.join(path), mode)
167 return util.makedirs(self.join(path), mode)
168
168
169 def makelock(self, info, path):
169 def makelock(self, info, path):
170 return util.makelock(info, self.join(path))
170 return util.makelock(info, self.join(path))
171
171
172 def mkdir(self, path=None):
172 def mkdir(self, path=None):
173 return os.mkdir(self.join(path))
173 return os.mkdir(self.join(path))
174
174
175 def mkstemp(self, suffix='', prefix='tmp', dir=None):
175 def mkstemp(self, suffix='', prefix='tmp', dir=None):
176 fd, name = pycompat.mkstemp(suffix=suffix, prefix=prefix,
176 fd, name = pycompat.mkstemp(suffix=suffix, prefix=prefix,
177 dir=self.join(dir))
177 dir=self.join(dir))
178 dname, fname = util.split(name)
178 dname, fname = util.split(name)
179 if dir:
179 if dir:
180 return fd, os.path.join(dir, fname)
180 return fd, os.path.join(dir, fname)
181 else:
181 else:
182 return fd, fname
182 return fd, fname
183
183
184 def readdir(self, path=None, stat=None, skip=None):
184 def readdir(self, path=None, stat=None, skip=None):
185 return util.listdir(self.join(path), stat, skip)
185 return util.listdir(self.join(path), stat, skip)
186
186
187 def readlock(self, path):
187 def readlock(self, path):
188 return util.readlock(self.join(path))
188 return util.readlock(self.join(path))
189
189
190 def rename(self, src, dst, checkambig=False):
190 def rename(self, src, dst, checkambig=False):
191 """Rename from src to dst
191 """Rename from src to dst
192
192
193 checkambig argument is used with util.filestat, and is useful
193 checkambig argument is used with util.filestat, and is useful
194 only if destination file is guarded by any lock
194 only if destination file is guarded by any lock
195 (e.g. repo.lock or repo.wlock).
195 (e.g. repo.lock or repo.wlock).
196
196
197 To avoid file stat ambiguity forcibly, checkambig=True involves
197 To avoid file stat ambiguity forcibly, checkambig=True involves
198 copying ``src`` file, if it is owned by another. Therefore, use
198 copying ``src`` file, if it is owned by another. Therefore, use
199 checkambig=True only in limited cases (see also issue5418 and
199 checkambig=True only in limited cases (see also issue5418 and
200 issue5584 for detail).
200 issue5584 for detail).
201 """
201 """
202 self._auditpath(dst, 'w')
202 srcpath = self.join(src)
203 srcpath = self.join(src)
203 dstpath = self.join(dst)
204 dstpath = self.join(dst)
204 oldstat = checkambig and util.filestat.frompath(dstpath)
205 oldstat = checkambig and util.filestat.frompath(dstpath)
205 if oldstat and oldstat.stat:
206 if oldstat and oldstat.stat:
206 ret = util.rename(srcpath, dstpath)
207 ret = util.rename(srcpath, dstpath)
207 _avoidambig(dstpath, oldstat)
208 _avoidambig(dstpath, oldstat)
208 return ret
209 return ret
209 return util.rename(srcpath, dstpath)
210 return util.rename(srcpath, dstpath)
210
211
211 def readlink(self, path):
212 def readlink(self, path):
212 return util.readlink(self.join(path))
213 return util.readlink(self.join(path))
213
214
214 def removedirs(self, path=None):
215 def removedirs(self, path=None):
215 """Remove a leaf directory and all empty intermediate ones
216 """Remove a leaf directory and all empty intermediate ones
216 """
217 """
217 return util.removedirs(self.join(path))
218 return util.removedirs(self.join(path))
218
219
219 def rmdir(self, path=None):
220 def rmdir(self, path=None):
220 """Remove an empty directory."""
221 """Remove an empty directory."""
221 return os.rmdir(self.join(path))
222 return os.rmdir(self.join(path))
222
223
223 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
224 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
224 """Remove a directory tree recursively
225 """Remove a directory tree recursively
225
226
226 If ``forcibly``, this tries to remove READ-ONLY files, too.
227 If ``forcibly``, this tries to remove READ-ONLY files, too.
227 """
228 """
228 if forcibly:
229 if forcibly:
229 def onerror(function, path, excinfo):
230 def onerror(function, path, excinfo):
230 if function is not os.remove:
231 if function is not os.remove:
231 raise
232 raise
232 # read-only files cannot be unlinked under Windows
233 # read-only files cannot be unlinked under Windows
233 s = os.stat(path)
234 s = os.stat(path)
234 if (s.st_mode & stat.S_IWRITE) != 0:
235 if (s.st_mode & stat.S_IWRITE) != 0:
235 raise
236 raise
236 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
237 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
237 os.remove(path)
238 os.remove(path)
238 else:
239 else:
239 onerror = None
240 onerror = None
240 return shutil.rmtree(self.join(path),
241 return shutil.rmtree(self.join(path),
241 ignore_errors=ignore_errors, onerror=onerror)
242 ignore_errors=ignore_errors, onerror=onerror)
242
243
243 def setflags(self, path, l, x):
244 def setflags(self, path, l, x):
244 return util.setflags(self.join(path), l, x)
245 return util.setflags(self.join(path), l, x)
245
246
246 def stat(self, path=None):
247 def stat(self, path=None):
247 return os.stat(self.join(path))
248 return os.stat(self.join(path))
248
249
249 def unlink(self, path=None):
250 def unlink(self, path=None):
250 return util.unlink(self.join(path))
251 return util.unlink(self.join(path))
251
252
252 def tryunlink(self, path=None):
253 def tryunlink(self, path=None):
253 """Attempt to remove a file, ignoring missing file errors."""
254 """Attempt to remove a file, ignoring missing file errors."""
254 util.tryunlink(self.join(path))
255 util.tryunlink(self.join(path))
255
256
256 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
257 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
257 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing,
258 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing,
258 rmdir=rmdir)
259 rmdir=rmdir)
259
260
260 def utime(self, path=None, t=None):
261 def utime(self, path=None, t=None):
261 return os.utime(self.join(path), t)
262 return os.utime(self.join(path), t)
262
263
263 def walk(self, path=None, onerror=None):
264 def walk(self, path=None, onerror=None):
264 """Yield (dirpath, dirs, files) tuple for each directories under path
265 """Yield (dirpath, dirs, files) tuple for each directories under path
265
266
266 ``dirpath`` is relative one from the root of this vfs. This
267 ``dirpath`` is relative one from the root of this vfs. This
267 uses ``os.sep`` as path separator, even you specify POSIX
268 uses ``os.sep`` as path separator, even you specify POSIX
268 style ``path``.
269 style ``path``.
269
270
270 "The root of this vfs" is represented as empty ``dirpath``.
271 "The root of this vfs" is represented as empty ``dirpath``.
271 """
272 """
272 root = os.path.normpath(self.join(None))
273 root = os.path.normpath(self.join(None))
273 # when dirpath == root, dirpath[prefixlen:] becomes empty
274 # when dirpath == root, dirpath[prefixlen:] becomes empty
274 # because len(dirpath) < prefixlen.
275 # because len(dirpath) < prefixlen.
275 prefixlen = len(pathutil.normasprefix(root))
276 prefixlen = len(pathutil.normasprefix(root))
276 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
277 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
277 yield (dirpath[prefixlen:], dirs, files)
278 yield (dirpath[prefixlen:], dirs, files)
278
279
279 @contextlib.contextmanager
280 @contextlib.contextmanager
280 def backgroundclosing(self, ui, expectedcount=-1):
281 def backgroundclosing(self, ui, expectedcount=-1):
281 """Allow files to be closed asynchronously.
282 """Allow files to be closed asynchronously.
282
283
283 When this context manager is active, ``backgroundclose`` can be passed
284 When this context manager is active, ``backgroundclose`` can be passed
284 to ``__call__``/``open`` to result in the file possibly being closed
285 to ``__call__``/``open`` to result in the file possibly being closed
285 asynchronously, on a background thread.
286 asynchronously, on a background thread.
286 """
287 """
287 # Sharing backgroundfilecloser between threads is complex and using
288 # Sharing backgroundfilecloser between threads is complex and using
288 # multiple instances puts us at risk of running out of file descriptors
289 # multiple instances puts us at risk of running out of file descriptors
289 # only allow to use backgroundfilecloser when in main thread.
290 # only allow to use backgroundfilecloser when in main thread.
290 if not isinstance(threading.currentThread(), threading._MainThread):
291 if not isinstance(threading.currentThread(), threading._MainThread):
291 yield
292 yield
292 return
293 return
293 vfs = getattr(self, 'vfs', self)
294 vfs = getattr(self, 'vfs', self)
294 if getattr(vfs, '_backgroundfilecloser', None):
295 if getattr(vfs, '_backgroundfilecloser', None):
295 raise error.Abort(
296 raise error.Abort(
296 _('can only have 1 active background file closer'))
297 _('can only have 1 active background file closer'))
297
298
298 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
299 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
299 try:
300 try:
300 vfs._backgroundfilecloser = bfc
301 vfs._backgroundfilecloser = bfc
301 yield bfc
302 yield bfc
302 finally:
303 finally:
303 vfs._backgroundfilecloser = None
304 vfs._backgroundfilecloser = None
304
305
305 class vfs(abstractvfs):
306 class vfs(abstractvfs):
306 '''Operate files relative to a base directory
307 '''Operate files relative to a base directory
307
308
308 This class is used to hide the details of COW semantics and
309 This class is used to hide the details of COW semantics and
309 remote file access from higher level code.
310 remote file access from higher level code.
310
311
311 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
312 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
312 (b) the base directory is managed by hg and considered sort-of append-only.
313 (b) the base directory is managed by hg and considered sort-of append-only.
313 See pathutil.pathauditor() for details.
314 See pathutil.pathauditor() for details.
314 '''
315 '''
315 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
316 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
316 realpath=False):
317 realpath=False):
317 if expandpath:
318 if expandpath:
318 base = util.expandpath(base)
319 base = util.expandpath(base)
319 if realpath:
320 if realpath:
320 base = os.path.realpath(base)
321 base = os.path.realpath(base)
321 self.base = base
322 self.base = base
322 self._audit = audit
323 self._audit = audit
323 if audit:
324 if audit:
324 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
325 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
325 else:
326 else:
326 self.audit = (lambda path, mode=None: True)
327 self.audit = (lambda path, mode=None: True)
327 self.createmode = None
328 self.createmode = None
328 self._trustnlink = None
329 self._trustnlink = None
329
330
330 @util.propertycache
331 @util.propertycache
331 def _cansymlink(self):
332 def _cansymlink(self):
332 return util.checklink(self.base)
333 return util.checklink(self.base)
333
334
334 @util.propertycache
335 @util.propertycache
335 def _chmod(self):
336 def _chmod(self):
336 return util.checkexec(self.base)
337 return util.checkexec(self.base)
337
338
338 def _fixfilemode(self, name):
339 def _fixfilemode(self, name):
339 if self.createmode is None or not self._chmod:
340 if self.createmode is None or not self._chmod:
340 return
341 return
341 os.chmod(name, self.createmode & 0o666)
342 os.chmod(name, self.createmode & 0o666)
342
343
343 def _auditpath(self, path, mode):
344 def _auditpath(self, path, mode):
344 if self._audit:
345 if self._audit:
345 if os.path.isabs(path) and path.startswith(self.base):
346 if os.path.isabs(path) and path.startswith(self.base):
346 path = os.path.relpath(path, self.base)
347 path = os.path.relpath(path, self.base)
347 r = util.checkosfilename(path)
348 r = util.checkosfilename(path)
348 if r:
349 if r:
349 raise error.Abort("%s: %r" % (r, path))
350 raise error.Abort("%s: %r" % (r, path))
350 self.audit(path, mode=mode)
351 self.audit(path, mode=mode)
351
352
352 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
353 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
353 backgroundclose=False, checkambig=False, auditpath=True,
354 backgroundclose=False, checkambig=False, auditpath=True,
354 makeparentdirs=True):
355 makeparentdirs=True):
355 '''Open ``path`` file, which is relative to vfs root.
356 '''Open ``path`` file, which is relative to vfs root.
356
357
357 By default, parent directories are created as needed. Newly created
358 By default, parent directories are created as needed. Newly created
358 directories are marked as "not to be indexed by the content indexing
359 directories are marked as "not to be indexed by the content indexing
359 service", if ``notindexed`` is specified for "write" mode access.
360 service", if ``notindexed`` is specified for "write" mode access.
360 Set ``makeparentdirs=False`` to not create directories implicitly.
361 Set ``makeparentdirs=False`` to not create directories implicitly.
361
362
362 If ``backgroundclose`` is passed, the file may be closed asynchronously.
363 If ``backgroundclose`` is passed, the file may be closed asynchronously.
363 It can only be used if the ``self.backgroundclosing()`` context manager
364 It can only be used if the ``self.backgroundclosing()`` context manager
364 is active. This should only be specified if the following criteria hold:
365 is active. This should only be specified if the following criteria hold:
365
366
366 1. There is a potential for writing thousands of files. Unless you
367 1. There is a potential for writing thousands of files. Unless you
367 are writing thousands of files, the performance benefits of
368 are writing thousands of files, the performance benefits of
368 asynchronously closing files is not realized.
369 asynchronously closing files is not realized.
369 2. Files are opened exactly once for the ``backgroundclosing``
370 2. Files are opened exactly once for the ``backgroundclosing``
370 active duration and are therefore free of race conditions between
371 active duration and are therefore free of race conditions between
371 closing a file on a background thread and reopening it. (If the
372 closing a file on a background thread and reopening it. (If the
372 file were opened multiple times, there could be unflushed data
373 file were opened multiple times, there could be unflushed data
373 because the original file handle hasn't been flushed/closed yet.)
374 because the original file handle hasn't been flushed/closed yet.)
374
375
375 ``checkambig`` argument is passed to atomictemplfile (valid
376 ``checkambig`` argument is passed to atomictemplfile (valid
376 only for writing), and is useful only if target file is
377 only for writing), and is useful only if target file is
377 guarded by any lock (e.g. repo.lock or repo.wlock).
378 guarded by any lock (e.g. repo.lock or repo.wlock).
378
379
379 To avoid file stat ambiguity forcibly, checkambig=True involves
380 To avoid file stat ambiguity forcibly, checkambig=True involves
380 copying ``path`` file opened in "append" mode (e.g. for
381 copying ``path`` file opened in "append" mode (e.g. for
381 truncation), if it is owned by another. Therefore, use
382 truncation), if it is owned by another. Therefore, use
382 combination of append mode and checkambig=True only in limited
383 combination of append mode and checkambig=True only in limited
383 cases (see also issue5418 and issue5584 for detail).
384 cases (see also issue5418 and issue5584 for detail).
384 '''
385 '''
385 if auditpath:
386 if auditpath:
386 self._auditpath(path, mode)
387 self._auditpath(path, mode)
387 f = self.join(path)
388 f = self.join(path)
388
389
389 if "b" not in mode:
390 if "b" not in mode:
390 mode += "b" # for that other OS
391 mode += "b" # for that other OS
391
392
392 nlink = -1
393 nlink = -1
393 if mode not in ('r', 'rb'):
394 if mode not in ('r', 'rb'):
394 dirname, basename = util.split(f)
395 dirname, basename = util.split(f)
395 # If basename is empty, then the path is malformed because it points
396 # If basename is empty, then the path is malformed because it points
396 # to a directory. Let the posixfile() call below raise IOError.
397 # to a directory. Let the posixfile() call below raise IOError.
397 if basename:
398 if basename:
398 if atomictemp:
399 if atomictemp:
399 if makeparentdirs:
400 if makeparentdirs:
400 util.makedirs(dirname, self.createmode, notindexed)
401 util.makedirs(dirname, self.createmode, notindexed)
401 return util.atomictempfile(f, mode, self.createmode,
402 return util.atomictempfile(f, mode, self.createmode,
402 checkambig=checkambig)
403 checkambig=checkambig)
403 try:
404 try:
404 if 'w' in mode:
405 if 'w' in mode:
405 util.unlink(f)
406 util.unlink(f)
406 nlink = 0
407 nlink = 0
407 else:
408 else:
408 # nlinks() may behave differently for files on Windows
409 # nlinks() may behave differently for files on Windows
409 # shares if the file is open.
410 # shares if the file is open.
410 with util.posixfile(f):
411 with util.posixfile(f):
411 nlink = util.nlinks(f)
412 nlink = util.nlinks(f)
412 if nlink < 1:
413 if nlink < 1:
413 nlink = 2 # force mktempcopy (issue1922)
414 nlink = 2 # force mktempcopy (issue1922)
414 except (OSError, IOError) as e:
415 except (OSError, IOError) as e:
415 if e.errno != errno.ENOENT:
416 if e.errno != errno.ENOENT:
416 raise
417 raise
417 nlink = 0
418 nlink = 0
418 if makeparentdirs:
419 if makeparentdirs:
419 util.makedirs(dirname, self.createmode, notindexed)
420 util.makedirs(dirname, self.createmode, notindexed)
420 if nlink > 0:
421 if nlink > 0:
421 if self._trustnlink is None:
422 if self._trustnlink is None:
422 self._trustnlink = nlink > 1 or util.checknlink(f)
423 self._trustnlink = nlink > 1 or util.checknlink(f)
423 if nlink > 1 or not self._trustnlink:
424 if nlink > 1 or not self._trustnlink:
424 util.rename(util.mktempcopy(f), f)
425 util.rename(util.mktempcopy(f), f)
425 fp = util.posixfile(f, mode)
426 fp = util.posixfile(f, mode)
426 if nlink == 0:
427 if nlink == 0:
427 self._fixfilemode(f)
428 self._fixfilemode(f)
428
429
429 if checkambig:
430 if checkambig:
430 if mode in ('r', 'rb'):
431 if mode in ('r', 'rb'):
431 raise error.Abort(_('implementation error: mode %s is not'
432 raise error.Abort(_('implementation error: mode %s is not'
432 ' valid for checkambig=True') % mode)
433 ' valid for checkambig=True') % mode)
433 fp = checkambigatclosing(fp)
434 fp = checkambigatclosing(fp)
434
435
435 if (backgroundclose and
436 if (backgroundclose and
436 isinstance(threading.currentThread(), threading._MainThread)):
437 isinstance(threading.currentThread(), threading._MainThread)):
437 if not self._backgroundfilecloser:
438 if not self._backgroundfilecloser:
438 raise error.Abort(_('backgroundclose can only be used when a '
439 raise error.Abort(_('backgroundclose can only be used when a '
439 'backgroundclosing context manager is active')
440 'backgroundclosing context manager is active')
440 )
441 )
441
442
442 fp = delayclosedfile(fp, self._backgroundfilecloser)
443 fp = delayclosedfile(fp, self._backgroundfilecloser)
443
444
444 return fp
445 return fp
445
446
446 def symlink(self, src, dst):
447 def symlink(self, src, dst):
447 self.audit(dst)
448 self.audit(dst)
448 linkname = self.join(dst)
449 linkname = self.join(dst)
449 util.tryunlink(linkname)
450 util.tryunlink(linkname)
450
451
451 util.makedirs(os.path.dirname(linkname), self.createmode)
452 util.makedirs(os.path.dirname(linkname), self.createmode)
452
453
453 if self._cansymlink:
454 if self._cansymlink:
454 try:
455 try:
455 os.symlink(src, linkname)
456 os.symlink(src, linkname)
456 except OSError as err:
457 except OSError as err:
457 raise OSError(err.errno, _('could not symlink to %r: %s') %
458 raise OSError(err.errno, _('could not symlink to %r: %s') %
458 (src, encoding.strtolocal(err.strerror)),
459 (src, encoding.strtolocal(err.strerror)),
459 linkname)
460 linkname)
460 else:
461 else:
461 self.write(dst, src)
462 self.write(dst, src)
462
463
463 def join(self, path, *insidef):
464 def join(self, path, *insidef):
464 if path:
465 if path:
465 return os.path.join(self.base, path, *insidef)
466 return os.path.join(self.base, path, *insidef)
466 else:
467 else:
467 return self.base
468 return self.base
468
469
469 opener = vfs
470 opener = vfs
470
471
471 class proxyvfs(object):
472 class proxyvfs(object):
472 def __init__(self, vfs):
473 def __init__(self, vfs):
473 self.vfs = vfs
474 self.vfs = vfs
474
475
475 @property
476 @property
476 def options(self):
477 def options(self):
477 return self.vfs.options
478 return self.vfs.options
478
479
479 @options.setter
480 @options.setter
480 def options(self, value):
481 def options(self, value):
481 self.vfs.options = value
482 self.vfs.options = value
482
483
483 class filtervfs(abstractvfs, proxyvfs):
484 class filtervfs(abstractvfs, proxyvfs):
484 '''Wrapper vfs for filtering filenames with a function.'''
485 '''Wrapper vfs for filtering filenames with a function.'''
485
486
486 def __init__(self, vfs, filter):
487 def __init__(self, vfs, filter):
487 proxyvfs.__init__(self, vfs)
488 proxyvfs.__init__(self, vfs)
488 self._filter = filter
489 self._filter = filter
489
490
490 def __call__(self, path, *args, **kwargs):
491 def __call__(self, path, *args, **kwargs):
491 return self.vfs(self._filter(path), *args, **kwargs)
492 return self.vfs(self._filter(path), *args, **kwargs)
492
493
493 def join(self, path, *insidef):
494 def join(self, path, *insidef):
494 if path:
495 if path:
495 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
496 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
496 else:
497 else:
497 return self.vfs.join(path)
498 return self.vfs.join(path)
498
499
499 filteropener = filtervfs
500 filteropener = filtervfs
500
501
501 class readonlyvfs(abstractvfs, proxyvfs):
502 class readonlyvfs(abstractvfs, proxyvfs):
502 '''Wrapper vfs preventing any writing.'''
503 '''Wrapper vfs preventing any writing.'''
503
504
504 def __init__(self, vfs):
505 def __init__(self, vfs):
505 proxyvfs.__init__(self, vfs)
506 proxyvfs.__init__(self, vfs)
506
507
507 def __call__(self, path, mode='r', *args, **kw):
508 def __call__(self, path, mode='r', *args, **kw):
508 if mode not in ('r', 'rb'):
509 if mode not in ('r', 'rb'):
509 raise error.Abort(_('this vfs is read only'))
510 raise error.Abort(_('this vfs is read only'))
510 return self.vfs(path, mode, *args, **kw)
511 return self.vfs(path, mode, *args, **kw)
511
512
512 def join(self, path, *insidef):
513 def join(self, path, *insidef):
513 return self.vfs.join(path, *insidef)
514 return self.vfs.join(path, *insidef)
514
515
515 class closewrapbase(object):
516 class closewrapbase(object):
516 """Base class of wrapper, which hooks closing
517 """Base class of wrapper, which hooks closing
517
518
518 Do not instantiate outside of the vfs layer.
519 Do not instantiate outside of the vfs layer.
519 """
520 """
520 def __init__(self, fh):
521 def __init__(self, fh):
521 object.__setattr__(self, r'_origfh', fh)
522 object.__setattr__(self, r'_origfh', fh)
522
523
523 def __getattr__(self, attr):
524 def __getattr__(self, attr):
524 return getattr(self._origfh, attr)
525 return getattr(self._origfh, attr)
525
526
526 def __setattr__(self, attr, value):
527 def __setattr__(self, attr, value):
527 return setattr(self._origfh, attr, value)
528 return setattr(self._origfh, attr, value)
528
529
529 def __delattr__(self, attr):
530 def __delattr__(self, attr):
530 return delattr(self._origfh, attr)
531 return delattr(self._origfh, attr)
531
532
532 def __enter__(self):
533 def __enter__(self):
533 self._origfh.__enter__()
534 self._origfh.__enter__()
534 return self
535 return self
535
536
536 def __exit__(self, exc_type, exc_value, exc_tb):
537 def __exit__(self, exc_type, exc_value, exc_tb):
537 raise NotImplementedError('attempted instantiating ' + str(type(self)))
538 raise NotImplementedError('attempted instantiating ' + str(type(self)))
538
539
539 def close(self):
540 def close(self):
540 raise NotImplementedError('attempted instantiating ' + str(type(self)))
541 raise NotImplementedError('attempted instantiating ' + str(type(self)))
541
542
542 class delayclosedfile(closewrapbase):
543 class delayclosedfile(closewrapbase):
543 """Proxy for a file object whose close is delayed.
544 """Proxy for a file object whose close is delayed.
544
545
545 Do not instantiate outside of the vfs layer.
546 Do not instantiate outside of the vfs layer.
546 """
547 """
547 def __init__(self, fh, closer):
548 def __init__(self, fh, closer):
548 super(delayclosedfile, self).__init__(fh)
549 super(delayclosedfile, self).__init__(fh)
549 object.__setattr__(self, r'_closer', closer)
550 object.__setattr__(self, r'_closer', closer)
550
551
551 def __exit__(self, exc_type, exc_value, exc_tb):
552 def __exit__(self, exc_type, exc_value, exc_tb):
552 self._closer.close(self._origfh)
553 self._closer.close(self._origfh)
553
554
554 def close(self):
555 def close(self):
555 self._closer.close(self._origfh)
556 self._closer.close(self._origfh)
556
557
557 class backgroundfilecloser(object):
558 class backgroundfilecloser(object):
558 """Coordinates background closing of file handles on multiple threads."""
559 """Coordinates background closing of file handles on multiple threads."""
559 def __init__(self, ui, expectedcount=-1):
560 def __init__(self, ui, expectedcount=-1):
560 self._running = False
561 self._running = False
561 self._entered = False
562 self._entered = False
562 self._threads = []
563 self._threads = []
563 self._threadexception = None
564 self._threadexception = None
564
565
565 # Only Windows/NTFS has slow file closing. So only enable by default
566 # Only Windows/NTFS has slow file closing. So only enable by default
566 # on that platform. But allow to be enabled elsewhere for testing.
567 # on that platform. But allow to be enabled elsewhere for testing.
567 defaultenabled = pycompat.iswindows
568 defaultenabled = pycompat.iswindows
568 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
569 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
569
570
570 if not enabled:
571 if not enabled:
571 return
572 return
572
573
573 # There is overhead to starting and stopping the background threads.
574 # There is overhead to starting and stopping the background threads.
574 # Don't do background processing unless the file count is large enough
575 # Don't do background processing unless the file count is large enough
575 # to justify it.
576 # to justify it.
576 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
577 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
577 # FUTURE dynamically start background threads after minfilecount closes.
578 # FUTURE dynamically start background threads after minfilecount closes.
578 # (We don't currently have any callers that don't know their file count)
579 # (We don't currently have any callers that don't know their file count)
579 if expectedcount > 0 and expectedcount < minfilecount:
580 if expectedcount > 0 and expectedcount < minfilecount:
580 return
581 return
581
582
582 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
583 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
583 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
584 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
584
585
585 ui.debug('starting %d threads for background file closing\n' %
586 ui.debug('starting %d threads for background file closing\n' %
586 threadcount)
587 threadcount)
587
588
588 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
589 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
589 self._running = True
590 self._running = True
590
591
591 for i in range(threadcount):
592 for i in range(threadcount):
592 t = threading.Thread(target=self._worker, name='backgroundcloser')
593 t = threading.Thread(target=self._worker, name='backgroundcloser')
593 self._threads.append(t)
594 self._threads.append(t)
594 t.start()
595 t.start()
595
596
596 def __enter__(self):
597 def __enter__(self):
597 self._entered = True
598 self._entered = True
598 return self
599 return self
599
600
600 def __exit__(self, exc_type, exc_value, exc_tb):
601 def __exit__(self, exc_type, exc_value, exc_tb):
601 self._running = False
602 self._running = False
602
603
603 # Wait for threads to finish closing so open files don't linger for
604 # Wait for threads to finish closing so open files don't linger for
604 # longer than lifetime of context manager.
605 # longer than lifetime of context manager.
605 for t in self._threads:
606 for t in self._threads:
606 t.join()
607 t.join()
607
608
608 def _worker(self):
609 def _worker(self):
609 """Main routine for worker thread."""
610 """Main routine for worker thread."""
610 while True:
611 while True:
611 try:
612 try:
612 fh = self._queue.get(block=True, timeout=0.100)
613 fh = self._queue.get(block=True, timeout=0.100)
613 # Need to catch or the thread will terminate and
614 # Need to catch or the thread will terminate and
614 # we could orphan file descriptors.
615 # we could orphan file descriptors.
615 try:
616 try:
616 fh.close()
617 fh.close()
617 except Exception as e:
618 except Exception as e:
618 # Stash so can re-raise from main thread later.
619 # Stash so can re-raise from main thread later.
619 self._threadexception = e
620 self._threadexception = e
620 except pycompat.queue.Empty:
621 except pycompat.queue.Empty:
621 if not self._running:
622 if not self._running:
622 break
623 break
623
624
624 def close(self, fh):
625 def close(self, fh):
625 """Schedule a file for closing."""
626 """Schedule a file for closing."""
626 if not self._entered:
627 if not self._entered:
627 raise error.Abort(_('can only call close() when context manager '
628 raise error.Abort(_('can only call close() when context manager '
628 'active'))
629 'active'))
629
630
630 # If a background thread encountered an exception, raise now so we fail
631 # If a background thread encountered an exception, raise now so we fail
631 # fast. Otherwise we may potentially go on for minutes until the error
632 # fast. Otherwise we may potentially go on for minutes until the error
632 # is acted on.
633 # is acted on.
633 if self._threadexception:
634 if self._threadexception:
634 e = self._threadexception
635 e = self._threadexception
635 self._threadexception = None
636 self._threadexception = None
636 raise e
637 raise e
637
638
638 # If we're not actively running, close synchronously.
639 # If we're not actively running, close synchronously.
639 if not self._running:
640 if not self._running:
640 fh.close()
641 fh.close()
641 return
642 return
642
643
643 self._queue.put(fh, block=True, timeout=None)
644 self._queue.put(fh, block=True, timeout=None)
644
645
645 class checkambigatclosing(closewrapbase):
646 class checkambigatclosing(closewrapbase):
646 """Proxy for a file object, to avoid ambiguity of file stat
647 """Proxy for a file object, to avoid ambiguity of file stat
647
648
648 See also util.filestat for detail about "ambiguity of file stat".
649 See also util.filestat for detail about "ambiguity of file stat".
649
650
650 This proxy is useful only if the target file is guarded by any
651 This proxy is useful only if the target file is guarded by any
651 lock (e.g. repo.lock or repo.wlock)
652 lock (e.g. repo.lock or repo.wlock)
652
653
653 Do not instantiate outside of the vfs layer.
654 Do not instantiate outside of the vfs layer.
654 """
655 """
655 def __init__(self, fh):
656 def __init__(self, fh):
656 super(checkambigatclosing, self).__init__(fh)
657 super(checkambigatclosing, self).__init__(fh)
657 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
658 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
658
659
659 def _checkambig(self):
660 def _checkambig(self):
660 oldstat = self._oldstat
661 oldstat = self._oldstat
661 if oldstat.stat:
662 if oldstat.stat:
662 _avoidambig(self._origfh.name, oldstat)
663 _avoidambig(self._origfh.name, oldstat)
663
664
664 def __exit__(self, exc_type, exc_value, exc_tb):
665 def __exit__(self, exc_type, exc_value, exc_tb):
665 self._origfh.__exit__(exc_type, exc_value, exc_tb)
666 self._origfh.__exit__(exc_type, exc_value, exc_tb)
666 self._checkambig()
667 self._checkambig()
667
668
668 def close(self):
669 def close(self):
669 self._origfh.close()
670 self._origfh.close()
670 self._checkambig()
671 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now