Files
neko-daemonizer-dante/neko_daemonizer_dante/daemonizer/_signals_unix.py
2024-02-11 17:12:19 +02:00

236 lines
7.6 KiB
Python

'''
LICENSING
-------------------------------------------------
daemoniker: Cross-platform daemonization tools.
Copyright (C) 2016 Muterra, Inc.
Contributors
------------
Nick Badger
badg@muterra.io | badg@nickbadger.com | nickbadger.com
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the
Free Software Foundation, Inc.,
51 Franklin Street,
Fifth Floor,
Boston, MA 02110-1301 USA
------------------------------------------------------
'''
# Global dependencies
import os
import sys
import signal
import logging
import atexit
import traceback
import shutil
# Intra-package dependencies
from .utils import platform_specificker
from .utils import default_to
from ._signals_common import _SighandlerCore
from .exceptions import DaemonikerSignal
from .exceptions import SignalError
from .exceptions import SIGINT
from .exceptions import SIGTERM
from .exceptions import SIGABRT
_SUPPORTED_PLATFORM = platform_specificker(
linux_choice = True,
win_choice = False,
cygwin_choice = False,
osx_choice = True,
# Dunno if this is a good idea but might as well try
other_choice = True
)
if _SUPPORTED_PLATFORM:
import fcntl
import pwd
import grp
import resource
# ###############################################
# Boilerplate
# ###############################################
import logging
logger = logging.getLogger(__name__)
# Control * imports.
__all__ = [
# 'Inquisitor',
]
# ###############################################
# Library
# ###############################################
def _restore_any_previous_handler(signum, maybe_handler, force_clear=False):
''' Makes sure that a previous handler was actually set, and then
restores it.
maybe_handler is the cached potential handler. If it's the constant
(ZeroDivisionError) we've been using to denote nothingness, then
maybe_handler will either:
1. do nothing, if force_clear == False
2. restore signal.SIG_DEFL, if force_clear == True
'''
# Nope, wasn't previously set
if maybe_handler == ZeroDivisionError:
# Restore the default if we're forcing it
if force_clear:
signal.signal(signum, signal.SIG_DFL)
# (Do nothing otherwise)
# It was previously set, so re-set it.
else:
signal.signal(signum, maybe_handler)
class SignalHandler1(_SighandlerCore):
''' Signal handling system using lightweight wrapper around built-in
signal.signal handling.
'''
def __init__(self, pid_file, sigint=None, sigterm=None, sigabrt=None):
''' Creates a signal handler, using the passed callables. None
will assign the default handler (raise in main). passing
IGNORE_SIGNAL constant will result in the signal being noop'd.
'''
self.sigint = sigint
self.sigterm = sigterm
self.sigabrt = sigabrt
# Yeah, except this isn't used at all (just here for cross-platform
# consistency)
self._pidfile = pid_file
# Assign these to impossible values so we can compare against them
# later, when deciding whether or not to restore a previous handler.
# Don't use None, in case it gets used to denote a default somewhere.
# This is deliberately unconventional.
self._old_sigint = ZeroDivisionError
self._old_sigterm = ZeroDivisionError
self._old_sigabrt = ZeroDivisionError
self._running = False
def start(self):
''' Starts signal handling.
'''
if self._running:
raise RuntimeError('SignalHandler is already running.')
# Initialize stuff to values that are impossible for signal.signal.
# Don't use None, in case it gets used to denote a default somewhere.
# This is deliberately unconventional.
old_sigint = ZeroDivisionError
old_sigterm = ZeroDivisionError
old_sigabrt = ZeroDivisionError
try:
# First we need to make closures around all of our attributes, so
# they can be updated after we start listening to signals
def sigint_closure(signum, frame):
return self.sigint(signum)
def sigterm_closure(signum, frame):
return self.sigterm(signum)
def sigabrt_closure(signum, frame):
return self.sigabrt(signum)
# Now simply register those with signal.signal
old_sigint = signal.signal(signal.SIGINT, sigint_closure)
old_sigterm = signal.signal(signal.SIGTERM, sigterm_closure)
old_sigabrt = signal.signal(signal.SIGABRT, sigabrt_closure)
# If that fails, restore previous state and reraise
except:
_restore_any_previous_handler(signal.SIGINT, old_sigint)
_restore_any_previous_handler(signal.SIGTERM, old_sigterm)
_restore_any_previous_handler(signal.SIGABRT, old_sigabrt)
raise
# If that succeeds, set self._running and cache old handlers
else:
self._old_sigint = old_sigint
self._old_sigterm = old_sigterm
self._old_sigabrt = old_sigabrt
self._running = True
def stop(self):
''' Stops signal handling, returning all signal handlers to
their previous handlers, or restoring their defaults if
something went fishy.
'''
try:
_restore_any_previous_handler(
signal.SIGINT,
self._old_sigint,
force_clear = True
)
_restore_any_previous_handler(
signal.SIGTERM,
self._old_sigterm,
force_clear = True
)
_restore_any_previous_handler(
signal.SIGABRT,
self._old_sigabrt,
force_clear = True
)
# If we get an exception there, just force restoring all defaults and
# reraise
except:
signal.signal(signal.SIGINT, signal.SIG_DFL)
signal.signal(signal.SIGTERM, signal.SIG_DFL)
signal.signal(signal.SIGABRT, signal.SIG_DFL)
raise
finally:
# See notes above re: using ZeroDivisionError instead of None
self._old_sigint = ZeroDivisionError
self._old_sigterm = ZeroDivisionError
self._old_sigabrt = ZeroDivisionError
self._running = False
@staticmethod
def _default_handler(signum, *args):
''' The default signal handler for Unix.
'''
# Just parallel the sighandlers that are available in Windows, because
# it is definitely the limiting factor here
sigs = {
signal.SIGABRT: SIGABRT,
signal.SIGINT: SIGINT,
signal.SIGTERM: SIGTERM,
}
try:
exc = sigs[signum]
except KeyError:
exc = DaemonikerSignal
raise exc()