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