From 7fd86a9db9a6cbce2cc96c3e31eefb22f5c1c150 Mon Sep 17 00:00:00 2001 From: Bram Duvigneau Date: Sat, 30 Apr 2016 15:40:56 +0200 Subject: [PATCH] Save configuration profiles and gesture maps as an atomic operation (refs issue #3165) Instead of directly writing the file, a temporary file is created and moved to the final location. This prevents corrupt configuration files being written. Added a fileUtils module with just a FaultTolerantFile context manager for now to facilitate this pattern. Please update miscDependencies submodule to a revision that includes the osmove module. --- miscDeps | 2 +- source/config/__init__.py | 10 +++++++--- source/fileUtils.py | 16 ++++++++++++++++ source/inputCore.py | 4 +++- 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 source/fileUtils.py diff --git a/miscDeps b/miscDeps index 48109d94c67..e7db1066486 160000 --- a/miscDeps +++ b/miscDeps @@ -1 +1 @@ -Subproject commit 48109d94c6741a0c57a03183b6bf5963e31469d2 +Subproject commit e7db10664865f5f54c8b4671aa78c84afd82017a diff --git a/source/config/__init__.py b/source/config/__init__.py index 41a1242d053..9a4444df0a9 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -6,6 +6,7 @@ import ctypes import ctypes.wintypes import os +import osreplace import sys from cStringIO import StringIO import itertools @@ -17,6 +18,7 @@ import shlobj import baseObject import easeOfAccess +from fileUtils import FaultTolerantFile import winKernel def validateConfig(configObj,validator,validationResult=None,keyList=None): @@ -638,10 +640,12 @@ def save(self): # Never save the config if running securely. return try: - self.profiles[0].write() + with FaultTolerantFile(self.profiles[0].filename) as f: + self.profiles[0].write(f) log.info("Base configuration saved") for name in self._dirtyProfiles: - self._profileCache[name].write() + with FaultTolerantFile(self._profileCache[name].filename) as f: + self._profileCache[name].write(f) log.info("Saved configuration profile %s" % name) self._dirtyProfiles.clear() except Exception as e: @@ -738,7 +742,7 @@ def renameProfile(self, oldName, newName): if oldName.lower() != newName.lower() and os.path.isfile(newFn): raise ValueError("A profile with the same name already exists: %s" % newName) - os.rename(oldFn, newFn) + osreplace.replace(oldFn, newFn) # Update any associated triggers. allTriggers = self.triggersToProfiles saveTrigs = False diff --git a/source/fileUtils.py b/source/fileUtils.py new file mode 100644 index 00000000000..8a26cf9bcf8 --- /dev/null +++ b/source/fileUtils.py @@ -0,0 +1,16 @@ +import os +import osreplace +from contextlib import contextmanager +from tempfile import NamedTemporaryFile +from logHandler import log + +@contextmanager +def FaultTolerantFile(name): + dirpath, filename = os.path.split(name) + with NamedTemporaryFile(dir=dirpath, prefix=filename, suffix='.tmp', delete=False) as f: + log.debug(f.name) + yield f + f.flush() + os.fsync(f) + f.close() + osreplace.replace(f.name, name) \ No newline at end of file diff --git a/source/inputCore.py b/source/inputCore.py index 962600c2aa0..179b875b3ce 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -23,6 +23,7 @@ import speech import characterProcessing import config +from fileUtils import FaultTolerantFile import watchdog from logHandler import log import globalVars @@ -359,7 +360,8 @@ def save(self): else: outSect[script] = [outVal, gesture] - out.write() + with FaultTolerantFile(out.filename) as f: + out.write(f) class InputManager(baseObject.AutoPropertyObject): """Manages functionality related to input from the user.