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