3
# $Id: _psposix.py 924 2011-02-18 00:17:55Z g.rodola $
6
"""Routines common to all posix systems."""
18
from psutil.error import AccessDenied, NoSuchProcess, TimeoutExpired
19
from psutil._compat import namedtuple
23
"""Check whether pid exists in the current process table."""
29
return e.errno == errno.EPERM
33
def wait_pid(pid, timeout=None):
34
"""Wait for process with pid 'pid' to terminate and return its
35
exit status code as an integer.
37
If pid is not a children of os.getpid() (current process) just
38
waits until the process disappears and return None.
40
If pid does not exist at all return None immediately.
42
Raise TimeoutExpired on timeout expired.
46
if time.time() >= stop_at:
51
waitcall = lambda: os.waitpid(pid, os.WNOHANG)
52
stop_at = time.time() + timeout
54
waitcall = lambda: os.waitpid(pid, 0)
58
retpid, status = waitcall()
60
if err.errno == errno.EINTR:
63
elif err.errno == errno.ECHILD:
64
# not a child of os.getpid(): poll until pid has
65
# disappeared and return None instead
77
# process exited due to a signal; return the integer of
79
if os.WIFSIGNALED(status):
80
return os.WTERMSIG(status)
81
# process exited using exit(2) system call; return the
82
# integer exit(2) system call has been called with
83
elif os.WIFEXITED(status):
84
return os.WEXITSTATUS(status)
87
raise RuntimeError("unknown process exit status")
91
"""A wrapper for lsof command line utility.
92
Executes lsof in subprocess and parses its output.
94
socket_table = {'TCP' : socket.SOCK_STREAM,
95
'UDP' : socket.SOCK_DGRAM,
96
'IPv4' : socket.AF_INET,
97
'IPv6' : socket.AF_INET6}
98
_openfile_ntuple = namedtuple('openfile', 'path fd')
99
_connection_ntuple = namedtuple('connection', 'fd family type local_address '
100
'remote_address status')
102
def __init__(self, pid, name):
104
self.process_name = name
106
def get_process_open_files(self):
107
"""Return files opened by process by parsing lsof output."""
109
# -i == network files only
110
# -a == ANDing of all options
111
# -p == process with given PID only
112
# -n == do not resolve IP addresses
113
# -P == do not resolve port numbers
114
# -w == suppresses warnings
115
# -F0nPt == (0) separate lines with "\x00"
119
cmd = "lsof -a -p %s -n -P -F0ftn" % self.pid
120
stdout = self.runcmd(cmd)
124
lines = stdout.split("\n")
125
del lines[0] # first line contains the PID
129
line = line.strip("\x00")
131
for field in line.split("\x00"):
132
key, value = field[0], field[1:]
134
if not 't' in fields:
139
if 'REG' in _type and fd.isdigit():
140
if not os.path.isfile(os.path.realpath(name)):
142
ntuple = self._openfile_ntuple(name, int(fd))
146
def get_process_connections(self):
147
"""Return connections opened by a process by parsing lsof output."""
149
# -i == network files only
150
# -a == ANDing of all options
151
# -p == process with given PID only
152
# -n == do not resolve IP addresses
153
# -P == do not resolve port numbers
154
# -w == suppresses warnings
155
# -F0nPt == (0) separate lines with "\x00"
156
# (n) and show internet addresses only
157
# (P) protocol type (TCP, UPD, Unix)
158
# (t) socket family (IPv4, IPv6)
159
# (T) connection status
160
# (f) file descriptors
161
cmd = "lsof -p %s -i -a -F0nPtTf -n -P" % self.pid
162
stdout = self.runcmd(cmd)
166
lines = stdout.split()
167
del lines[0] # first line contains the PID
169
line = line.strip("\x00")
171
for field in line.split("\x00"):
172
if field.startswith('T'):
173
key, value = field.split('=')
175
key, value = field[0], field[1:]
178
# XXX - might trow execption; needs "continue on unsupported
179
# family or type" (e.g. unix sockets)
180
# we consider TCP and UDP sockets only
182
if stype not in self.socket_table:
185
_type = self.socket_table[fields['P']]
186
family = self.socket_table[fields['t']]
188
fd = int(fields['f'])
189
if _type == socket.SOCK_STREAM:
190
status = fields['TST']
191
# OS X shows "CLOSED" instead of "CLOSE" so translate them
192
if status == "CLOSED":
196
if not '->' in peers:
197
local_addr = self._normaddress(peers, family)
199
# OS X processes e.g. SystemUIServer can return *:* for local
200
# address, so we return 0 and move on
204
local_addr, remote_addr = peers.split("->")
205
local_addr = self._normaddress(local_addr, family)
206
remote_addr = self._normaddress(remote_addr, family)
208
conn = self._connection_ntuple(fd, family, _type, local_addr,
210
connections.append(conn)
214
def runcmd(self, cmd):
215
"""Expects an lsof-related command line, execute it in a
216
subprocess and return its output.
217
If something goes bad stderr is parsed and proper exceptions
220
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
221
stderr=subprocess.PIPE)
222
stdout, stderr = p.communicate()
223
if sys.version_info >= (3,):
224
stdout, stderr = map(lambda x: x.decode(sys.stdout.encoding),
227
utility = cmd.split(' ')[0]
228
if self._which(utility) is None:
229
msg = "this functionnality requires %s command line utility " \
230
"to be installed on the system" % utility
231
raise NotImplementedError(msg)
232
elif "permission denied" in stderr.lower():
233
# "permission denied" can be found also in case of zombie
235
p = psutil.Process(self.pid)
236
if not p.is_running():
237
raise NoSuchProcess(self.pid, self.process_name)
238
raise AccessDenied(self.pid, self.process_name)
239
elif "lsof: warning:" in stderr.lower():
240
# usually appears when lsof is run for the first time and
241
# complains about missing cache file in user home
242
warnings.warn(stderr, RuntimeWarning)
244
# this must be considered an application bug
245
raise RuntimeError(stderr)
247
p = psutil.Process(self.pid)
248
if not p.is_running():
249
raise NoSuchProcess(self.pid, self.process_name)
255
"""Same as UNIX which command. Return None on command not found."""
257
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
259
fpath, fname = os.path.split(program)
264
for path in os.environ["PATH"].split(os.pathsep):
265
exe_file = os.path.join(path, program)
271
def _normaddress(addr, family):
272
"""Normalize an IP address."""
273
assert family in (socket.AF_INET, socket.AF_INET6), "unsupported family"
274
if family == socket.AF_INET:
275
ip, port = addr.split(':')
278
ip, port = re.findall('\[([^]]+)\]:([0-9]+)', addr)[0]
280
ip, port = addr.split(':')
282
if family == socket.AF_INET:
284
elif family == socket.AF_INET6:
286
# OS X can have some procs e.g. SystemUIServer listening on *:*
288
raise ValueError("invalid IP %s" %addr)
291
return (ip, int(port))