raise exception when locking the storage fails

Previously it was silently ignored, which is dangerous when multiple instances of Radicale are running.
A configuration option to disable locking was added.
This commit is contained in:
Unrud 2017-06-01 11:21:22 +02:00
parent 17d03be27b
commit a18874fc59
3 changed files with 31 additions and 8 deletions

4
config
View File

@ -101,6 +101,10 @@
# Folder for storing local collections, created if not present # Folder for storing local collections, created if not present
#filesystem_folder = /var/lib/radicale/collections #filesystem_folder = /var/lib/radicale/collections
# Lock the storage. Never start multiple instances of Radicale or edit the
# storage externally while Radicale is running if disabled.
#filesystem_locking = True
# Sync all changes to disk during requests. (This can impair performance.) # Sync all changes to disk during requests. (This can impair performance.)
# Disabling it increases the risk of data loss, when the system crashes or # Disabling it increases the risk of data loss, when the system crashes or
# power fails! # power fails!

View File

@ -139,6 +139,10 @@ INITIAL_CONFIG = OrderedDict([
"value": "True", "value": "True",
"help": "sync all changes to filesystem during requests", "help": "sync all changes to filesystem during requests",
"type": bool}), "type": bool}),
("filesystem_locking", {
"value": "True",
"help": "lock the storage while accessing it",
"type": bool}),
("filesystem_close_lock_file", { ("filesystem_close_lock_file", {
"value": "False", "value": "False",
"help": "close the lock file when no more clients are waiting", "help": "close the lock file when no more clients are waiting",

View File

@ -751,6 +751,10 @@ class Collection(BaseCollection):
@classmethod @classmethod
@contextmanager @contextmanager
def acquire_lock(cls, mode, user=None): def acquire_lock(cls, mode, user=None):
if not cls.configuration.getboolean("storage", "filesystem_locking"):
yield
return
def condition(): def condition():
if mode == "r": if mode == "r":
return not cls._writer return not cls._writer
@ -786,21 +790,27 @@ class Collection(BaseCollection):
# by arbitrary users # by arbitrary users
try: try:
os.chmod(lock_path, stat.S_IWUSR | stat.S_IRUSR) os.chmod(lock_path, stat.S_IWUSR | stat.S_IRUSR)
except OSError: except OSError as e:
cls.logger.debug("Failed to set permissions on lock file") cls.logger.info("Failed to set permissions on lock file:"
" %s", e, exc_info=True)
if not cls._lock_file_locked: if not cls._lock_file_locked:
if os.name == "nt": if os.name == "nt":
handle = msvcrt.get_osfhandle(cls._lock_file.fileno()) handle = msvcrt.get_osfhandle(cls._lock_file.fileno())
flags = LOCKFILE_EXCLUSIVE_LOCK if mode == "w" else 0 flags = LOCKFILE_EXCLUSIVE_LOCK if mode == "w" else 0
overlapped = Overlapped() overlapped = Overlapped()
if not lock_file_ex(handle, flags, 0, 1, 0, overlapped): if not lock_file_ex(handle, flags, 0, 1, 0, overlapped):
cls.logger.debug("Locking not supported") raise RuntimeError("Locking the storage failed: %s" %
ctypes.FormatError())
elif os.name == "posix": elif os.name == "posix":
_cmd = fcntl.LOCK_EX if mode == "w" else fcntl.LOCK_SH _cmd = fcntl.LOCK_EX if mode == "w" else fcntl.LOCK_SH
try: try:
fcntl.flock(cls._lock_file.fileno(), _cmd) fcntl.flock(cls._lock_file.fileno(), _cmd)
except OSError: except OSError as e:
cls.logger.debug("Locking not supported") raise RuntimeError("Locking the storage failed: %s" %
e) from e
else:
raise RuntimeError("Locking the storage failed: "
"Unsupported operating system")
cls._lock_file_locked = True cls._lock_file_locked = True
try: try:
yield yield
@ -822,12 +832,17 @@ class Collection(BaseCollection):
handle = msvcrt.get_osfhandle(cls._lock_file.fileno()) handle = msvcrt.get_osfhandle(cls._lock_file.fileno())
overlapped = Overlapped() overlapped = Overlapped()
if not unlock_file_ex(handle, 0, 1, 0, overlapped): if not unlock_file_ex(handle, 0, 1, 0, overlapped):
cls.logger.debug("Unlocking not supported") raise RuntimeError("Unlocking the storage failed: "
"%s" % ctypes.FormatError())
elif os.name == "posix": elif os.name == "posix":
try: try:
fcntl.flock(cls._lock_file.fileno(), fcntl.LOCK_UN) fcntl.flock(cls._lock_file.fileno(), fcntl.LOCK_UN)
except OSError: except OSError as e:
cls.logger.debug("Unlocking not supported") raise RuntimeError("Unlocking the storage failed: "
"%s" % e) from e
else:
raise RuntimeError("Unlocking the storage failed: "
"Unsupported operating system")
cls._lock_file_locked = False cls._lock_file_locked = False
if cls._waiters: if cls._waiters:
cls._waiters[0].notify() cls._waiters[0].notify()