├── LICENSE ├── README.md ├── examples ├── basic.py └── hellodaemon.py ├── setup.py └── yapdi.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Kasun Herath 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice 4 | and this permission notice appear in all copies. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 7 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 8 | DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 9 | SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YapDi - Yet another python Daemon implementation # 2 | 3 | ## Overview ## 4 | 5 | As the name implies YapDi is yet another python daemon implementation. Python 2.x standard library didn't have a daemonizing module. It is one of those rare few modules python standard library lacked that put the phrase "batteries included" to shame. There is [PEP 3143](http://www.python.org/dev/peps/pep-3143/) which introduces a daemonizing package to python 3.x standard library. 6 | 7 | Python community is scattered with so many daemon implementations and this is one of them. This module is a modified version of a code originally posted [here](http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/). So any compliments should go there first. 8 | 9 | ## Installation ## 10 | 11 | python setup.py install 12 | 13 | ## Basic Usage ## 14 | import yapdi 15 | 16 | # Creating a yapdi daemon instance 17 | daemon = yapdi.Daemon() 18 | 19 | # You can also supply a pid file name at daemon instance creation time 20 | daemon = yapdi.Daemon(pidfile='/var/run/.myservice.pid') 21 | 22 | # Daemonizing an instance; Any code placed under this would get executed in daemon mode 23 | daemon.daemonize() 24 | 25 | # Stopping a running instance 26 | daemon.kill() 27 | 28 | # Restarting an instance; If an instance is already running it would be killed and started again, else would just start 29 | daemon.restart() 30 | 31 | # Checking whether an instance is running 32 | if daemon.status(): 33 | print('An instance is already running') 34 | else: 35 | print('No instance is running') 36 | 37 | # Running a daemonized instance as a different user; any code below these two lines would get executed in daemon mode as user 'user123' 38 | daemon.set_user('user123') 39 | daemon.daemonize() 40 | 41 | -------------------------------------------------------------------------------- /examples/basic.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | 3 | ''' YapDi Example - Demonstrate basic YapDi functionality. 4 | Author - Kasun Herath 5 | USAGE - python basic.py start|stop|restart 6 | 7 | python basic.py start would execute count() in daemon mode 8 | if there is no instance already running. 9 | 10 | count() prints a counting number to syslog. To view output of 11 | count() execute a follow tail to syslog file. Most probably 12 | tail -f /var/log/syslog under linux and tail -f /var/log/messages 13 | under BSD. 14 | 15 | python basic.py stop would kill any running instance. 16 | 17 | python basic.py restart would kill any running instance; and 18 | start an instance. ''' 19 | 20 | import sys 21 | import syslog 22 | import time 23 | 24 | import yapdi 25 | 26 | COMMAND_START = 'start' 27 | COMMAND_STOP = 'stop' 28 | COMMAND_RESTART = 'restart' 29 | 30 | def usage(): 31 | print("USAGE: python %s %s|%s|%s" % (sys.argv[0], COMMAND_START, COMMAND_STOP, COMMAND_RESTART)) 32 | 33 | # Invalid executions 34 | if len(sys.argv) < 2 or sys.argv[1] not in [COMMAND_START, COMMAND_STOP, COMMAND_RESTART]: 35 | usage() 36 | exit() 37 | 38 | def count(): 39 | ''' Outputs a counting value to syslog. Sleeps for 1 second between counts ''' 40 | i = 0 41 | while 1: 42 | syslog.openlog("yapdi-example.info", 0, syslog.LOG_USER) 43 | syslog.syslog(syslog.LOG_NOTICE, 'Counting %s' % (i)) 44 | i += 1 45 | time.sleep(1) 46 | 47 | if sys.argv[1] == COMMAND_START: 48 | daemon = yapdi.Daemon() 49 | 50 | # Check whether an instance is already running 51 | if daemon.status(): 52 | print("An instance is already running.") 53 | exit() 54 | retcode = daemon.daemonize() 55 | 56 | # Execute if daemonization was successful else exit 57 | if retcode == yapdi.OPERATION_SUCCESSFUL: 58 | count() 59 | else: 60 | print('Daemonization failed') 61 | 62 | elif sys.argv[1] == COMMAND_STOP: 63 | daemon = yapdi.Daemon() 64 | 65 | # Check whether no instance is running 66 | if not daemon.status(): 67 | print("No instance running.") 68 | exit() 69 | retcode = daemon.kill() 70 | if retcode == yapdi.OPERATION_FAILED: 71 | print('Trying to stop running instance failed') 72 | 73 | elif sys.argv[1] == COMMAND_RESTART: 74 | daemon = yapdi.Daemon() 75 | retcode = daemon.restart() 76 | 77 | # Execute if daemonization was successful else exit 78 | if retcode == yapdi.OPERATION_SUCCESSFUL: 79 | count() 80 | else: 81 | print('Daemonization failed') 82 | -------------------------------------------------------------------------------- /examples/hellodaemon.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | 3 | ''' YapDi Most basic Example - Prints a 'Hello Daemon' in daemon mode. This is to just to demonstrate how to run a statement(s) in daemon mode. 4 | The output will not be visible. 5 | 6 | Author - Kasun Herath 7 | USAGE - python hellodaemon.py ''' 8 | 9 | import yapdi 10 | 11 | daemon = yapdi.Daemon() 12 | retcode = daemon.daemonize() 13 | 14 | # This would run in daemon mode; output is not visible 15 | if retcode == yapdi.OPERATION_SUCCESSFUL: 16 | print('Hello Daemon') 17 | 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='yapdi', 6 | description='Yet another python Daemon implementation', 7 | author='Kasun Herath', 8 | author_email='kasunh01@gmail.com', 9 | py_modules=['yapdi'], 10 | ) 11 | 12 | 13 | -------------------------------------------------------------------------------- /yapdi.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | ''' 3 | # 4 | # YapDi - Yet another python Daemon implementation 5 | # Author Kasun Herath 6 | # 7 | ''' 8 | 9 | from signal import SIGTERM 10 | import sys, atexit, os, pwd 11 | import time 12 | 13 | OPERATION_SUCCESSFUL = 0 14 | OPERATION_FAILED = 1 15 | INSTANCE_ALREADY_RUNNING = 2 16 | INSTANCE_NOT_RUNNING = 3 17 | SET_USER_FAILED = 4 18 | 19 | class Daemon: 20 | def __init__(self, pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 21 | self.stdin = stdin 22 | self.stdout = stdout 23 | self.stderr = stderr 24 | 25 | # If pidfile is not specified derive it by supplying scriptname 26 | if not pidfile: 27 | self.pidfile = self.get_pidfile(sys.argv[0]) 28 | else: 29 | self.pidfile = pidfile 30 | 31 | # user to run under 32 | self.daemon_user = None 33 | 34 | def daemonize(self): 35 | ''' Daemonize the current process and return ''' 36 | if self.status(): 37 | return INSTANCE_ALREADY_RUNNING 38 | try: 39 | pid = os.fork() 40 | if pid > 0: 41 | # exit first parent 42 | sys.exit(0) 43 | except OSError, e: 44 | return OPERATION_FAILED 45 | 46 | # decouple from parent environment 47 | os.setsid() 48 | os.umask(0) 49 | 50 | # do second fork 51 | try: 52 | pid = os.fork() 53 | if pid > 0: 54 | # exit from second parent 55 | sys.exit(0) 56 | except OSError, e: 57 | return OPERATION_FAILED 58 | 59 | # redirect standard file descriptors 60 | sys.stdout.flush() 61 | sys.stderr.flush() 62 | si = file(self.stdin, 'r') 63 | so = file(self.stdout, 'a+') 64 | se = file(self.stderr, 'a+', 0) 65 | os.dup2(si.fileno(), sys.stdin.fileno()) 66 | os.dup2(so.fileno(), sys.stdout.fileno()) 67 | os.dup2(se.fileno(), sys.stderr.fileno()) 68 | 69 | # write pidfile 70 | atexit.register(self.delpid) 71 | pid = str(os.getpid()) 72 | file(self.pidfile,'w+').write("%s\n" % pid) 73 | 74 | # If daemon user is set change current user to self.daemon_user 75 | if self.daemon_user: 76 | try: 77 | uid = pwd.getpwnam(self.daemon_user)[2] 78 | os.setuid(uid) 79 | except NameError, e: 80 | return SET_USER_FAILED 81 | except OSError, e: 82 | return SET_USER_FAILED 83 | return OPERATION_SUCCESSFUL 84 | 85 | def delpid(self): 86 | os.remove(self.pidfile) 87 | 88 | def kill(self): 89 | ''' kill any running instance ''' 90 | # check if an instance is not running 91 | pid = self.status() 92 | if not pid: 93 | return INSTANCE_NOT_RUNNING 94 | 95 | # Try killing the daemon process 96 | try: 97 | while 1: 98 | os.kill(pid, SIGTERM) 99 | time.sleep(0.1) 100 | except OSError, err: 101 | err = str(err) 102 | if err.find("No such process") > 0: 103 | if os.path.exists(self.pidfile): 104 | os.remove(self.pidfile) 105 | else: 106 | return OPERATION_FAILED 107 | return OPERATION_SUCCESSFUL 108 | 109 | def restart(self): 110 | ''' Restart an instance; If an instance is already running kill it and start else just start ''' 111 | if self.status(): 112 | kill_status = self.kill() 113 | if kill_status == OPERATION_FAILED: 114 | return kill_status 115 | return self.daemonize() 116 | 117 | def status(self): 118 | ''' check whether an instance is already running. If running return pid or else False ''' 119 | try: 120 | pf = file(self.pidfile,'r') 121 | pid = int(pf.read().strip()) 122 | pf.close() 123 | except IOError: 124 | return None 125 | try: 126 | os.kill(pid, 0) 127 | except OSError: 128 | return None 129 | return pid 130 | 131 | def set_user(self, username): 132 | ''' Set user under which the daemonized process should be run ''' 133 | if not isinstance(username, str): 134 | raise TypeError('username should be of type str') 135 | self.daemon_user = username 136 | 137 | def get_pidfile(self, scriptname): 138 | ''' Return file name to save pid given original script name ''' 139 | pidpath_components = scriptname.split('/')[0:-1] 140 | pidpath_components.append('.yapdi.pid') 141 | return '/'.join(pidpath_components) 142 | --------------------------------------------------------------------------------