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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
"""File utilities
>>> import tempfile, os
>>> pidfn = tempfile.mktemp('.pid')
>>> write_atomic(pidfn, "1")
>>> write_atomic(pidfn, "2")
>>> os.remove(pidfn)
>>> write_atomic(pidfn, "1", '.bak')
>>> write_atomic(pidfn, "2", '.bak')
>>> os.remove(pidfn)
"""
import sys
import os
import errno
__all__ = ['write_atomic', 'signal_pidfile']
# non-win32
def write_atomic(fn, data, bakext=None, mode='b'):
"""Write file with rename."""
if mode not in ['', 'b', 't']:
raise ValueError("unsupported fopen mode")
# write new data to tmp file
fn2 = fn + '.new'
f = open(fn2, 'w' + mode)
f.write(data)
f.close()
# link old data to bak file
if bakext:
if bakext.find('/') >= 0:
raise ValueError("invalid bakext")
fnb = fn + bakext
try:
os.unlink(fnb)
except OSError, e:
if e.errno != errno.ENOENT:
raise
try:
os.link(fn, fnb)
except OSError, e:
if e.errno != errno.ENOENT:
raise
# win32 does not like replace
if sys.platform == 'win32':
try:
os.remove(fn)
except:
pass
# atomically replace file
os.rename(fn2, fn)
def signal_pidfile(pidfile, sig):
"""Send a signal to process whose ID is located in pidfile.
Read only first line of pidfile to support multiline
pidfiles like postmaster.pid.
Returns True is successful, False if pidfile does not exist
or process itself is dead. Any other errors will passed
as exceptions."""
ln = ''
try:
f = open(pidfile, 'r')
ln = f.readline().strip()
f.close()
pid = int(ln)
if sig == 0 and sys.platform == 'win32':
return win32_detect_pid(pid)
os.kill(pid, sig)
return True
except IOError, ex:
if ex.errno != errno.ENOENT:
raise
except OSError, ex:
if ex.errno != errno.ESRCH:
raise
except ValueError, ex:
# this leaves slight race when someone is just creating the file,
# but more common case is old empty file.
if not ln:
return False
raise ValueError('Corrupt pidfile: %s' % pidfile)
return False
def win32_detect_pid(pid):
"""Process detection for win32."""
# avoid pywin32 dependecy, use ctypes instead
import ctypes
# win32 constants
PROCESS_QUERY_INFORMATION = 1024
STILL_ACTIVE = 259
ERROR_INVALID_PARAMETER = 87
ERROR_ACCESS_DENIED = 5
# Load kernel32.dll
k = ctypes.windll.kernel32
OpenProcess = k.OpenProcess
OpenProcess.restype = ctypes.c_void_p
# query pid exit code
h = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid)
if h == None:
err = k.GetLastError()
if err == ERROR_INVALID_PARAMETER:
return False
if err == ERROR_ACCESS_DENIED:
return True
raise OSError(errno.EFAULT, "Unknown win32error: " + str(err))
code = ctypes.c_int()
k.GetExitCodeProcess(h, ctypes.byref(code))
k.CloseHandle(h)
return code.value == STILL_ACTIVE
def win32_write_atomic(fn, data, bakext=None, mode='b'):
"""Write file with rename for win32."""
if mode not in ['', 'b', 't']:
raise ValueError("unsupported fopen mode")
# write new data to tmp file
fn2 = fn + '.new'
f = open(fn2, 'w' + mode)
f.write(data)
f.close()
# move old data to bak file
if bakext:
if bakext.find('/') >= 0:
raise ValueError("invalid bakext")
fnb = fn + bakext
try:
os.remove(fnb)
except OSError, e:
if e.errno != errno.ENOENT:
raise
try:
os.rename(fn, fnb)
except OSError, e:
if e.errno != errno.ENOENT:
raise
else:
try:
os.remove(fn)
except:
pass
# replace file
os.rename(fn2, fn)
if sys.platform == 'win32':
write_atomic = win32_write_atomic
if __name__ == '__main__':
import doctest
doctest.testmod()
|