Mercurial > hg
comparison mercurial/windows.py @ 13206:650314ed845d stable
windows.rename: eliminate temp name race (issue2571)
On Windows, os.rename reliably raises OSError with errno.EEXIST if the
destination already exists (even on shares served by Samba).
Windows does *not* silently overwrite the destination of a rename.
So there is no need to first call os.path.exists on the chosen temp path.
Trusting os.path.exists is actually harmful, since using it enables the
following racy sequence of actions:
1) os.path.exists(temp) returns False
2) some evil other process creates a file with name temp
3) os.rename(dst, temp) now fails because temp has been taken
Not using os.path.exists and directly trying os.rename(dst, temp)
eliminates this race.
author | Adrian Buehlmann <adrian@cadifra.com> |
---|---|
date | Mon, 27 Dec 2010 01:12:31 +0100 |
parents | 6c9345f9edca |
children | 6bf39d88c857 |
comparison
equal
deleted
inserted
replaced
13204:5b83ab614dab | 13206:650314ed845d |
---|---|
297 # happens immediately even for open files, so we rename | 297 # happens immediately even for open files, so we rename |
298 # destination to a temporary name, then delete that. Then | 298 # destination to a temporary name, then delete that. Then |
299 # rename is safe to do. | 299 # rename is safe to do. |
300 # The temporary name is chosen at random to avoid the situation | 300 # The temporary name is chosen at random to avoid the situation |
301 # where a file is left lying around from a previous aborted run. | 301 # where a file is left lying around from a previous aborted run. |
302 # The usual race condition this introduces can't be avoided as | 302 |
303 # we need the name to rename into, and not the file itself. Due | 303 for tries in xrange(10): |
304 # to the nature of the operation however, any races will at worst | 304 temp = '%s-%08x' % (dst, random.randint(0, 0xffffffff)) |
305 # lead to the rename failing and the current operation aborting. | 305 try: |
306 | 306 os.rename(dst, temp) # raises OSError EEXIST if temp exists |
307 def tempname(prefix): | 307 break |
308 for tries in xrange(10): | 308 except OSError, e: |
309 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff)) | 309 if e.errno != errno.EEXIST: |
310 if not os.path.exists(temp): | 310 raise |
311 return temp | 311 else: |
312 raise IOError, (errno.EEXIST, "No usable temporary filename found") | 312 raise IOError, (errno.EEXIST, "No usable temporary filename found") |
313 | 313 |
314 temp = tempname(dst) | |
315 os.rename(dst, temp) | |
316 try: | 314 try: |
317 os.unlink(temp) | 315 os.unlink(temp) |
318 except: | 316 except: |
319 # Some rude AV-scanners on Windows may cause the unlink to | 317 # Some rude AV-scanners on Windows may cause the unlink to |
320 # fail. Not aborting here just leaks the temp file, whereas | 318 # fail. Not aborting here just leaks the temp file, whereas |