##// END OF EJS Templates
vfs: handle _auditpath in proxyvfs...
Boris Feld -
r41126:adee334d default
parent child Browse files
Show More
@@ -1,671 +1,674 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 self._auditpath(dst, 'w')
203 srcpath = self.join(src)
203 srcpath = self.join(src)
204 dstpath = self.join(dst)
204 dstpath = self.join(dst)
205 oldstat = checkambig and util.filestat.frompath(dstpath)
205 oldstat = checkambig and util.filestat.frompath(dstpath)
206 if oldstat and oldstat.stat:
206 if oldstat and oldstat.stat:
207 ret = util.rename(srcpath, dstpath)
207 ret = util.rename(srcpath, dstpath)
208 _avoidambig(dstpath, oldstat)
208 _avoidambig(dstpath, oldstat)
209 return ret
209 return ret
210 return util.rename(srcpath, dstpath)
210 return util.rename(srcpath, dstpath)
211
211
212 def readlink(self, path):
212 def readlink(self, path):
213 return util.readlink(self.join(path))
213 return util.readlink(self.join(path))
214
214
215 def removedirs(self, path=None):
215 def removedirs(self, path=None):
216 """Remove a leaf directory and all empty intermediate ones
216 """Remove a leaf directory and all empty intermediate ones
217 """
217 """
218 return util.removedirs(self.join(path))
218 return util.removedirs(self.join(path))
219
219
220 def rmdir(self, path=None):
220 def rmdir(self, path=None):
221 """Remove an empty directory."""
221 """Remove an empty directory."""
222 return os.rmdir(self.join(path))
222 return os.rmdir(self.join(path))
223
223
224 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
224 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
225 """Remove a directory tree recursively
225 """Remove a directory tree recursively
226
226
227 If ``forcibly``, this tries to remove READ-ONLY files, too.
227 If ``forcibly``, this tries to remove READ-ONLY files, too.
228 """
228 """
229 if forcibly:
229 if forcibly:
230 def onerror(function, path, excinfo):
230 def onerror(function, path, excinfo):
231 if function is not os.remove:
231 if function is not os.remove:
232 raise
232 raise
233 # read-only files cannot be unlinked under Windows
233 # read-only files cannot be unlinked under Windows
234 s = os.stat(path)
234 s = os.stat(path)
235 if (s.st_mode & stat.S_IWRITE) != 0:
235 if (s.st_mode & stat.S_IWRITE) != 0:
236 raise
236 raise
237 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)
238 os.remove(path)
238 os.remove(path)
239 else:
239 else:
240 onerror = None
240 onerror = None
241 return shutil.rmtree(self.join(path),
241 return shutil.rmtree(self.join(path),
242 ignore_errors=ignore_errors, onerror=onerror)
242 ignore_errors=ignore_errors, onerror=onerror)
243
243
244 def setflags(self, path, l, x):
244 def setflags(self, path, l, x):
245 return util.setflags(self.join(path), l, x)
245 return util.setflags(self.join(path), l, x)
246
246
247 def stat(self, path=None):
247 def stat(self, path=None):
248 return os.stat(self.join(path))
248 return os.stat(self.join(path))
249
249
250 def unlink(self, path=None):
250 def unlink(self, path=None):
251 return util.unlink(self.join(path))
251 return util.unlink(self.join(path))
252
252
253 def tryunlink(self, path=None):
253 def tryunlink(self, path=None):
254 """Attempt to remove a file, ignoring missing file errors."""
254 """Attempt to remove a file, ignoring missing file errors."""
255 util.tryunlink(self.join(path))
255 util.tryunlink(self.join(path))
256
256
257 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
257 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
258 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing,
258 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing,
259 rmdir=rmdir)
259 rmdir=rmdir)
260
260
261 def utime(self, path=None, t=None):
261 def utime(self, path=None, t=None):
262 return os.utime(self.join(path), t)
262 return os.utime(self.join(path), t)
263
263
264 def walk(self, path=None, onerror=None):
264 def walk(self, path=None, onerror=None):
265 """Yield (dirpath, dirs, files) tuple for each directories under path
265 """Yield (dirpath, dirs, files) tuple for each directories under path
266
266
267 ``dirpath`` is relative one from the root of this vfs. This
267 ``dirpath`` is relative one from the root of this vfs. This
268 uses ``os.sep`` as path separator, even you specify POSIX
268 uses ``os.sep`` as path separator, even you specify POSIX
269 style ``path``.
269 style ``path``.
270
270
271 "The root of this vfs" is represented as empty ``dirpath``.
271 "The root of this vfs" is represented as empty ``dirpath``.
272 """
272 """
273 root = os.path.normpath(self.join(None))
273 root = os.path.normpath(self.join(None))
274 # when dirpath == root, dirpath[prefixlen:] becomes empty
274 # when dirpath == root, dirpath[prefixlen:] becomes empty
275 # because len(dirpath) < prefixlen.
275 # because len(dirpath) < prefixlen.
276 prefixlen = len(pathutil.normasprefix(root))
276 prefixlen = len(pathutil.normasprefix(root))
277 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):
278 yield (dirpath[prefixlen:], dirs, files)
278 yield (dirpath[prefixlen:], dirs, files)
279
279
280 @contextlib.contextmanager
280 @contextlib.contextmanager
281 def backgroundclosing(self, ui, expectedcount=-1):
281 def backgroundclosing(self, ui, expectedcount=-1):
282 """Allow files to be closed asynchronously.
282 """Allow files to be closed asynchronously.
283
283
284 When this context manager is active, ``backgroundclose`` can be passed
284 When this context manager is active, ``backgroundclose`` can be passed
285 to ``__call__``/``open`` to result in the file possibly being closed
285 to ``__call__``/``open`` to result in the file possibly being closed
286 asynchronously, on a background thread.
286 asynchronously, on a background thread.
287 """
287 """
288 # Sharing backgroundfilecloser between threads is complex and using
288 # Sharing backgroundfilecloser between threads is complex and using
289 # 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
290 # only allow to use backgroundfilecloser when in main thread.
290 # only allow to use backgroundfilecloser when in main thread.
291 if not isinstance(threading.currentThread(), threading._MainThread):
291 if not isinstance(threading.currentThread(), threading._MainThread):
292 yield
292 yield
293 return
293 return
294 vfs = getattr(self, 'vfs', self)
294 vfs = getattr(self, 'vfs', self)
295 if getattr(vfs, '_backgroundfilecloser', None):
295 if getattr(vfs, '_backgroundfilecloser', None):
296 raise error.Abort(
296 raise error.Abort(
297 _('can only have 1 active background file closer'))
297 _('can only have 1 active background file closer'))
298
298
299 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
299 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
300 try:
300 try:
301 vfs._backgroundfilecloser = bfc
301 vfs._backgroundfilecloser = bfc
302 yield bfc
302 yield bfc
303 finally:
303 finally:
304 vfs._backgroundfilecloser = None
304 vfs._backgroundfilecloser = None
305
305
306 class vfs(abstractvfs):
306 class vfs(abstractvfs):
307 '''Operate files relative to a base directory
307 '''Operate files relative to a base directory
308
308
309 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
310 remote file access from higher level code.
310 remote file access from higher level code.
311
311
312 '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
313 (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.
314 See pathutil.pathauditor() for details.
314 See pathutil.pathauditor() for details.
315 '''
315 '''
316 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
316 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
317 realpath=False):
317 realpath=False):
318 if expandpath:
318 if expandpath:
319 base = util.expandpath(base)
319 base = util.expandpath(base)
320 if realpath:
320 if realpath:
321 base = os.path.realpath(base)
321 base = os.path.realpath(base)
322 self.base = base
322 self.base = base
323 self._audit = audit
323 self._audit = audit
324 if audit:
324 if audit:
325 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
325 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
326 else:
326 else:
327 self.audit = (lambda path, mode=None: True)
327 self.audit = (lambda path, mode=None: True)
328 self.createmode = None
328 self.createmode = None
329 self._trustnlink = None
329 self._trustnlink = None
330
330
331 @util.propertycache
331 @util.propertycache
332 def _cansymlink(self):
332 def _cansymlink(self):
333 return util.checklink(self.base)
333 return util.checklink(self.base)
334
334
335 @util.propertycache
335 @util.propertycache
336 def _chmod(self):
336 def _chmod(self):
337 return util.checkexec(self.base)
337 return util.checkexec(self.base)
338
338
339 def _fixfilemode(self, name):
339 def _fixfilemode(self, name):
340 if self.createmode is None or not self._chmod:
340 if self.createmode is None or not self._chmod:
341 return
341 return
342 os.chmod(name, self.createmode & 0o666)
342 os.chmod(name, self.createmode & 0o666)
343
343
344 def _auditpath(self, path, mode):
344 def _auditpath(self, path, mode):
345 if self._audit:
345 if self._audit:
346 if os.path.isabs(path) and path.startswith(self.base):
346 if os.path.isabs(path) and path.startswith(self.base):
347 path = os.path.relpath(path, self.base)
347 path = os.path.relpath(path, self.base)
348 r = util.checkosfilename(path)
348 r = util.checkosfilename(path)
349 if r:
349 if r:
350 raise error.Abort("%s: %r" % (r, path))
350 raise error.Abort("%s: %r" % (r, path))
351 self.audit(path, mode=mode)
351 self.audit(path, mode=mode)
352
352
353 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
353 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
354 backgroundclose=False, checkambig=False, auditpath=True,
354 backgroundclose=False, checkambig=False, auditpath=True,
355 makeparentdirs=True):
355 makeparentdirs=True):
356 '''Open ``path`` file, which is relative to vfs root.
356 '''Open ``path`` file, which is relative to vfs root.
357
357
358 By default, parent directories are created as needed. Newly created
358 By default, parent directories are created as needed. Newly created
359 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
360 service", if ``notindexed`` is specified for "write" mode access.
360 service", if ``notindexed`` is specified for "write" mode access.
361 Set ``makeparentdirs=False`` to not create directories implicitly.
361 Set ``makeparentdirs=False`` to not create directories implicitly.
362
362
363 If ``backgroundclose`` is passed, the file may be closed asynchronously.
363 If ``backgroundclose`` is passed, the file may be closed asynchronously.
364 It can only be used if the ``self.backgroundclosing()`` context manager
364 It can only be used if the ``self.backgroundclosing()`` context manager
365 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:
366
366
367 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
368 are writing thousands of files, the performance benefits of
368 are writing thousands of files, the performance benefits of
369 asynchronously closing files is not realized.
369 asynchronously closing files is not realized.
370 2. Files are opened exactly once for the ``backgroundclosing``
370 2. Files are opened exactly once for the ``backgroundclosing``
371 active duration and are therefore free of race conditions between
371 active duration and are therefore free of race conditions between
372 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
373 file were opened multiple times, there could be unflushed data
373 file were opened multiple times, there could be unflushed data
374 because the original file handle hasn't been flushed/closed yet.)
374 because the original file handle hasn't been flushed/closed yet.)
375
375
376 ``checkambig`` argument is passed to atomictemplfile (valid
376 ``checkambig`` argument is passed to atomictemplfile (valid
377 only for writing), and is useful only if target file is
377 only for writing), and is useful only if target file is
378 guarded by any lock (e.g. repo.lock or repo.wlock).
378 guarded by any lock (e.g. repo.lock or repo.wlock).
379
379
380 To avoid file stat ambiguity forcibly, checkambig=True involves
380 To avoid file stat ambiguity forcibly, checkambig=True involves
381 copying ``path`` file opened in "append" mode (e.g. for
381 copying ``path`` file opened in "append" mode (e.g. for
382 truncation), if it is owned by another. Therefore, use
382 truncation), if it is owned by another. Therefore, use
383 combination of append mode and checkambig=True only in limited
383 combination of append mode and checkambig=True only in limited
384 cases (see also issue5418 and issue5584 for detail).
384 cases (see also issue5418 and issue5584 for detail).
385 '''
385 '''
386 if auditpath:
386 if auditpath:
387 self._auditpath(path, mode)
387 self._auditpath(path, mode)
388 f = self.join(path)
388 f = self.join(path)
389
389
390 if "b" not in mode:
390 if "b" not in mode:
391 mode += "b" # for that other OS
391 mode += "b" # for that other OS
392
392
393 nlink = -1
393 nlink = -1
394 if mode not in ('r', 'rb'):
394 if mode not in ('r', 'rb'):
395 dirname, basename = util.split(f)
395 dirname, basename = util.split(f)
396 # 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
397 # to a directory. Let the posixfile() call below raise IOError.
397 # to a directory. Let the posixfile() call below raise IOError.
398 if basename:
398 if basename:
399 if atomictemp:
399 if atomictemp:
400 if makeparentdirs:
400 if makeparentdirs:
401 util.makedirs(dirname, self.createmode, notindexed)
401 util.makedirs(dirname, self.createmode, notindexed)
402 return util.atomictempfile(f, mode, self.createmode,
402 return util.atomictempfile(f, mode, self.createmode,
403 checkambig=checkambig)
403 checkambig=checkambig)
404 try:
404 try:
405 if 'w' in mode:
405 if 'w' in mode:
406 util.unlink(f)
406 util.unlink(f)
407 nlink = 0
407 nlink = 0
408 else:
408 else:
409 # nlinks() may behave differently for files on Windows
409 # nlinks() may behave differently for files on Windows
410 # shares if the file is open.
410 # shares if the file is open.
411 with util.posixfile(f):
411 with util.posixfile(f):
412 nlink = util.nlinks(f)
412 nlink = util.nlinks(f)
413 if nlink < 1:
413 if nlink < 1:
414 nlink = 2 # force mktempcopy (issue1922)
414 nlink = 2 # force mktempcopy (issue1922)
415 except (OSError, IOError) as e:
415 except (OSError, IOError) as e:
416 if e.errno != errno.ENOENT:
416 if e.errno != errno.ENOENT:
417 raise
417 raise
418 nlink = 0
418 nlink = 0
419 if makeparentdirs:
419 if makeparentdirs:
420 util.makedirs(dirname, self.createmode, notindexed)
420 util.makedirs(dirname, self.createmode, notindexed)
421 if nlink > 0:
421 if nlink > 0:
422 if self._trustnlink is None:
422 if self._trustnlink is None:
423 self._trustnlink = nlink > 1 or util.checknlink(f)
423 self._trustnlink = nlink > 1 or util.checknlink(f)
424 if nlink > 1 or not self._trustnlink:
424 if nlink > 1 or not self._trustnlink:
425 util.rename(util.mktempcopy(f), f)
425 util.rename(util.mktempcopy(f), f)
426 fp = util.posixfile(f, mode)
426 fp = util.posixfile(f, mode)
427 if nlink == 0:
427 if nlink == 0:
428 self._fixfilemode(f)
428 self._fixfilemode(f)
429
429
430 if checkambig:
430 if checkambig:
431 if mode in ('r', 'rb'):
431 if mode in ('r', 'rb'):
432 raise error.Abort(_('implementation error: mode %s is not'
432 raise error.Abort(_('implementation error: mode %s is not'
433 ' valid for checkambig=True') % mode)
433 ' valid for checkambig=True') % mode)
434 fp = checkambigatclosing(fp)
434 fp = checkambigatclosing(fp)
435
435
436 if (backgroundclose and
436 if (backgroundclose and
437 isinstance(threading.currentThread(), threading._MainThread)):
437 isinstance(threading.currentThread(), threading._MainThread)):
438 if not self._backgroundfilecloser:
438 if not self._backgroundfilecloser:
439 raise error.Abort(_('backgroundclose can only be used when a '
439 raise error.Abort(_('backgroundclose can only be used when a '
440 'backgroundclosing context manager is active')
440 'backgroundclosing context manager is active')
441 )
441 )
442
442
443 fp = delayclosedfile(fp, self._backgroundfilecloser)
443 fp = delayclosedfile(fp, self._backgroundfilecloser)
444
444
445 return fp
445 return fp
446
446
447 def symlink(self, src, dst):
447 def symlink(self, src, dst):
448 self.audit(dst)
448 self.audit(dst)
449 linkname = self.join(dst)
449 linkname = self.join(dst)
450 util.tryunlink(linkname)
450 util.tryunlink(linkname)
451
451
452 util.makedirs(os.path.dirname(linkname), self.createmode)
452 util.makedirs(os.path.dirname(linkname), self.createmode)
453
453
454 if self._cansymlink:
454 if self._cansymlink:
455 try:
455 try:
456 os.symlink(src, linkname)
456 os.symlink(src, linkname)
457 except OSError as err:
457 except OSError as err:
458 raise OSError(err.errno, _('could not symlink to %r: %s') %
458 raise OSError(err.errno, _('could not symlink to %r: %s') %
459 (src, encoding.strtolocal(err.strerror)),
459 (src, encoding.strtolocal(err.strerror)),
460 linkname)
460 linkname)
461 else:
461 else:
462 self.write(dst, src)
462 self.write(dst, src)
463
463
464 def join(self, path, *insidef):
464 def join(self, path, *insidef):
465 if path:
465 if path:
466 return os.path.join(self.base, path, *insidef)
466 return os.path.join(self.base, path, *insidef)
467 else:
467 else:
468 return self.base
468 return self.base
469
469
470 opener = vfs
470 opener = vfs
471
471
472 class proxyvfs(abstractvfs):
472 class proxyvfs(abstractvfs):
473 def __init__(self, vfs):
473 def __init__(self, vfs):
474 self.vfs = vfs
474 self.vfs = vfs
475
475
476 def _auditpath(self, path, mode):
477 return self.vfs._auditpath(path, mode)
478
476 @property
479 @property
477 def options(self):
480 def options(self):
478 return self.vfs.options
481 return self.vfs.options
479
482
480 @options.setter
483 @options.setter
481 def options(self, value):
484 def options(self, value):
482 self.vfs.options = value
485 self.vfs.options = value
483
486
484 class filtervfs(proxyvfs, abstractvfs):
487 class filtervfs(proxyvfs, abstractvfs):
485 '''Wrapper vfs for filtering filenames with a function.'''
488 '''Wrapper vfs for filtering filenames with a function.'''
486
489
487 def __init__(self, vfs, filter):
490 def __init__(self, vfs, filter):
488 proxyvfs.__init__(self, vfs)
491 proxyvfs.__init__(self, vfs)
489 self._filter = filter
492 self._filter = filter
490
493
491 def __call__(self, path, *args, **kwargs):
494 def __call__(self, path, *args, **kwargs):
492 return self.vfs(self._filter(path), *args, **kwargs)
495 return self.vfs(self._filter(path), *args, **kwargs)
493
496
494 def join(self, path, *insidef):
497 def join(self, path, *insidef):
495 if path:
498 if path:
496 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
499 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
497 else:
500 else:
498 return self.vfs.join(path)
501 return self.vfs.join(path)
499
502
500 filteropener = filtervfs
503 filteropener = filtervfs
501
504
502 class readonlyvfs(proxyvfs):
505 class readonlyvfs(proxyvfs):
503 '''Wrapper vfs preventing any writing.'''
506 '''Wrapper vfs preventing any writing.'''
504
507
505 def __init__(self, vfs):
508 def __init__(self, vfs):
506 proxyvfs.__init__(self, vfs)
509 proxyvfs.__init__(self, vfs)
507
510
508 def __call__(self, path, mode='r', *args, **kw):
511 def __call__(self, path, mode='r', *args, **kw):
509 if mode not in ('r', 'rb'):
512 if mode not in ('r', 'rb'):
510 raise error.Abort(_('this vfs is read only'))
513 raise error.Abort(_('this vfs is read only'))
511 return self.vfs(path, mode, *args, **kw)
514 return self.vfs(path, mode, *args, **kw)
512
515
513 def join(self, path, *insidef):
516 def join(self, path, *insidef):
514 return self.vfs.join(path, *insidef)
517 return self.vfs.join(path, *insidef)
515
518
516 class closewrapbase(object):
519 class closewrapbase(object):
517 """Base class of wrapper, which hooks closing
520 """Base class of wrapper, which hooks closing
518
521
519 Do not instantiate outside of the vfs layer.
522 Do not instantiate outside of the vfs layer.
520 """
523 """
521 def __init__(self, fh):
524 def __init__(self, fh):
522 object.__setattr__(self, r'_origfh', fh)
525 object.__setattr__(self, r'_origfh', fh)
523
526
524 def __getattr__(self, attr):
527 def __getattr__(self, attr):
525 return getattr(self._origfh, attr)
528 return getattr(self._origfh, attr)
526
529
527 def __setattr__(self, attr, value):
530 def __setattr__(self, attr, value):
528 return setattr(self._origfh, attr, value)
531 return setattr(self._origfh, attr, value)
529
532
530 def __delattr__(self, attr):
533 def __delattr__(self, attr):
531 return delattr(self._origfh, attr)
534 return delattr(self._origfh, attr)
532
535
533 def __enter__(self):
536 def __enter__(self):
534 self._origfh.__enter__()
537 self._origfh.__enter__()
535 return self
538 return self
536
539
537 def __exit__(self, exc_type, exc_value, exc_tb):
540 def __exit__(self, exc_type, exc_value, exc_tb):
538 raise NotImplementedError('attempted instantiating ' + str(type(self)))
541 raise NotImplementedError('attempted instantiating ' + str(type(self)))
539
542
540 def close(self):
543 def close(self):
541 raise NotImplementedError('attempted instantiating ' + str(type(self)))
544 raise NotImplementedError('attempted instantiating ' + str(type(self)))
542
545
543 class delayclosedfile(closewrapbase):
546 class delayclosedfile(closewrapbase):
544 """Proxy for a file object whose close is delayed.
547 """Proxy for a file object whose close is delayed.
545
548
546 Do not instantiate outside of the vfs layer.
549 Do not instantiate outside of the vfs layer.
547 """
550 """
548 def __init__(self, fh, closer):
551 def __init__(self, fh, closer):
549 super(delayclosedfile, self).__init__(fh)
552 super(delayclosedfile, self).__init__(fh)
550 object.__setattr__(self, r'_closer', closer)
553 object.__setattr__(self, r'_closer', closer)
551
554
552 def __exit__(self, exc_type, exc_value, exc_tb):
555 def __exit__(self, exc_type, exc_value, exc_tb):
553 self._closer.close(self._origfh)
556 self._closer.close(self._origfh)
554
557
555 def close(self):
558 def close(self):
556 self._closer.close(self._origfh)
559 self._closer.close(self._origfh)
557
560
558 class backgroundfilecloser(object):
561 class backgroundfilecloser(object):
559 """Coordinates background closing of file handles on multiple threads."""
562 """Coordinates background closing of file handles on multiple threads."""
560 def __init__(self, ui, expectedcount=-1):
563 def __init__(self, ui, expectedcount=-1):
561 self._running = False
564 self._running = False
562 self._entered = False
565 self._entered = False
563 self._threads = []
566 self._threads = []
564 self._threadexception = None
567 self._threadexception = None
565
568
566 # Only Windows/NTFS has slow file closing. So only enable by default
569 # Only Windows/NTFS has slow file closing. So only enable by default
567 # on that platform. But allow to be enabled elsewhere for testing.
570 # on that platform. But allow to be enabled elsewhere for testing.
568 defaultenabled = pycompat.iswindows
571 defaultenabled = pycompat.iswindows
569 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
572 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
570
573
571 if not enabled:
574 if not enabled:
572 return
575 return
573
576
574 # There is overhead to starting and stopping the background threads.
577 # There is overhead to starting and stopping the background threads.
575 # Don't do background processing unless the file count is large enough
578 # Don't do background processing unless the file count is large enough
576 # to justify it.
579 # to justify it.
577 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
580 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
578 # FUTURE dynamically start background threads after minfilecount closes.
581 # FUTURE dynamically start background threads after minfilecount closes.
579 # (We don't currently have any callers that don't know their file count)
582 # (We don't currently have any callers that don't know their file count)
580 if expectedcount > 0 and expectedcount < minfilecount:
583 if expectedcount > 0 and expectedcount < minfilecount:
581 return
584 return
582
585
583 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
586 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
584 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
587 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
585
588
586 ui.debug('starting %d threads for background file closing\n' %
589 ui.debug('starting %d threads for background file closing\n' %
587 threadcount)
590 threadcount)
588
591
589 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
592 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
590 self._running = True
593 self._running = True
591
594
592 for i in range(threadcount):
595 for i in range(threadcount):
593 t = threading.Thread(target=self._worker, name='backgroundcloser')
596 t = threading.Thread(target=self._worker, name='backgroundcloser')
594 self._threads.append(t)
597 self._threads.append(t)
595 t.start()
598 t.start()
596
599
597 def __enter__(self):
600 def __enter__(self):
598 self._entered = True
601 self._entered = True
599 return self
602 return self
600
603
601 def __exit__(self, exc_type, exc_value, exc_tb):
604 def __exit__(self, exc_type, exc_value, exc_tb):
602 self._running = False
605 self._running = False
603
606
604 # Wait for threads to finish closing so open files don't linger for
607 # Wait for threads to finish closing so open files don't linger for
605 # longer than lifetime of context manager.
608 # longer than lifetime of context manager.
606 for t in self._threads:
609 for t in self._threads:
607 t.join()
610 t.join()
608
611
609 def _worker(self):
612 def _worker(self):
610 """Main routine for worker thread."""
613 """Main routine for worker thread."""
611 while True:
614 while True:
612 try:
615 try:
613 fh = self._queue.get(block=True, timeout=0.100)
616 fh = self._queue.get(block=True, timeout=0.100)
614 # Need to catch or the thread will terminate and
617 # Need to catch or the thread will terminate and
615 # we could orphan file descriptors.
618 # we could orphan file descriptors.
616 try:
619 try:
617 fh.close()
620 fh.close()
618 except Exception as e:
621 except Exception as e:
619 # Stash so can re-raise from main thread later.
622 # Stash so can re-raise from main thread later.
620 self._threadexception = e
623 self._threadexception = e
621 except pycompat.queue.Empty:
624 except pycompat.queue.Empty:
622 if not self._running:
625 if not self._running:
623 break
626 break
624
627
625 def close(self, fh):
628 def close(self, fh):
626 """Schedule a file for closing."""
629 """Schedule a file for closing."""
627 if not self._entered:
630 if not self._entered:
628 raise error.Abort(_('can only call close() when context manager '
631 raise error.Abort(_('can only call close() when context manager '
629 'active'))
632 'active'))
630
633
631 # If a background thread encountered an exception, raise now so we fail
634 # If a background thread encountered an exception, raise now so we fail
632 # fast. Otherwise we may potentially go on for minutes until the error
635 # fast. Otherwise we may potentially go on for minutes until the error
633 # is acted on.
636 # is acted on.
634 if self._threadexception:
637 if self._threadexception:
635 e = self._threadexception
638 e = self._threadexception
636 self._threadexception = None
639 self._threadexception = None
637 raise e
640 raise e
638
641
639 # If we're not actively running, close synchronously.
642 # If we're not actively running, close synchronously.
640 if not self._running:
643 if not self._running:
641 fh.close()
644 fh.close()
642 return
645 return
643
646
644 self._queue.put(fh, block=True, timeout=None)
647 self._queue.put(fh, block=True, timeout=None)
645
648
646 class checkambigatclosing(closewrapbase):
649 class checkambigatclosing(closewrapbase):
647 """Proxy for a file object, to avoid ambiguity of file stat
650 """Proxy for a file object, to avoid ambiguity of file stat
648
651
649 See also util.filestat for detail about "ambiguity of file stat".
652 See also util.filestat for detail about "ambiguity of file stat".
650
653
651 This proxy is useful only if the target file is guarded by any
654 This proxy is useful only if the target file is guarded by any
652 lock (e.g. repo.lock or repo.wlock)
655 lock (e.g. repo.lock or repo.wlock)
653
656
654 Do not instantiate outside of the vfs layer.
657 Do not instantiate outside of the vfs layer.
655 """
658 """
656 def __init__(self, fh):
659 def __init__(self, fh):
657 super(checkambigatclosing, self).__init__(fh)
660 super(checkambigatclosing, self).__init__(fh)
658 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
661 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
659
662
660 def _checkambig(self):
663 def _checkambig(self):
661 oldstat = self._oldstat
664 oldstat = self._oldstat
662 if oldstat.stat:
665 if oldstat.stat:
663 _avoidambig(self._origfh.name, oldstat)
666 _avoidambig(self._origfh.name, oldstat)
664
667
665 def __exit__(self, exc_type, exc_value, exc_tb):
668 def __exit__(self, exc_type, exc_value, exc_tb):
666 self._origfh.__exit__(exc_type, exc_value, exc_tb)
669 self._origfh.__exit__(exc_type, exc_value, exc_tb)
667 self._checkambig()
670 self._checkambig()
668
671
669 def close(self):
672 def close(self):
670 self._origfh.close()
673 self._origfh.close()
671 self._checkambig()
674 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now