Merge pull request #6940 from ThomasWaldmann/mkstemp_mode-1.1

use a custom mkstemp with mode support, fixes #6933, fixes #6400
This commit is contained in:
TW 2022-08-05 14:20:02 +02:00 committed by GitHub
commit 9e22b27dad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 11 deletions

View File

@ -2533,3 +2533,71 @@ def umount(mountpoint):
return subprocess.call(['fusermount', '-u', mountpoint], env=env)
except FileNotFoundError:
return subprocess.call(['umount', mountpoint], env=env)
# below is a slightly modified tempfile.mkstemp that has an additional mode parameter.
# see https://github.com/borgbackup/borg/issues/6933 and https://github.com/borgbackup/borg/issues/6400
import os as _os # NOQA
import sys as _sys # NOQA
import errno as _errno # NOQA
from tempfile import _sanitize_params, _get_candidate_names, TMP_MAX, _text_openflags, _bin_openflags # NOQA
def _mkstemp_inner(dir, pre, suf, flags, output_type, mode=0o600):
"""Code common to mkstemp, TemporaryFile, and NamedTemporaryFile."""
dir = _os.path.abspath(dir)
names = _get_candidate_names()
if output_type is bytes:
names = map(_os.fsencode, names)
for seq in range(TMP_MAX):
name = next(names)
file = _os.path.join(dir, pre + name + suf)
# _sys.audit("tempfile.mkstemp", file) # no .audit in sys module of python 3.5
try:
fd = _os.open(file, flags, mode)
except FileExistsError:
continue # try again
except PermissionError:
# This exception is thrown when a directory with the chosen name
# already exists on windows.
if _os.name == "nt" and _os.path.isdir(dir) and _os.access(dir, _os.W_OK):
continue
else:
raise
return fd, file
raise FileExistsError(_errno.EEXIST, "No usable temporary file name found")
def mkstemp_mode(suffix=None, prefix=None, dir=None, text=False, mode=0o600):
"""User-callable function to create and return a unique temporary
file. The return value is a pair (fd, name) where fd is the
file descriptor returned by os.open, and name is the filename.
If 'suffix' is not None, the file name will end with that suffix,
otherwise there will be no suffix.
If 'prefix' is not None, the file name will begin with that prefix,
otherwise a default prefix is used.
If 'dir' is not None, the file will be created in that directory,
otherwise a default directory is used.
If 'text' is specified and true, the file is opened in text
mode. Else (the default) the file is opened in binary mode.
If any of 'suffix', 'prefix' and 'dir' are not None, they must be the
same type. If they are bytes, the returned name will be bytes; str
otherwise.
The file is readable and writable only by the creating user ID.
If the operating system uses permission bits to indicate whether a
file is executable, the file is executable by no one. The file
descriptor is not inherited by children of this process.
Caller is responsible for deleting the file when done with it.
"""
prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
if text:
flags = _text_openflags
else:
flags = _bin_openflags
return _mkstemp_inner(dir, prefix, suffix, flags, output_type, mode)

View File

@ -1,10 +1,8 @@
import errno
import os
import socket
import tempfile
import uuid
from borg.constants import UMASK_DEFAULT
from borg.helpers import safe_unlink
"""
@ -183,7 +181,9 @@ class SaveFile:
def __enter__(self):
from .. import platform
self.tmp_fd, self.tmp_fname = tempfile.mkstemp(prefix=self.tmp_prefix, suffix='.tmp', dir=self.dir)
from ..helpers import mkstemp_mode
self.tmp_fd, self.tmp_fname = mkstemp_mode(prefix=self.tmp_prefix, suffix='.tmp', dir=self.dir, mode=0o666)
self.f = platform.SyncFile(self.tmp_fname, fd=self.tmp_fd, binary=self.binary)
return self.f
@ -195,14 +195,6 @@ class SaveFile:
safe_unlink(self.tmp_fname) # with-body has failed, clean up tmp file
return # continue processing the exception normally
# tempfile.mkstemp always uses owner-only file permissions for the temp file,
# but as we'll rename it to the non-temp permanent file now, we need to respect
# the umask and change the file mode to what a normally created file would have.
# thanks to the crappy os.umask api, we can't query the umask without setting it. :-(
umask = os.umask(UMASK_DEFAULT)
os.umask(umask)
os.chmod(self.tmp_fname, mode=0o666 & ~ umask)
try:
os.replace(self.tmp_fname, self.path) # POSIX: atomic rename
except OSError: