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