lock.py 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. # This file is part of Radicale Server - Calendar 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 radicale import pathutils
  26. from radicale.log import logger
  27. class CollectionLockMixin:
  28. def _acquire_cache_lock(self, ns=""):
  29. if self._storage._lock.locked == "w":
  30. return contextlib.ExitStack()
  31. cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache")
  32. self._storage._makedirs_synced(cache_folder)
  33. lock_path = os.path.join(cache_folder,
  34. ".Radicale.lock" + (".%s" % ns if ns else ""))
  35. lock = pathutils.RwLock(lock_path)
  36. return lock.acquire("w")
  37. class StorageLockMixin:
  38. def __init__(self, configuration):
  39. super().__init__(configuration)
  40. folder = self.configuration.get("storage", "filesystem_folder")
  41. lock_path = os.path.join(folder, ".Radicale.lock")
  42. self._lock = pathutils.RwLock(lock_path)
  43. @contextlib.contextmanager
  44. def acquire_lock(self, mode, user=""):
  45. with self._lock.acquire(mode):
  46. yield
  47. # execute hook
  48. hook = self.configuration.get("storage", "hook")
  49. if mode == "w" and hook:
  50. folder = self.configuration.get("storage", "filesystem_folder")
  51. debug = logger.isEnabledFor(logging.DEBUG)
  52. popen_kwargs = dict(
  53. stdin=subprocess.DEVNULL,
  54. stdout=subprocess.PIPE if debug else subprocess.DEVNULL,
  55. stderr=subprocess.PIPE if debug else subprocess.DEVNULL,
  56. shell=True, universal_newlines=True, cwd=folder)
  57. # Use new process group for child to prevent terminals
  58. # from sending SIGINT etc.
  59. if os.name == "posix":
  60. # Process group is also used to identify child processes
  61. popen_kwargs["preexec_fn"] = os.setpgrp
  62. elif sys.platform == "win32":
  63. popen_kwargs["creationflags"] = (
  64. subprocess.CREATE_NEW_PROCESS_GROUP)
  65. command = hook % {"user": shlex.quote(user or "Anonymous")}
  66. logger.debug("Running storage hook")
  67. p = subprocess.Popen(command, **popen_kwargs)
  68. try:
  69. stdout_data, stderr_data = p.communicate()
  70. except BaseException: # e.g. KeyboardInterrupt or SystemExit
  71. p.kill()
  72. p.wait()
  73. raise
  74. finally:
  75. if os.name == "posix":
  76. # Kill remaining children identified by process group
  77. with contextlib.suppress(OSError):
  78. os.killpg(p.pid, signal.SIGKILL)
  79. if stdout_data:
  80. logger.debug("Captured stdout from hook:\n%s", stdout_data)
  81. if stderr_data:
  82. logger.debug("Captured stderr from hook:\n%s", stderr_data)
  83. if p.returncode != 0:
  84. raise subprocess.CalledProcessError(p.returncode, p.args)