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