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