Марк Коренберг added the comment: You also forgot about two things:
1. set temporary file permissions before rename 2. fsync(open(os.dirname(temporaryfile))) 3. if original file name is symlink, replace will works wrong. os.realpath should be used. So, here are real life function, that we use in production: # TODO: save_mtime, save_selinux, extended attr, chattrs and so on... # TODO: malicious user may replace directory with another while writing to file, # so we should use open directory first, and then use openat() and renameat() # with relative filename instead of specifying absolute path. # also NameTemporaryFile should allow to specify dir=<dir_file_descriptor> @contextmanager def replace_file(path, save_perms=False, fsync=True, **kwargs): realpath = os.path.realpath(path) operating_dir = os.path.dirname(realpath) uid = None gid = None mode = None if save_perms: try: stinfo = os.lstat(realpath) uid = stinfo.st_uid gid = stinfo.st_gid mode = stinfo.st_mode except OSError as e: if e.errno != errno.ENOENT: raise with NamedTemporaryFile(dir=operating_dir, **kwargs) as fff: filedes = fff.fileno() if None not in (uid, gid, mode): os.fchown(filedes, uid, gid) os.fchmod(filedes, mode & 0o7777) yield fff # survive application crash fff.flush() if fsync: # survive power outage, is not required if that is temporary file os.fsync(filedes) os.rename(fff.name, realpath) # see http://bugs.python.org/issue21579 fff._closer.delete = False if fsync: # Sync directory: http://stackoverflow.com/questions/3764822/how-to-durably-rename-a-file-in-posix dirfd = os.open(operating_dir, os.O_RDONLY | os.O_CLOEXEC | os.O_DIRECTORY) try: os.fsync(dirfd) finally: os.close(dirfd) ---------- nosy: +mmarkk _______________________________________ Python tracker <rep...@bugs.python.org> <http://bugs.python.org/issue8604> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com