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