-
Notifications
You must be signed in to change notification settings - Fork 14
/
yapdi.py
141 lines (123 loc) · 4.17 KB
/
yapdi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# !/usr/bin/env python
'''
#
# YapDi - Yet another python Daemon implementation <https://github.com/kasun/YapDi>
# Author Kasun Herath <kasunh01@gmail.com>
#
'''
from signal import SIGTERM
import sys, atexit, os, pwd
import time
OPERATION_SUCCESSFUL = 0
OPERATION_FAILED = 1
INSTANCE_ALREADY_RUNNING = 2
INSTANCE_NOT_RUNNING = 3
SET_USER_FAILED = 4
class Daemon:
def __init__(self, pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
# If pidfile is not specified derive it by supplying scriptname
if not pidfile:
self.pidfile = self.get_pidfile(sys.argv[0])
else:
self.pidfile = pidfile
# user to run under
self.daemon_user = None
def daemonize(self):
''' Daemonize the current process and return '''
if self.status():
return INSTANCE_ALREADY_RUNNING
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
return OPERATION_FAILED
# decouple from parent environment
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError, e:
return OPERATION_FAILED
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
se = file(self.stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
file(self.pidfile,'w+').write("%s\n" % pid)
# If daemon user is set change current user to self.daemon_user
if self.daemon_user:
try:
uid = pwd.getpwnam(self.daemon_user)[2]
os.setuid(uid)
except NameError, e:
return SET_USER_FAILED
except OSError, e:
return SET_USER_FAILED
return OPERATION_SUCCESSFUL
def delpid(self):
os.remove(self.pidfile)
def kill(self):
''' kill any running instance '''
# check if an instance is not running
pid = self.status()
if not pid:
return INSTANCE_NOT_RUNNING
# Try killing the daemon process
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
return OPERATION_FAILED
return OPERATION_SUCCESSFUL
def restart(self):
''' Restart an instance; If an instance is already running kill it and start else just start '''
if self.status():
kill_status = self.kill()
if kill_status == OPERATION_FAILED:
return kill_status
return self.daemonize()
def status(self):
''' check whether an instance is already running. If running return pid or else False '''
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
return None
try:
os.kill(pid, 0)
except OSError:
return None
return pid
def set_user(self, username):
''' Set user under which the daemonized process should be run '''
if not isinstance(username, str):
raise TypeError('username should be of type str')
self.daemon_user = username
def get_pidfile(self, scriptname):
''' Return file name to save pid given original script name '''
pidpath_components = scriptname.split('/')[0:-1]
pidpath_components.append('.yapdi.pid')
return '/'.join(pidpath_components)