##// END OF EJS Templates
vfs: makes all audited path relative...
Boris Feld -
r41122:c8006a25 default
parent child Browse files
Show More
@@ -1,665 +1,667 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 _auditpath(self, path, mode):
340 def _auditpath(self, path, mode):
341 if self._audit:
341 if self._audit:
342 if os.path.isabs(path) and path.startswith(self.base):
343 path = os.path.relpath(path, self.base)
342 r = util.checkosfilename(path)
344 r = util.checkosfilename(path)
343 if r:
345 if r:
344 raise error.Abort("%s: %r" % (r, path))
346 raise error.Abort("%s: %r" % (r, path))
345 self.audit(path, mode=mode)
347 self.audit(path, mode=mode)
346
348
347 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
349 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
348 backgroundclose=False, checkambig=False, auditpath=True,
350 backgroundclose=False, checkambig=False, auditpath=True,
349 makeparentdirs=True):
351 makeparentdirs=True):
350 '''Open ``path`` file, which is relative to vfs root.
352 '''Open ``path`` file, which is relative to vfs root.
351
353
352 By default, parent directories are created as needed. Newly created
354 By default, parent directories are created as needed. Newly created
353 directories are marked as "not to be indexed by the content indexing
355 directories are marked as "not to be indexed by the content indexing
354 service", if ``notindexed`` is specified for "write" mode access.
356 service", if ``notindexed`` is specified for "write" mode access.
355 Set ``makeparentdirs=False`` to not create directories implicitly.
357 Set ``makeparentdirs=False`` to not create directories implicitly.
356
358
357 If ``backgroundclose`` is passed, the file may be closed asynchronously.
359 If ``backgroundclose`` is passed, the file may be closed asynchronously.
358 It can only be used if the ``self.backgroundclosing()`` context manager
360 It can only be used if the ``self.backgroundclosing()`` context manager
359 is active. This should only be specified if the following criteria hold:
361 is active. This should only be specified if the following criteria hold:
360
362
361 1. There is a potential for writing thousands of files. Unless you
363 1. There is a potential for writing thousands of files. Unless you
362 are writing thousands of files, the performance benefits of
364 are writing thousands of files, the performance benefits of
363 asynchronously closing files is not realized.
365 asynchronously closing files is not realized.
364 2. Files are opened exactly once for the ``backgroundclosing``
366 2. Files are opened exactly once for the ``backgroundclosing``
365 active duration and are therefore free of race conditions between
367 active duration and are therefore free of race conditions between
366 closing a file on a background thread and reopening it. (If the
368 closing a file on a background thread and reopening it. (If the
367 file were opened multiple times, there could be unflushed data
369 file were opened multiple times, there could be unflushed data
368 because the original file handle hasn't been flushed/closed yet.)
370 because the original file handle hasn't been flushed/closed yet.)
369
371
370 ``checkambig`` argument is passed to atomictemplfile (valid
372 ``checkambig`` argument is passed to atomictemplfile (valid
371 only for writing), and is useful only if target file is
373 only for writing), and is useful only if target file is
372 guarded by any lock (e.g. repo.lock or repo.wlock).
374 guarded by any lock (e.g. repo.lock or repo.wlock).
373
375
374 To avoid file stat ambiguity forcibly, checkambig=True involves
376 To avoid file stat ambiguity forcibly, checkambig=True involves
375 copying ``path`` file opened in "append" mode (e.g. for
377 copying ``path`` file opened in "append" mode (e.g. for
376 truncation), if it is owned by another. Therefore, use
378 truncation), if it is owned by another. Therefore, use
377 combination of append mode and checkambig=True only in limited
379 combination of append mode and checkambig=True only in limited
378 cases (see also issue5418 and issue5584 for detail).
380 cases (see also issue5418 and issue5584 for detail).
379 '''
381 '''
380 if auditpath:
382 if auditpath:
381 self._auditpath(path, mode)
383 self._auditpath(path, mode)
382 f = self.join(path)
384 f = self.join(path)
383
385
384 if "b" not in mode:
386 if "b" not in mode:
385 mode += "b" # for that other OS
387 mode += "b" # for that other OS
386
388
387 nlink = -1
389 nlink = -1
388 if mode not in ('r', 'rb'):
390 if mode not in ('r', 'rb'):
389 dirname, basename = util.split(f)
391 dirname, basename = util.split(f)
390 # If basename is empty, then the path is malformed because it points
392 # If basename is empty, then the path is malformed because it points
391 # to a directory. Let the posixfile() call below raise IOError.
393 # to a directory. Let the posixfile() call below raise IOError.
392 if basename:
394 if basename:
393 if atomictemp:
395 if atomictemp:
394 if makeparentdirs:
396 if makeparentdirs:
395 util.makedirs(dirname, self.createmode, notindexed)
397 util.makedirs(dirname, self.createmode, notindexed)
396 return util.atomictempfile(f, mode, self.createmode,
398 return util.atomictempfile(f, mode, self.createmode,
397 checkambig=checkambig)
399 checkambig=checkambig)
398 try:
400 try:
399 if 'w' in mode:
401 if 'w' in mode:
400 util.unlink(f)
402 util.unlink(f)
401 nlink = 0
403 nlink = 0
402 else:
404 else:
403 # nlinks() may behave differently for files on Windows
405 # nlinks() may behave differently for files on Windows
404 # shares if the file is open.
406 # shares if the file is open.
405 with util.posixfile(f):
407 with util.posixfile(f):
406 nlink = util.nlinks(f)
408 nlink = util.nlinks(f)
407 if nlink < 1:
409 if nlink < 1:
408 nlink = 2 # force mktempcopy (issue1922)
410 nlink = 2 # force mktempcopy (issue1922)
409 except (OSError, IOError) as e:
411 except (OSError, IOError) as e:
410 if e.errno != errno.ENOENT:
412 if e.errno != errno.ENOENT:
411 raise
413 raise
412 nlink = 0
414 nlink = 0
413 if makeparentdirs:
415 if makeparentdirs:
414 util.makedirs(dirname, self.createmode, notindexed)
416 util.makedirs(dirname, self.createmode, notindexed)
415 if nlink > 0:
417 if nlink > 0:
416 if self._trustnlink is None:
418 if self._trustnlink is None:
417 self._trustnlink = nlink > 1 or util.checknlink(f)
419 self._trustnlink = nlink > 1 or util.checknlink(f)
418 if nlink > 1 or not self._trustnlink:
420 if nlink > 1 or not self._trustnlink:
419 util.rename(util.mktempcopy(f), f)
421 util.rename(util.mktempcopy(f), f)
420 fp = util.posixfile(f, mode)
422 fp = util.posixfile(f, mode)
421 if nlink == 0:
423 if nlink == 0:
422 self._fixfilemode(f)
424 self._fixfilemode(f)
423
425
424 if checkambig:
426 if checkambig:
425 if mode in ('r', 'rb'):
427 if mode in ('r', 'rb'):
426 raise error.Abort(_('implementation error: mode %s is not'
428 raise error.Abort(_('implementation error: mode %s is not'
427 ' valid for checkambig=True') % mode)
429 ' valid for checkambig=True') % mode)
428 fp = checkambigatclosing(fp)
430 fp = checkambigatclosing(fp)
429
431
430 if (backgroundclose and
432 if (backgroundclose and
431 isinstance(threading.currentThread(), threading._MainThread)):
433 isinstance(threading.currentThread(), threading._MainThread)):
432 if not self._backgroundfilecloser:
434 if not self._backgroundfilecloser:
433 raise error.Abort(_('backgroundclose can only be used when a '
435 raise error.Abort(_('backgroundclose can only be used when a '
434 'backgroundclosing context manager is active')
436 'backgroundclosing context manager is active')
435 )
437 )
436
438
437 fp = delayclosedfile(fp, self._backgroundfilecloser)
439 fp = delayclosedfile(fp, self._backgroundfilecloser)
438
440
439 return fp
441 return fp
440
442
441 def symlink(self, src, dst):
443 def symlink(self, src, dst):
442 self.audit(dst)
444 self.audit(dst)
443 linkname = self.join(dst)
445 linkname = self.join(dst)
444 util.tryunlink(linkname)
446 util.tryunlink(linkname)
445
447
446 util.makedirs(os.path.dirname(linkname), self.createmode)
448 util.makedirs(os.path.dirname(linkname), self.createmode)
447
449
448 if self._cansymlink:
450 if self._cansymlink:
449 try:
451 try:
450 os.symlink(src, linkname)
452 os.symlink(src, linkname)
451 except OSError as err:
453 except OSError as err:
452 raise OSError(err.errno, _('could not symlink to %r: %s') %
454 raise OSError(err.errno, _('could not symlink to %r: %s') %
453 (src, encoding.strtolocal(err.strerror)),
455 (src, encoding.strtolocal(err.strerror)),
454 linkname)
456 linkname)
455 else:
457 else:
456 self.write(dst, src)
458 self.write(dst, src)
457
459
458 def join(self, path, *insidef):
460 def join(self, path, *insidef):
459 if path:
461 if path:
460 return os.path.join(self.base, path, *insidef)
462 return os.path.join(self.base, path, *insidef)
461 else:
463 else:
462 return self.base
464 return self.base
463
465
464 opener = vfs
466 opener = vfs
465
467
466 class proxyvfs(object):
468 class proxyvfs(object):
467 def __init__(self, vfs):
469 def __init__(self, vfs):
468 self.vfs = vfs
470 self.vfs = vfs
469
471
470 @property
472 @property
471 def options(self):
473 def options(self):
472 return self.vfs.options
474 return self.vfs.options
473
475
474 @options.setter
476 @options.setter
475 def options(self, value):
477 def options(self, value):
476 self.vfs.options = value
478 self.vfs.options = value
477
479
478 class filtervfs(abstractvfs, proxyvfs):
480 class filtervfs(abstractvfs, proxyvfs):
479 '''Wrapper vfs for filtering filenames with a function.'''
481 '''Wrapper vfs for filtering filenames with a function.'''
480
482
481 def __init__(self, vfs, filter):
483 def __init__(self, vfs, filter):
482 proxyvfs.__init__(self, vfs)
484 proxyvfs.__init__(self, vfs)
483 self._filter = filter
485 self._filter = filter
484
486
485 def __call__(self, path, *args, **kwargs):
487 def __call__(self, path, *args, **kwargs):
486 return self.vfs(self._filter(path), *args, **kwargs)
488 return self.vfs(self._filter(path), *args, **kwargs)
487
489
488 def join(self, path, *insidef):
490 def join(self, path, *insidef):
489 if path:
491 if path:
490 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
492 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
491 else:
493 else:
492 return self.vfs.join(path)
494 return self.vfs.join(path)
493
495
494 filteropener = filtervfs
496 filteropener = filtervfs
495
497
496 class readonlyvfs(abstractvfs, proxyvfs):
498 class readonlyvfs(abstractvfs, proxyvfs):
497 '''Wrapper vfs preventing any writing.'''
499 '''Wrapper vfs preventing any writing.'''
498
500
499 def __init__(self, vfs):
501 def __init__(self, vfs):
500 proxyvfs.__init__(self, vfs)
502 proxyvfs.__init__(self, vfs)
501
503
502 def __call__(self, path, mode='r', *args, **kw):
504 def __call__(self, path, mode='r', *args, **kw):
503 if mode not in ('r', 'rb'):
505 if mode not in ('r', 'rb'):
504 raise error.Abort(_('this vfs is read only'))
506 raise error.Abort(_('this vfs is read only'))
505 return self.vfs(path, mode, *args, **kw)
507 return self.vfs(path, mode, *args, **kw)
506
508
507 def join(self, path, *insidef):
509 def join(self, path, *insidef):
508 return self.vfs.join(path, *insidef)
510 return self.vfs.join(path, *insidef)
509
511
510 class closewrapbase(object):
512 class closewrapbase(object):
511 """Base class of wrapper, which hooks closing
513 """Base class of wrapper, which hooks closing
512
514
513 Do not instantiate outside of the vfs layer.
515 Do not instantiate outside of the vfs layer.
514 """
516 """
515 def __init__(self, fh):
517 def __init__(self, fh):
516 object.__setattr__(self, r'_origfh', fh)
518 object.__setattr__(self, r'_origfh', fh)
517
519
518 def __getattr__(self, attr):
520 def __getattr__(self, attr):
519 return getattr(self._origfh, attr)
521 return getattr(self._origfh, attr)
520
522
521 def __setattr__(self, attr, value):
523 def __setattr__(self, attr, value):
522 return setattr(self._origfh, attr, value)
524 return setattr(self._origfh, attr, value)
523
525
524 def __delattr__(self, attr):
526 def __delattr__(self, attr):
525 return delattr(self._origfh, attr)
527 return delattr(self._origfh, attr)
526
528
527 def __enter__(self):
529 def __enter__(self):
528 self._origfh.__enter__()
530 self._origfh.__enter__()
529 return self
531 return self
530
532
531 def __exit__(self, exc_type, exc_value, exc_tb):
533 def __exit__(self, exc_type, exc_value, exc_tb):
532 raise NotImplementedError('attempted instantiating ' + str(type(self)))
534 raise NotImplementedError('attempted instantiating ' + str(type(self)))
533
535
534 def close(self):
536 def close(self):
535 raise NotImplementedError('attempted instantiating ' + str(type(self)))
537 raise NotImplementedError('attempted instantiating ' + str(type(self)))
536
538
537 class delayclosedfile(closewrapbase):
539 class delayclosedfile(closewrapbase):
538 """Proxy for a file object whose close is delayed.
540 """Proxy for a file object whose close is delayed.
539
541
540 Do not instantiate outside of the vfs layer.
542 Do not instantiate outside of the vfs layer.
541 """
543 """
542 def __init__(self, fh, closer):
544 def __init__(self, fh, closer):
543 super(delayclosedfile, self).__init__(fh)
545 super(delayclosedfile, self).__init__(fh)
544 object.__setattr__(self, r'_closer', closer)
546 object.__setattr__(self, r'_closer', closer)
545
547
546 def __exit__(self, exc_type, exc_value, exc_tb):
548 def __exit__(self, exc_type, exc_value, exc_tb):
547 self._closer.close(self._origfh)
549 self._closer.close(self._origfh)
548
550
549 def close(self):
551 def close(self):
550 self._closer.close(self._origfh)
552 self._closer.close(self._origfh)
551
553
552 class backgroundfilecloser(object):
554 class backgroundfilecloser(object):
553 """Coordinates background closing of file handles on multiple threads."""
555 """Coordinates background closing of file handles on multiple threads."""
554 def __init__(self, ui, expectedcount=-1):
556 def __init__(self, ui, expectedcount=-1):
555 self._running = False
557 self._running = False
556 self._entered = False
558 self._entered = False
557 self._threads = []
559 self._threads = []
558 self._threadexception = None
560 self._threadexception = None
559
561
560 # Only Windows/NTFS has slow file closing. So only enable by default
562 # Only Windows/NTFS has slow file closing. So only enable by default
561 # on that platform. But allow to be enabled elsewhere for testing.
563 # on that platform. But allow to be enabled elsewhere for testing.
562 defaultenabled = pycompat.iswindows
564 defaultenabled = pycompat.iswindows
563 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
565 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
564
566
565 if not enabled:
567 if not enabled:
566 return
568 return
567
569
568 # There is overhead to starting and stopping the background threads.
570 # There is overhead to starting and stopping the background threads.
569 # Don't do background processing unless the file count is large enough
571 # Don't do background processing unless the file count is large enough
570 # to justify it.
572 # to justify it.
571 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
573 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
572 # FUTURE dynamically start background threads after minfilecount closes.
574 # FUTURE dynamically start background threads after minfilecount closes.
573 # (We don't currently have any callers that don't know their file count)
575 # (We don't currently have any callers that don't know their file count)
574 if expectedcount > 0 and expectedcount < minfilecount:
576 if expectedcount > 0 and expectedcount < minfilecount:
575 return
577 return
576
578
577 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
579 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
578 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
580 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
579
581
580 ui.debug('starting %d threads for background file closing\n' %
582 ui.debug('starting %d threads for background file closing\n' %
581 threadcount)
583 threadcount)
582
584
583 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
585 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
584 self._running = True
586 self._running = True
585
587
586 for i in range(threadcount):
588 for i in range(threadcount):
587 t = threading.Thread(target=self._worker, name='backgroundcloser')
589 t = threading.Thread(target=self._worker, name='backgroundcloser')
588 self._threads.append(t)
590 self._threads.append(t)
589 t.start()
591 t.start()
590
592
591 def __enter__(self):
593 def __enter__(self):
592 self._entered = True
594 self._entered = True
593 return self
595 return self
594
596
595 def __exit__(self, exc_type, exc_value, exc_tb):
597 def __exit__(self, exc_type, exc_value, exc_tb):
596 self._running = False
598 self._running = False
597
599
598 # Wait for threads to finish closing so open files don't linger for
600 # Wait for threads to finish closing so open files don't linger for
599 # longer than lifetime of context manager.
601 # longer than lifetime of context manager.
600 for t in self._threads:
602 for t in self._threads:
601 t.join()
603 t.join()
602
604
603 def _worker(self):
605 def _worker(self):
604 """Main routine for worker thread."""
606 """Main routine for worker thread."""
605 while True:
607 while True:
606 try:
608 try:
607 fh = self._queue.get(block=True, timeout=0.100)
609 fh = self._queue.get(block=True, timeout=0.100)
608 # Need to catch or the thread will terminate and
610 # Need to catch or the thread will terminate and
609 # we could orphan file descriptors.
611 # we could orphan file descriptors.
610 try:
612 try:
611 fh.close()
613 fh.close()
612 except Exception as e:
614 except Exception as e:
613 # Stash so can re-raise from main thread later.
615 # Stash so can re-raise from main thread later.
614 self._threadexception = e
616 self._threadexception = e
615 except pycompat.queue.Empty:
617 except pycompat.queue.Empty:
616 if not self._running:
618 if not self._running:
617 break
619 break
618
620
619 def close(self, fh):
621 def close(self, fh):
620 """Schedule a file for closing."""
622 """Schedule a file for closing."""
621 if not self._entered:
623 if not self._entered:
622 raise error.Abort(_('can only call close() when context manager '
624 raise error.Abort(_('can only call close() when context manager '
623 'active'))
625 'active'))
624
626
625 # If a background thread encountered an exception, raise now so we fail
627 # If a background thread encountered an exception, raise now so we fail
626 # fast. Otherwise we may potentially go on for minutes until the error
628 # fast. Otherwise we may potentially go on for minutes until the error
627 # is acted on.
629 # is acted on.
628 if self._threadexception:
630 if self._threadexception:
629 e = self._threadexception
631 e = self._threadexception
630 self._threadexception = None
632 self._threadexception = None
631 raise e
633 raise e
632
634
633 # If we're not actively running, close synchronously.
635 # If we're not actively running, close synchronously.
634 if not self._running:
636 if not self._running:
635 fh.close()
637 fh.close()
636 return
638 return
637
639
638 self._queue.put(fh, block=True, timeout=None)
640 self._queue.put(fh, block=True, timeout=None)
639
641
640 class checkambigatclosing(closewrapbase):
642 class checkambigatclosing(closewrapbase):
641 """Proxy for a file object, to avoid ambiguity of file stat
643 """Proxy for a file object, to avoid ambiguity of file stat
642
644
643 See also util.filestat for detail about "ambiguity of file stat".
645 See also util.filestat for detail about "ambiguity of file stat".
644
646
645 This proxy is useful only if the target file is guarded by any
647 This proxy is useful only if the target file is guarded by any
646 lock (e.g. repo.lock or repo.wlock)
648 lock (e.g. repo.lock or repo.wlock)
647
649
648 Do not instantiate outside of the vfs layer.
650 Do not instantiate outside of the vfs layer.
649 """
651 """
650 def __init__(self, fh):
652 def __init__(self, fh):
651 super(checkambigatclosing, self).__init__(fh)
653 super(checkambigatclosing, self).__init__(fh)
652 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
654 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
653
655
654 def _checkambig(self):
656 def _checkambig(self):
655 oldstat = self._oldstat
657 oldstat = self._oldstat
656 if oldstat.stat:
658 if oldstat.stat:
657 _avoidambig(self._origfh.name, oldstat)
659 _avoidambig(self._origfh.name, oldstat)
658
660
659 def __exit__(self, exc_type, exc_value, exc_tb):
661 def __exit__(self, exc_type, exc_value, exc_tb):
660 self._origfh.__exit__(exc_type, exc_value, exc_tb)
662 self._origfh.__exit__(exc_type, exc_value, exc_tb)
661 self._checkambig()
663 self._checkambig()
662
664
663 def close(self):
665 def close(self):
664 self._origfh.close()
666 self._origfh.close()
665 self._checkambig()
667 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now