Mercurial > public > mercurial-scm > hg
comparison mercurial/util.py @ 29017:07be86828e79 stable
util: fix race in makedirs()
Update makedirs() to ignore EEXIST in case someone else has already created the
directory in question. Previously the ensuredirs() function existed, and was
nearly identical to makedirs() except that it fixed this race. Unfortunately
ensuredirs() was only used in 3 places, and most code uses the racy makedirs()
function. This fixes makedirs() to be non-racy, and replaces calls to
ensuredirs() with makedirs().
In particular, mercurial.scmutil.origpath() used the racy makedirs() code,
which could cause failures during "hg update" as it tried to create backup
directories.
This does slightly change the behavior of call sites using ensuredirs():
previously ensuredirs() would throw EEXIST if the path existed but was a
regular file instead of a directory. It did this by explicitly checking
os.path.isdir() after getting EEXIST. The makedirs() code did not do this and
swallowed all EEXIST errors. I kept the makedirs() behavior, since it seemed
preferable to avoid the extra stat call in the common case where this directory
already exists. If the path does happen to be a file, the caller will almost
certainly fail with an ENOTDIR error shortly afterwards anyway. I checked
the 3 existing call sites of ensuredirs(), and this seems to be the case for
them.
author | Adam Simpkins <simpkins@fb.com> |
---|---|
date | Tue, 26 Apr 2016 15:32:59 -0700 |
parents | 032c4c2f802a |
children | ca4065028e00 |
comparison
equal
deleted
inserted
replaced
29016:94451300f3ec | 29017:07be86828e79 |
---|---|
1418 def __del__(self): | 1418 def __del__(self): |
1419 if safehasattr(self, '_fp'): # constructor actually did something | 1419 if safehasattr(self, '_fp'): # constructor actually did something |
1420 self.discard() | 1420 self.discard() |
1421 | 1421 |
1422 def makedirs(name, mode=None, notindexed=False): | 1422 def makedirs(name, mode=None, notindexed=False): |
1423 """recursive directory creation with parent mode inheritance""" | 1423 """recursive directory creation with parent mode inheritance |
1424 | |
1425 Newly created directories are marked as "not to be indexed by | |
1426 the content indexing service", if ``notindexed`` is specified | |
1427 for "write" mode access. | |
1428 """ | |
1424 try: | 1429 try: |
1425 makedir(name, notindexed) | 1430 makedir(name, notindexed) |
1426 except OSError as err: | 1431 except OSError as err: |
1427 if err.errno == errno.EEXIST: | 1432 if err.errno == errno.EEXIST: |
1428 return | 1433 return |
1430 raise | 1435 raise |
1431 parent = os.path.dirname(os.path.abspath(name)) | 1436 parent = os.path.dirname(os.path.abspath(name)) |
1432 if parent == name: | 1437 if parent == name: |
1433 raise | 1438 raise |
1434 makedirs(parent, mode, notindexed) | 1439 makedirs(parent, mode, notindexed) |
1435 makedir(name, notindexed) | 1440 try: |
1436 if mode is not None: | 1441 makedir(name, notindexed) |
1437 os.chmod(name, mode) | 1442 except OSError as err: |
1438 | 1443 # Catch EEXIST to handle races |
1439 def ensuredirs(name, mode=None, notindexed=False): | 1444 if err.errno == errno.EEXIST: |
1440 """race-safe recursive directory creation | 1445 return |
1441 | 1446 raise |
1442 Newly created directories are marked as "not to be indexed by | |
1443 the content indexing service", if ``notindexed`` is specified | |
1444 for "write" mode access. | |
1445 """ | |
1446 if os.path.isdir(name): | |
1447 return | |
1448 parent = os.path.dirname(os.path.abspath(name)) | |
1449 if parent != name: | |
1450 ensuredirs(parent, mode, notindexed) | |
1451 try: | |
1452 makedir(name, notindexed) | |
1453 except OSError as err: | |
1454 if err.errno == errno.EEXIST and os.path.isdir(name): | |
1455 # someone else seems to have won a directory creation race | |
1456 return | |
1457 raise | |
1458 if mode is not None: | 1447 if mode is not None: |
1459 os.chmod(name, mode) | 1448 os.chmod(name, mode) |
1460 | 1449 |
1461 def readfile(path): | 1450 def readfile(path): |
1462 with open(path, 'rb') as fp: | 1451 with open(path, 'rb') as fp: |