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