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