Refactoring: Changed remaining check parameters starting with an 's' to the new rules...
[check_mk.git] / cmk / utils / daemon.py
blob23577111a321b79c5a8bf0b82abe533a088612e4
1 #!/usr/bin/python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2016 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 import os
28 import sys
29 from pwd import getpwnam
30 from grp import getgrnam
31 import ctypes
32 import ctypes.util
33 from contextlib import contextmanager
34 from typing import Generator # pylint: disable=unused-import
36 from pathlib2 import Path # pylint: disable=unused-import
38 import cmk.utils.store
39 from cmk.utils.exceptions import MKGeneralException
42 def daemonize(user=0, group=0):
43 # do the UNIX double-fork magic, see Stevens' "Advanced
44 # Programming in the UNIX Environment" for details (ISBN 0201563177)
45 try:
46 pid = os.fork()
47 if pid > 0:
48 # exit first parent
49 sys.exit(0)
50 except OSError as e:
51 sys.stderr.write("Fork failed (#1): %d (%s)\n" % (e.errno, e.strerror))
52 sys.exit(1)
54 # decouple from parent environment
55 # chdir -> don't prevent unmounting...
56 os.chdir("/")
58 # Create new process group with the process as leader
59 os.setsid()
61 # Set user/group depending on params
62 if group:
63 os.setregid(getgrnam(group)[2], getgrnam(group)[2])
64 if user:
65 os.setreuid(getpwnam(user)[2], getpwnam(user)[2])
67 # do second fork
68 try:
69 pid = os.fork()
70 if pid > 0:
71 sys.exit(0)
72 except OSError as e:
73 sys.stderr.write("Fork failed (#2): %d (%s)\n" % (e.errno, e.strerror))
74 sys.exit(1)
76 sys.stdout.flush()
77 sys.stderr.flush()
79 si = os.open("/dev/null", os.O_RDONLY)
80 so = os.open("/dev/null", os.O_WRONLY)
81 os.dup2(si, 0)
82 os.dup2(so, 1)
83 os.dup2(so, 2)
84 os.close(si)
85 os.close(so)
88 def closefrom(lowfd):
89 """Closes all file descriptors starting with "lowfd", ignoring errors
91 Deletes all open file descriptors greater than or equal to lowfd from the
92 per-process object reference table. Any errors encountered while closing
93 file descriptors are ignored.
95 Difference to os.closerange() is that this automatically determines the
96 highest fd number to close.
97 """
98 try:
99 highfd = os.sysconf("SC_OPEN_MAX")
100 except ValueError:
101 highfd = 1024
103 os.closerange(lowfd, highfd)
106 # TODO: Change API and call sites to work with Path() objects
107 def lock_with_pid_file(path):
108 # type: (str) -> None
110 Use this after daemonizing or in foreground mode to ensure there is only
111 one process running.
113 if not cmk.utils.store.try_aquire_lock(path):
114 raise MKGeneralException("Failed to aquire PID file lock: "
115 "Another process is already running")
117 # Now that we have the lock we are allowed to write our pid to the file.
118 # The pid can then be used by the init script.
119 with file(path, "w") as f:
120 f.write("%d\n" % os.getpid())
123 # TODO: Change API and call sites to work with Path() objects
124 def _cleanup_locked_pid_file(path):
125 # type: (str) -> None
126 """Cleanup the lock + file acquired by the function above"""
127 if not cmk.utils.store.have_lock(path):
128 return
130 cmk.utils.store.release_lock(path)
132 try:
133 os.remove(path)
134 except OSError:
135 pass
138 @contextmanager
139 def pid_file_lock(path):
140 # type: (Path) -> Generator[None, None, None]
141 """Context manager for PID file based locking"""
142 lock_with_pid_file("%s" % path)
143 try:
144 yield
145 finally:
146 _cleanup_locked_pid_file("%s" % path)
149 def set_cmdline(cmdline):
151 Change the process name and process command line on of the running process
152 This works at least with Python 2.x on Linux
154 argv = ctypes.POINTER(ctypes.c_char_p)()
155 argc = ctypes.c_int()
156 ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(argc), ctypes.byref(argv))
157 cmdlen = sum([len(argv[i]) for i in range(argc.value)]) + argc.value
158 # TODO: This can probably be simplified...
159 _new_cmdline = ctypes.c_char_p(cmdline.ljust(cmdlen, '\0'))
161 set_procname(cmdline)
164 def set_procname(cmdline):
166 Change the process name of the running process
167 This works at least with Python 2.x on Linux
169 libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c'))
171 #argv = ctypes.POINTER(ctypes.c_char_p)()
173 # replace the command line, which is available via /proc/<pid>/cmdline.
174 # This is .e.g used by ps
175 #libc.memcpy(argv.contents, new_cmdline, cmdlen)
177 # replace the prctl name, which is available via /proc/<pid>/status.
178 # This is for example used by top and killall
179 #libc.prctl(15, new_cmdline, 0, 0, 0)
181 name_buffer = ctypes.create_string_buffer(len(cmdline) + 1)
182 name_buffer.value = cmdline
183 libc.prctl(15, ctypes.byref(name_buffer), 0, 0, 0)