Марк Коренберг 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

Reply via email to