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