|  | @@ -1,9 +1,223 @@
 | 
	
		
			
				|  |  | +import os
 | 
	
		
			
				|  |  | +import sys
 | 
	
		
			
				|  |  | +import pwd
 | 
	
		
			
				|  |  | +import grp
 | 
	
		
			
				|  |  | +import errno
 | 
	
		
			
				|  |  | +import atexit
 | 
	
		
			
				|  |  |  import signal
 | 
	
		
			
				|  |  |  try:
 | 
	
		
			
				|  |  |      from setproctitle import setproctitle as _setproctitle
 | 
	
		
			
				|  |  |  except ImportError:
 | 
	
		
			
				|  |  |      _setproctitle = None
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +CAN_DETACH = True
 | 
	
		
			
				|  |  | +try:
 | 
	
		
			
				|  |  | +    import resource
 | 
	
		
			
				|  |  | +except ImportError:
 | 
	
		
			
				|  |  | +    CAN_DETACH = False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +DAEMON_UMASK = 0
 | 
	
		
			
				|  |  | +DAEMON_WORKDIR = "/"
 | 
	
		
			
				|  |  | +DAEMON_REDIRECT_TO = getattr(os, "devnull", "/dev/nulll")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def create_pidlock(pidfile):
 | 
	
		
			
				|  |  | +    """Create a pidfile to be used with python-daemon.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    If the pidfile already exists the program exits with an error message,
 | 
	
		
			
				|  |  | +    however if the process it refers to is not running anymore, the pidfile
 | 
	
		
			
				|  |  | +    is just deleted.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    from lockfile import pidlockfile, LockFailed
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    class PIDFile(object):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        def __init__(self, path):
 | 
	
		
			
				|  |  | +            self.path = os.path.abspath(path)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        def __enter__(self):
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                pidlockfile.write_pid_to_pidfile(self.path)
 | 
	
		
			
				|  |  | +            except OSError, exc:
 | 
	
		
			
				|  |  | +                raise LockFailed(str(exc))
 | 
	
		
			
				|  |  | +            return self
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        def __exit__(self, *_exc):
 | 
	
		
			
				|  |  | +            self.release()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        def is_locked(self):
 | 
	
		
			
				|  |  | +            return os.path.exists(self.path)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        def release(self):
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                os.unlink(self.path)
 | 
	
		
			
				|  |  | +            except OSError, exc:
 | 
	
		
			
				|  |  | +                if exc.errno in (errno.ENOENT, errno.EACCES):
 | 
	
		
			
				|  |  | +                    return
 | 
	
		
			
				|  |  | +                raise
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        def read_pid(self):
 | 
	
		
			
				|  |  | +            return pidlockfile.read_pid_from_pidfile(self.path)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        def is_stale(self):
 | 
	
		
			
				|  |  | +            pid = self.read_pid()
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                os.kill(pid, 0)
 | 
	
		
			
				|  |  | +            except os.error, exc:
 | 
	
		
			
				|  |  | +                if exc.errno == errno.ESRCH:
 | 
	
		
			
				|  |  | +                    sys.stderr.write("Stale pidfile exists. Removing it.\n")
 | 
	
		
			
				|  |  | +                    self.release()
 | 
	
		
			
				|  |  | +                    return True
 | 
	
		
			
				|  |  | +            except TypeError, exc:
 | 
	
		
			
				|  |  | +                sys.stderr.write("Broken pidfile found. Removing it.\n")
 | 
	
		
			
				|  |  | +                self.release()
 | 
	
		
			
				|  |  | +                return True
 | 
	
		
			
				|  |  | +            return False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pidlock = PIDFile(pidfile)
 | 
	
		
			
				|  |  | +    if pidlock.is_locked() and not pidlock.is_stale():
 | 
	
		
			
				|  |  | +        raise SystemExit(
 | 
	
		
			
				|  |  | +                "ERROR: Pidfile (%s) already exists.\n"
 | 
	
		
			
				|  |  | +                "Seems we're already running? (PID: %s)" % (
 | 
	
		
			
				|  |  | +                    pidfile, pidlock.read_pid()))
 | 
	
		
			
				|  |  | +    return pidlock
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class DaemonContext(object):
 | 
	
		
			
				|  |  | +    _is_open = False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, pidfile=None, chroot_directory=None,
 | 
	
		
			
				|  |  | +            working_directory=DAEMON_WORKDIR, umask=DAEMON_UMASK, **kwargs):
 | 
	
		
			
				|  |  | +        self.chroot_directory = chroot_directory
 | 
	
		
			
				|  |  | +        self.working_directory = working_directory
 | 
	
		
			
				|  |  | +        self.umask = umask
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def detach(self):
 | 
	
		
			
				|  |  | +        if os.fork() == 0:      # first child
 | 
	
		
			
				|  |  | +            os.setsid()         # create new session
 | 
	
		
			
				|  |  | +            if os.fork() > 0:   # second child
 | 
	
		
			
				|  |  | +                os._exit(0)
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            os._exit(0)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def open(self):
 | 
	
		
			
				|  |  | +        from daemon import daemon
 | 
	
		
			
				|  |  | +        if self._is_open:
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.detach()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if self.chroot_directory is not None:
 | 
	
		
			
				|  |  | +            daemon.change_root_directory(self.chroot_directory)
 | 
	
		
			
				|  |  | +        os.chdir(self.working_directory)
 | 
	
		
			
				|  |  | +        os.umask(self.umask)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        daemon.close_all_open_files()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        os.open(DAEMON_REDIRECT_TO, os.O_RDWR)
 | 
	
		
			
				|  |  | +        os.dup2(0, 1)
 | 
	
		
			
				|  |  | +        os.dup2(0, 2)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._is_open = True
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def close(self):
 | 
	
		
			
				|  |  | +        if self._is_open:
 | 
	
		
			
				|  |  | +            self._is_open = False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def create_daemon_context(logfile=None, pidfile=None, **options):
 | 
	
		
			
				|  |  | +    if not CAN_DETACH:
 | 
	
		
			
				|  |  | +        raise RuntimeError(
 | 
	
		
			
				|  |  | +                "This platform does not support detach.")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # set SIGCLD back to the default SIG_DFL (before python-daemon overrode
 | 
	
		
			
				|  |  | +    # it) lets the parent wait() for the terminated child process and stops
 | 
	
		
			
				|  |  | +    # the 'OSError: [Errno 10] No child processes' problem.
 | 
	
		
			
				|  |  | +    reset_signal("SIGCLD")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # Since without stderr any errors will be silently suppressed,
 | 
	
		
			
				|  |  | +    # we need to know that we have access to the logfile.
 | 
	
		
			
				|  |  | +    if logfile:
 | 
	
		
			
				|  |  | +        open(logfile, "a").close()
 | 
	
		
			
				|  |  | +    if pidfile:
 | 
	
		
			
				|  |  | +        open(pidfile, "a").close()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    defaults = {"umask": lambda: 0,
 | 
	
		
			
				|  |  | +                "chroot_directory": lambda: None,
 | 
	
		
			
				|  |  | +                "working_directory": lambda: os.getcwd()}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    for opt_name, opt_default_gen in defaults.items():
 | 
	
		
			
				|  |  | +        if opt_name not in options or options[opt_name] is None:
 | 
	
		
			
				|  |  | +            options[opt_name] = opt_default_gen()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    context = DaemonContext(**options)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return context, context.close
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def parse_uid(uid):
 | 
	
		
			
				|  |  | +    """Parse user id.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    uid can be an interger (uid) or a string (username), if a username
 | 
	
		
			
				|  |  | +    the uid is taken from the password file.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    try:
 | 
	
		
			
				|  |  | +        return int(uid)
 | 
	
		
			
				|  |  | +    except ValueError:
 | 
	
		
			
				|  |  | +        return pwd.getpwnam(uid).pw_uid
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def parse_gid(gid):
 | 
	
		
			
				|  |  | +    """Parse group id.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    gid can be an integer (gid) or a string (group name), if a group name
 | 
	
		
			
				|  |  | +    the gid is taken from the password file.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    try:
 | 
	
		
			
				|  |  | +        return int(gid)
 | 
	
		
			
				|  |  | +    except ValueError:
 | 
	
		
			
				|  |  | +        return grp.getgrnam(gid).gr_gid
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def setegid(gid):
 | 
	
		
			
				|  |  | +    """Set effective group id."""
 | 
	
		
			
				|  |  | +    gid = parse_gid(gid)
 | 
	
		
			
				|  |  | +    if gid != os.getgid():
 | 
	
		
			
				|  |  | +        os.setegid
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def seteuid(uid):
 | 
	
		
			
				|  |  | +    """Set effective user id."""
 | 
	
		
			
				|  |  | +    uid = parse_uid(uid)
 | 
	
		
			
				|  |  | +    if uid != os.getuid():
 | 
	
		
			
				|  |  | +        os.seteuid(uid)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def set_effective_user(uid=None, gid=None):
 | 
	
		
			
				|  |  | +    """Change process privileges to new user/group.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    If uid and gid is set the effective user/group is set.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    If only uid is set, the effective uer is set, and the group is
 | 
	
		
			
				|  |  | +    set to the users primary group.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    If only gid is set, the effective group is set.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    uid = uid and parse_uid(uid)
 | 
	
		
			
				|  |  | +    gid = gid and parse_gid(gid)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if uid:
 | 
	
		
			
				|  |  | +        # If gid isn't defined, get the primary gid of the uer.
 | 
	
		
			
				|  |  | +        setegid(gid or pwd.getpwuid(uid).pw_gid)
 | 
	
		
			
				|  |  | +        seteuid(uid)
 | 
	
		
			
				|  |  | +    else:
 | 
	
		
			
				|  |  | +        gid and setegid(gid)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def reset_signal(signal_name):
 | 
	
		
			
				|  |  |      """Reset signal to the default signal handler.
 | 
	
	
		
			
				|  | @@ -60,12 +274,14 @@ def set_process_title(progname, info=None):
 | 
	
		
			
				|  |  |      return proctitle
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def set_mp_process_title(progname, info=None):
 | 
	
		
			
				|  |  | +def set_mp_process_title(progname, info=None, hostname=None):
 | 
	
		
			
				|  |  |      """Set the ps name using the multiprocessing process name.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      Only works if :mod:`setproctitle` is installed.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      """
 | 
	
		
			
				|  |  |      from multiprocessing.process import current_process
 | 
	
		
			
				|  |  | -    return set_process_title("%s.%s" % (progname, current_process().name),
 | 
	
		
			
				|  |  | +    if hostname:
 | 
	
		
			
				|  |  | +        progname = "%s@%s" % (progname, hostname.split(".")[0])
 | 
	
		
			
				|  |  | +    return set_process_title("%s:%s" % (progname, current_process().name),
 | 
	
		
			
				|  |  |                               info=info)
 |