mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
204 lines
6.3 KiB
Python
204 lines
6.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
# vim:fenc=utf-8
|
|
#
|
|
# Copyright (C) 2020 Freie Universität Berlin
|
|
#
|
|
# This file is subject to the terms and conditions of the GNU Lesser
|
|
# General Public License v2.1. See the file LICENSE in the top level
|
|
# directory for more details.
|
|
|
|
|
|
import atexit
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import time
|
|
import threading
|
|
import signal
|
|
import sys
|
|
|
|
import pkg
|
|
|
|
|
|
__author__ = "Martine S. Lenders"
|
|
__copyright__ = "Copyright (C) 2020 Freie Universität Berlin"
|
|
__credits__ = ["Martine S. Lenders"]
|
|
__license__ = "LGPLv2.1"
|
|
__maintainer__ = "Martine S. Lenders"
|
|
__email__ = "m.lenders@fu-berlin.de"
|
|
|
|
|
|
# see https://refactoring.guru/design-patterns/singleton/python/example
|
|
class _SingletonMeta(type):
|
|
"""
|
|
This is a thread-safe implementation of Singleton.
|
|
"""
|
|
|
|
_instance = None
|
|
|
|
_lock = threading.Lock()
|
|
"""
|
|
We now have a lock object that will be used to synchronize threads
|
|
during first access to the Singleton.
|
|
"""
|
|
|
|
def __call__(cls, *args, **kwargs):
|
|
# Now, imagine that the program has just been launched. Since
|
|
# there's no Singleton instance yet, multiple threads can
|
|
# simultaneously pass the previous conditional and reach this point
|
|
# almost at the same time. The first of them will acquire lock and
|
|
# will proceed further, while the rest will wait here.
|
|
with cls._lock:
|
|
# The first thread to acquire the lock, reaches this
|
|
# conditional, goes inside and creates the Singleton instance.
|
|
# Once it leaves the lock block, a thread that might have been
|
|
# waiting for the lock release may then enter this section. But
|
|
# since the Singleton field is already initialized, the thread
|
|
# won't create a new object.
|
|
if not cls._instance:
|
|
cls._instance = super().__call__(*args, **kwargs)
|
|
return cls._instance
|
|
|
|
|
|
class DHCPv6Server(metaclass=_SingletonMeta):
|
|
"""
|
|
Inspired by Daemon class
|
|
https://web.archive.org/web/20160305151936/\
|
|
http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
|
|
"""
|
|
PORT = 547
|
|
command = None
|
|
package = None
|
|
installer = None
|
|
|
|
def __init__(self, daemonized=False, pidfile=None, stdin=None, stdout=None,
|
|
stderr=None):
|
|
if self.command is None or self.package is None:
|
|
raise NotImplementedError("Please inherit from {} and set the "
|
|
"the static attributes `command` and "
|
|
"`packet`".format(type(self)), file=sys)
|
|
assert (not daemonized) or (pidfile is not None)
|
|
self.daemonized = daemonized
|
|
self.pidfile = pidfile
|
|
self.stdin = stdin
|
|
self.stdout = stdout
|
|
self.stderr = stderr
|
|
|
|
@classmethod
|
|
def is_installed(cls):
|
|
return shutil.which(cls.command[0]) is not None
|
|
|
|
def install(self):
|
|
self.installer = pkg.PackageManagerFactory.get_installer()
|
|
self.installer.install(self.package)
|
|
|
|
def daemonize(self):
|
|
"""
|
|
do the UNIX double-fork magic, see Stevens' "Advanced
|
|
Programming in the UNIX Environment" for details (ISBN 0201563177)
|
|
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
|
"""
|
|
try:
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
# exit first parent
|
|
sys.exit(0)
|
|
except OSError as e:
|
|
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
|
|
sys.exit(1)
|
|
|
|
# decouple from parent environment
|
|
os.chdir("/")
|
|
os.setsid()
|
|
os.umask(0)
|
|
|
|
# do second fork
|
|
try:
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
# exit from second parent
|
|
sys.exit(0)
|
|
except OSError as e:
|
|
sys.stderr.write("fork #2 failed: %d (%s)\n" %
|
|
(e.errno, e.strerror))
|
|
sys.exit(1)
|
|
|
|
# redirect standard file descriptors
|
|
if self.stdin:
|
|
si = open(self.stdin, 'r')
|
|
os.dup2(si.fileno(), sys.stdin.fileno())
|
|
if self.stdout:
|
|
sys.stdout.flush()
|
|
so = open(self.stdout, 'a+')
|
|
os.dup2(so.fileno(), sys.stdout.fileno())
|
|
if self.stderr:
|
|
sys.stderr.flush()
|
|
se = open(self.stderr, 'a+', 0)
|
|
os.dup2(se.fileno(), sys.stderr.fileno())
|
|
|
|
atexit.register(self.delpid)
|
|
# write pidfile
|
|
with open(self.pidfile, "w+") as f:
|
|
f.write("{}\n".format(os.getpid()))
|
|
|
|
def delpid(self):
|
|
os.remove(self.pidfile)
|
|
|
|
def stop(self):
|
|
"""
|
|
Stop the daemon
|
|
"""
|
|
# Get the pid from the pidfile
|
|
try:
|
|
pf = open(self.pidfile, 'r')
|
|
pid = int(pf.read().strip())
|
|
pf.close()
|
|
except IOError:
|
|
pid = None
|
|
if not pid:
|
|
message = "pidfile %s does not exist. Daemon not running?\n"
|
|
sys.stderr.write(message % self.pidfile)
|
|
return # not an error in a restart
|
|
|
|
# Try killing the daemon process
|
|
try:
|
|
while 1:
|
|
os.kill(pid, signal.SIGTERM)
|
|
time.sleep(0.1)
|
|
except OSError as err:
|
|
err = str(err)
|
|
if err.find("No such process") > 0:
|
|
if os.path.exists(self.pidfile):
|
|
os.remove(self.pidfile)
|
|
else:
|
|
print(str(err), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
def pre_run(self):
|
|
# may be overridden by implementation to do things before running
|
|
# the daemon or script
|
|
pass
|
|
|
|
def run(self):
|
|
if not self.is_installed():
|
|
self.install()
|
|
if self.daemonized:
|
|
"""
|
|
Start the daemon
|
|
"""
|
|
# Check for a pidfile to see if the daemon already runs
|
|
try:
|
|
pf = open(self.pidfile, 'r')
|
|
pid = int(pf.read().strip())
|
|
pf.close()
|
|
except (IOError, ValueError):
|
|
pid = None
|
|
if pid:
|
|
message = "pidfile %s already exist. Daemon already running?\n"
|
|
sys.stderr.write(message % self.pidfile)
|
|
sys.exit(1)
|
|
# Start the daemon
|
|
self.daemonize()
|
|
self.pre_run()
|
|
subprocess.run(self.command)
|