1 # Copyright 2012 New Dream Network, LLC (DreamHost)
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
18 import logging as std_logging
19 from logging import handlers
25 from oslo_log import log as logging
27 from neutron._i18n import _, _LE, _LI
28 from neutron.common import exceptions
30 LOG = logging.getLogger(__name__)
34 # Note: We can't use sys.std*.fileno() here. sys.std* objects may be
35 # random file-like objects that may not match the true system std* fds
36 # - and indeed may not even have a file descriptor at all (eg: test
37 # fixtures that monkey patch fixtures.StringStream onto sys.stdout).
38 # Below we always want the _real_ well-known 0,1,2 Unix fds during
39 # os.dup2 manipulation.
45 def setuid(user_id_or_name):
47 new_uid = int(user_id_or_name)
48 except (TypeError, ValueError):
49 new_uid = pwd.getpwnam(user_id_or_name).pw_uid
54 msg = _('Failed to set uid %s') % new_uid
56 raise exceptions.FailToDropPrivilegesExit(msg)
59 def setgid(group_id_or_name):
61 new_gid = int(group_id_or_name)
62 except (TypeError, ValueError):
63 new_gid = grp.getgrnam(group_id_or_name).gr_gid
68 msg = _('Failed to set gid %s') % new_gid
70 raise exceptions.FailToDropPrivilegesExit(msg)
74 """Replace WatchedFileHandler handlers by FileHandler ones.
76 Neutron logging uses WatchedFileHandler handlers but they do not
77 support privileges drop, this method replaces them by FileHandler
78 handlers supporting privileges drop.
80 log_root = logging.getLogger(None).logger
81 to_replace = [h for h in log_root.handlers
82 if isinstance(h, handlers.WatchedFileHandler)]
83 for handler in to_replace:
84 # NOTE(cbrandily): we use default delay(=False) to ensure the log file
85 # is opened before privileges drop.
86 new_handler = std_logging.FileHandler(handler.baseFilename,
88 encoding=handler.encoding)
89 log_root.removeHandler(handler)
90 log_root.addHandler(new_handler)
93 def drop_privileges(user=None, group=None):
94 """Drop privileges to user/group privileges."""
95 if user is None and group is None:
99 msg = _('Root permissions are required to drop privileges.')
101 raise exceptions.FailToDropPrivilegesExit(msg)
103 if group is not None:
107 msg = _('Failed to remove supplemental groups')
109 raise exceptions.FailToDropPrivilegesExit(msg)
115 LOG.info(_LI("Process runs with uid/gid: %(uid)s/%(gid)s"),
116 {'uid': os.getuid(), 'gid': os.getgid()})
119 class Pidfile(object):
120 def __init__(self, pidfile, procname, uuid=None):
121 self.pidfile = pidfile
122 self.procname = procname
125 self.fd = os.open(pidfile, os.O_CREAT | os.O_RDWR)
126 fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
128 LOG.exception(_LE("Error while handling pidfile: %s"), pidfile)
135 fcntl.flock(self.fd, fcntl.LOCK_UN)
137 def write(self, pid):
138 os.ftruncate(self.fd, 0)
139 os.write(self.fd, "%d" % pid)
144 pid = int(os.read(self.fd, 128))
145 os.lseek(self.fd, 0, os.SEEK_SET)
150 def is_running(self):
155 cmdline = '/proc/%s/cmdline' % pid
157 with open(cmdline, "r") as f:
158 exec_out = f.readline()
159 return self.procname in exec_out and (not self.uuid or
160 self.uuid in exec_out)
165 class Daemon(object):
166 """A generic daemon class.
168 Usage: subclass the Daemon class and override the run() method
170 def __init__(self, pidfile, stdin=DEVNULL, stdout=DEVNULL,
171 stderr=DEVNULL, procname='python', uuid=None,
172 user=None, group=None, watch_log=True):
173 """Note: pidfile may be None."""
177 self.procname = procname
178 self.pidfile = (Pidfile(pidfile, procname, uuid)
179 if pidfile is not None else None)
182 self.watch_log = watch_log
190 LOG.exception(_LE('Fork failed'))
194 """Daemonize process by doing Stevens double fork."""
196 # flush any buffered data before fork/dup2.
197 if self.stdout is not DEVNULL:
199 if self.stderr is not DEVNULL:
201 # sys.std* may not match STD{OUT,ERR}_FILENO. Tough.
202 for f in (sys.stdout, sys.stderr):
208 # decouple from parent environment
216 # redirect standard file descriptors
217 with open(os.devnull, 'w+') as devnull:
218 stdin = devnull if self.stdin is DEVNULL else self.stdin
219 stdout = devnull if self.stdout is DEVNULL else self.stdout
220 stderr = devnull if self.stderr is DEVNULL else self.stderr
221 os.dup2(stdin.fileno(), STDIN_FILENO)
222 os.dup2(stdout.fileno(), STDOUT_FILENO)
223 os.dup2(stderr.fileno(), STDERR_FILENO)
225 if self.pidfile is not None:
227 atexit.register(self.delete_pid)
228 signal.signal(signal.SIGTERM, self.handle_sigterm)
229 self.pidfile.write(os.getpid())
231 def delete_pid(self):
232 if self.pidfile is not None:
233 os.remove(str(self.pidfile))
235 def handle_sigterm(self, signum, frame):
239 """Start the daemon."""
241 if self.pidfile is not None and self.pidfile.is_running():
242 self.pidfile.unlock()
243 LOG.error(_LE('Pidfile %s already exist. Daemon already '
244 'running?'), self.pidfile)
252 """Override this method and call super().run when subclassing Daemon.
254 start() will call this method after the process has daemonized.
256 if not self.watch_log:
258 drop_privileges(self.user, self.group)