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