/usr/lib/python3/dist-packages/UM/SaveFile.py is in python3-uranium 3.1.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | # Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
import tempfile
import os
import os.path
import sys
if sys.platform != "win32":
import fcntl
def lockFile(file):
fcntl.flock(file, fcntl.LOCK_EX)
else:
def lockFile(file): #pylint: disable=unused-argument
pass
## A class to handle atomic writes to a file.
#
# This class can be used to perform atomic writes to a file. Atomic writes ensure
# that the file contents are always correct and that concurrent writes do not
# end up writing to the same file at the same time.
class SaveFile:
# How many time so re-try saving this file when getting unknown exceptions.
__max_retries = 10
# Create a new SaveFile.
#
# \param path The path to write to.
# \param mode The file mode to use. See open() for details.
# \param encoding The encoding to use while writing the file. Defaults to UTF-8.
# \param kwargs Keyword arguments passed on to open().
def __init__(self, path, mode, encoding = "utf-8", **kwargs):
self._path = path
self._mode = mode
self._encoding = encoding
self._open_kwargs = kwargs
self._file = None
self._temp_file = None
def __enter__(self):
# Create a temporary file that we can write to.
self._temp_file = tempfile.NamedTemporaryFile(self._mode, dir = os.path.dirname(self._path), encoding = self._encoding, delete = False, **self._open_kwargs) #pylint: disable=bad-whitespace
return self._temp_file
def __exit__(self, exc_type, exc_value, traceback):
self._temp_file.close()
self.__max_retries = 10
while not self._file:
# First, try to open the file we want to write to.
try:
self._file = open(self._path, self._mode, encoding = self._encoding, **self._open_kwargs)
except PermissionError:
self._file = None #Always retry.
except Exception as e:
if self.__max_retries <= 0:
raise e
self.__max_retries -= 1
while True:
# Try to acquire a lock. This will block if the file was already locked by a different process.
lockFile(self._file)
# Once the lock is released it is possible the other instance already replaced the file we opened.
# So try to open it again and check if we have the same file.
# If we do, that means the file did not get replaced in the mean time and we properly acquired a lock on the right file.
try:
file_new = open(self._path, self._mode, encoding = self._encoding, **self._open_kwargs)
except PermissionError:
# This primarily happens on Windows where trying to open an opened file will raise a PermissionError.
# We want to block on those, to simulate blocking writes.
continue
except Exception as e:
#In other cases with unknown exceptions, don't try again indefinitely.
if self.__max_retries <= 0:
raise e
self.__max_retries -= 1
if not self._file.closed and os.path.sameopenfile(self._file.fileno(), file_new.fileno()):
file_new.close()
# Close the actual file to release the file lock.
# Note that this introduces a slight race condition where another process can lock the file
# before this process can replace it.
self._file.close()
# Replace the existing file with the temporary file.
# This operation is guaranteed to be atomic on Unix systems and should be atomic on Windows as well.
# This way we can ensure we either have the old file or the new file.
# Note that due to the above mentioned race condition, on Windows a PermissionError can be raised.
# If that happens, the replace operation failed and we should try again.
try:
os.replace(self._temp_file.name, self._path)
except PermissionError:
continue
except Exception as e:
if self.__max_retries <= 0:
raise e
self.__max_retries -= 1
break
else:
# Otherwise, retry the entire procedure.
self._file.close()
self._file = file_new
|