1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-06-26 16:45:52 +00:00
Radicale/radicale/storage/multifilesystem/lock.py

122 lines
5.4 KiB
Python
Raw Permalink Normal View History

2021-12-08 21:45:42 +01:00
# This file is part of Radicale - CalDAV and CardDAV server
2018-09-04 03:33:50 +02:00
# Copyright © 2014 Jean-Marc Martins
# Copyright © 2012-2017 Guillaume Ayoub
2025-03-27 07:57:13 +01:00
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
# Copyright © 2023-2025 Peter Bieringer <pb@bieringer.de>
2018-09-04 03:33:50 +02:00
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
import contextlib
import logging
import os
import shlex
import signal
2018-09-04 03:33:50 +02:00
import subprocess
2021-07-26 20:56:46 +02:00
import sys
2021-07-26 20:56:47 +02:00
from typing import Iterator
2018-09-04 03:33:50 +02:00
2021-07-26 20:56:47 +02:00
from radicale import config, pathutils, types
2018-09-04 03:33:50 +02:00
from radicale.log import logger
2021-07-26 20:56:47 +02:00
from radicale.storage.multifilesystem.base import CollectionBase, StorageBase
2018-09-04 03:33:50 +02:00
2021-07-26 20:56:47 +02:00
class CollectionPartLock(CollectionBase):
@types.contextmanager
def _acquire_cache_lock(self, ns: str = "") -> Iterator[None]:
if self._storage._lock.locked == "w":
2021-07-26 20:56:47 +02:00
yield
return
2025-03-28 07:35:15 +01:00
cache_folder = self._storage._get_collection_cache_subfolder(self._filesystem_path, ".Radicale.cache", ns)
self._storage._makedirs_synced(cache_folder)
2018-09-04 03:33:50 +02:00
lock_path = os.path.join(cache_folder,
".Radicale.lock" + (".%s" % ns if ns else ""))
2025-03-28 07:35:15 +01:00
logger.debug("Lock file (CollectionPartLock): %r" % lock_path)
2018-09-04 03:33:50 +02:00
lock = pathutils.RwLock(lock_path)
2021-07-26 20:56:47 +02:00
with lock.acquire("w"):
yield
2018-09-04 03:33:50 +02:00
2021-07-26 20:56:47 +02:00
class StoragePartLock(StorageBase):
2021-07-26 20:56:47 +02:00
_lock: pathutils.RwLock
_hook: str
2020-01-15 06:38:32 +01:00
2021-07-26 20:56:47 +02:00
def __init__(self, configuration: config.Configuration) -> None:
super().__init__(configuration)
lock_path = os.path.join(self._filesystem_folder, ".Radicale.lock")
logger.debug("Lock file (StoragePartLock): %r" % lock_path)
self._lock = pathutils.RwLock(lock_path)
2021-07-26 20:56:47 +02:00
self._hook = configuration.get("storage", "hook")
2021-07-26 20:56:47 +02:00
@types.contextmanager
2025-03-27 08:30:22 +01:00
def acquire_lock(self, mode: str, user: str = "", *args, **kwargs) -> Iterator[None]:
with self._lock.acquire(mode):
2018-09-04 03:33:50 +02:00
yield
# execute hook
2021-07-26 20:56:47 +02:00
if mode == "w" and self._hook:
2018-09-04 03:33:50 +02:00
debug = logger.isEnabledFor(logging.DEBUG)
# Use new process group for child to prevent terminals
# from sending SIGINT etc.
2021-07-26 20:56:47 +02:00
preexec_fn = None
creationflags = 0
if sys.platform == "win32":
creationflags |= subprocess.CREATE_NEW_PROCESS_GROUP
else:
# Process group is also used to identify child processes
2021-07-26 20:56:47 +02:00
preexec_fn = os.setpgrp
2025-03-27 08:30:48 +01:00
# optional argument
path = kwargs.get('path', "")
2025-03-27 07:57:28 +01:00
try:
command = self._hook % {
2025-03-27 08:30:48 +01:00
"path": shlex.quote(self._get_collection_root_folder() + path),
"cwd": shlex.quote(self._filesystem_folder),
2025-03-27 07:57:28 +01:00
"user": shlex.quote(user or "Anonymous")}
except KeyError as e:
logger.error("Storage hook contains not supported placeholder %s (skip execution of: %r)" % (e, self._hook))
return
logger.debug("Executing storage hook: '%s'" % command)
try:
p = subprocess.Popen(
command, stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE if debug else subprocess.DEVNULL,
stderr=subprocess.PIPE if debug else subprocess.DEVNULL,
shell=True, universal_newlines=True, preexec_fn=preexec_fn,
cwd=self._filesystem_folder, creationflags=creationflags)
except Exception as e:
logger.error("Execution of storage hook not successful on 'Popen': %s" % e)
return
logger.debug("Executing storage hook started 'Popen'")
2020-08-31 13:54:48 +02:00
try:
stdout_data, stderr_data = p.communicate()
except BaseException as e: # e.g. KeyboardInterrupt or SystemExit
logger.error("Execution of storage hook not successful on 'communicate': %s" % e)
2020-08-31 13:54:48 +02:00
p.kill()
2020-10-04 13:40:30 +02:00
p.wait()
return
finally:
if sys.platform != "win32":
2020-10-04 13:40:30 +02:00
# Kill remaining children identified by process group
with contextlib.suppress(OSError):
os.killpg(p.pid, signal.SIGKILL)
logger.debug("Executing storage hook finished")
2018-09-04 03:33:50 +02:00
if stdout_data:
logger.debug("Captured stdout from storage hook:\n%s", stdout_data)
2018-09-04 03:33:50 +02:00
if stderr_data:
logger.debug("Captured stderr from storage hook:\n%s", stderr_data)
2018-09-04 03:33:50 +02:00
if p.returncode != 0:
logger.error("Execution of storage hook not successful: %s" % subprocess.CalledProcessError(p.returncode, p.args))
return