lock.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2014 Jean-Marc Martins
  3. # Copyright © 2012-2017 Guillaume Ayoub
  4. # Copyright © 2017-2019 Unrud <unrud@outlook.com>
  5. #
  6. # This library is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  18. import contextlib
  19. import logging
  20. import os
  21. import shlex
  22. import signal
  23. import subprocess
  24. import sys
  25. from typing import Iterator
  26. from radicale import config, pathutils, types
  27. from radicale.log import logger
  28. from radicale.storage.multifilesystem.base import CollectionBase, StorageBase
  29. class CollectionPartLock(CollectionBase):
  30. @types.contextmanager
  31. def _acquire_cache_lock(self, ns: str = "") -> Iterator[None]:
  32. if self._storage._lock.locked == "w":
  33. yield
  34. return
  35. cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache")
  36. self._storage._makedirs_synced(cache_folder)
  37. lock_path = os.path.join(cache_folder,
  38. ".Radicale.lock" + (".%s" % ns if ns else ""))
  39. lock = pathutils.RwLock(lock_path)
  40. with lock.acquire("w"):
  41. yield
  42. class StoragePartLock(StorageBase):
  43. _lock: pathutils.RwLock
  44. _hook: str
  45. def __init__(self, configuration: config.Configuration) -> None:
  46. super().__init__(configuration)
  47. lock_path = os.path.join(self._filesystem_folder, ".Radicale.lock")
  48. self._lock = pathutils.RwLock(lock_path)
  49. self._hook = configuration.get("storage", "hook")
  50. @types.contextmanager
  51. def acquire_lock(self, mode: str, user: str = "") -> Iterator[None]:
  52. with self._lock.acquire(mode):
  53. yield
  54. # execute hook
  55. if mode == "w" and self._hook:
  56. debug = logger.isEnabledFor(logging.DEBUG)
  57. # Use new process group for child to prevent terminals
  58. # from sending SIGINT etc.
  59. preexec_fn = None
  60. creationflags = 0
  61. if sys.platform == "win32":
  62. creationflags |= subprocess.CREATE_NEW_PROCESS_GROUP
  63. else:
  64. # Process group is also used to identify child processes
  65. preexec_fn = os.setpgrp
  66. command = self._hook % {
  67. "user": shlex.quote(user or "Anonymous")}
  68. logger.debug("Running storage hook")
  69. p = subprocess.Popen(
  70. command, stdin=subprocess.DEVNULL,
  71. stdout=subprocess.PIPE if debug else subprocess.DEVNULL,
  72. stderr=subprocess.PIPE if debug else subprocess.DEVNULL,
  73. shell=True, universal_newlines=True, preexec_fn=preexec_fn,
  74. cwd=self._filesystem_folder, creationflags=creationflags)
  75. try:
  76. stdout_data, stderr_data = p.communicate()
  77. except BaseException: # e.g. KeyboardInterrupt or SystemExit
  78. p.kill()
  79. p.wait()
  80. raise
  81. finally:
  82. if sys.platform != "win32":
  83. # Kill remaining children identified by process group
  84. with contextlib.suppress(OSError):
  85. os.killpg(p.pid, signal.SIGKILL)
  86. if stdout_data:
  87. logger.debug("Captured stdout from hook:\n%s", stdout_data)
  88. if stderr_data:
  89. logger.debug("Captured stderr from hook:\n%s", stderr_data)
  90. if p.returncode != 0:
  91. raise subprocess.CalledProcessError(p.returncode, p.args)