~ubuntu-branches/ubuntu/oneiric/python-psutil/oneiric

« back to all changes in this revision

Viewing changes to psutil/_psposix.py

  • Committer: Bazaar Package Importer
  • Author(s): Sandro Tosi
  • Date: 2011-04-04 20:26:42 UTC
  • mfrom: (2.1.3 sid)
  • Revision ID: james.westby@ubuntu.com-20110404202642-u2fyar19eabqb2mn
Tags: 0.2.1-1
* New upstream release
* debian/copyright
  - extended packaging copyright years
* debian/rules
  - use the correct PYTHONPATH when running tests, for all supported versions
* debian/control
  - it now contains also extensions, so it's an arch:any package
  - move python-support from b-d-i to b-d
  - we now need python-all-dev in b-d
  - added procps to b-d, needed to run tests
* debian/{control, pyversion}
  - removed pyversion, replaced by XS-P-V field

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# $Id: _psposix.py 924 2011-02-18 00:17:55Z g.rodola $
 
4
#
 
5
 
 
6
"""Routines common to all posix systems."""
 
7
 
 
8
import os
 
9
import errno
 
10
import subprocess
 
11
import psutil
 
12
import socket
 
13
import re
 
14
import sys
 
15
import warnings
 
16
import time
 
17
 
 
18
from psutil.error import AccessDenied, NoSuchProcess, TimeoutExpired
 
19
from psutil._compat import namedtuple
 
20
 
 
21
 
 
22
def pid_exists(pid):
 
23
    """Check whether pid exists in the current process table."""
 
24
    if pid < 0:
 
25
        return False
 
26
    try:
 
27
        os.kill(pid, 0)
 
28
    except OSError, e:
 
29
        return e.errno == errno.EPERM
 
30
    else:
 
31
        return True
 
32
 
 
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.
 
36
 
 
37
    If pid is not a children of os.getpid() (current process) just
 
38
    waits until the process disappears and return None.
 
39
 
 
40
    If pid does not exist at all return None immediately.
 
41
 
 
42
    Raise TimeoutExpired on timeout expired.
 
43
    """
 
44
    def check_timeout():
 
45
        if timeout:
 
46
            if time.time() >= stop_at:
 
47
                raise TimeoutExpired
 
48
            time.sleep(0.001)
 
49
 
 
50
    if timeout:
 
51
        waitcall = lambda: os.waitpid(pid, os.WNOHANG)
 
52
        stop_at = time.time() + timeout
 
53
    else:
 
54
        waitcall = lambda: os.waitpid(pid, 0)
 
55
 
 
56
    while 1:
 
57
        try:
 
58
            retpid, status = waitcall()
 
59
        except OSError, err:
 
60
            if err.errno == errno.EINTR:
 
61
                check_timeout()
 
62
                continue
 
63
            elif err.errno == errno.ECHILD:
 
64
                # not a child of os.getpid(): poll until pid has
 
65
                # disappeared and return None instead
 
66
                while 1:
 
67
                    if pid_exists(pid):
 
68
                        check_timeout()
 
69
                    else:
 
70
                        return
 
71
            else:
 
72
                raise
 
73
        else:
 
74
            if retpid == 0:
 
75
                check_timeout()
 
76
                continue
 
77
            # process exited due to a signal; return the integer of
 
78
            # that signal
 
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)
 
85
            else:
 
86
                # should never happen
 
87
                raise RuntimeError("unknown process exit status")
 
88
 
 
89
 
 
90
class LsofParser:
 
91
    """A wrapper for lsof command line utility.
 
92
    Executes lsof in subprocess and parses its output.
 
93
    """
 
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')
 
101
 
 
102
    def __init__(self, pid, name):
 
103
        self.pid = pid
 
104
        self.process_name = name
 
105
 
 
106
    def get_process_open_files(self):
 
107
        """Return files opened by process by parsing lsof output."""
 
108
        # Options:
 
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"
 
116
        #           (n) file name
 
117
        #           (t) file type
 
118
        #           (f) file descriptr
 
119
        cmd = "lsof -a -p %s -n -P -F0ftn" % self.pid
 
120
        stdout = self.runcmd(cmd)
 
121
        if not stdout:
 
122
            return []
 
123
        files = []
 
124
        lines = stdout.split("\n")
 
125
        del lines[0]  # first line contains the PID
 
126
        for line in lines:
 
127
            if not line:
 
128
                continue
 
129
            line = line.strip("\x00")
 
130
            fields = {}
 
131
            for field in line.split("\x00"):
 
132
                key, value = field[0], field[1:]
 
133
                fields[key] = value
 
134
            if not 't' in fields:
 
135
                continue
 
136
            _type = fields['t']
 
137
            fd = fields['f']
 
138
            name = fields['n']
 
139
            if 'REG' in _type and fd.isdigit():
 
140
                if not os.path.isfile(os.path.realpath(name)):
 
141
                    continue
 
142
                ntuple = self._openfile_ntuple(name, int(fd))
 
143
                files.append(ntuple)
 
144
        return files
 
145
 
 
146
    def get_process_connections(self):
 
147
        """Return connections opened by a process by parsing lsof output."""
 
148
        # Options:
 
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)
 
163
        if not stdout:
 
164
            return []
 
165
        connections = []
 
166
        lines = stdout.split()
 
167
        del lines[0]  # first line contains the PID
 
168
        for line in lines:
 
169
            line = line.strip("\x00")
 
170
            fields = {}
 
171
            for field in line.split("\x00"):
 
172
                if field.startswith('T'):
 
173
                    key, value = field.split('=')
 
174
                else:
 
175
                    key, value = field[0], field[1:]
 
176
                fields[key] = value
 
177
 
 
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
 
181
            stype = fields['P']
 
182
            if stype not in self.socket_table:
 
183
                continue
 
184
            else:
 
185
                _type = self.socket_table[fields['P']]
 
186
            family = self.socket_table[fields['t']]
 
187
            peers = fields['n']
 
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":
 
193
                    status = "CLOSE"
 
194
            else:
 
195
                status = ""
 
196
            if not '->' in peers:
 
197
                local_addr = self._normaddress(peers, family)
 
198
                remote_addr = ()
 
199
                # OS X processes e.g. SystemUIServer can return *:* for local
 
200
                # address, so we return 0 and move on
 
201
                if local_addr == 0:
 
202
                    continue
 
203
            else:
 
204
                local_addr, remote_addr = peers.split("->")
 
205
                local_addr = self._normaddress(local_addr, family)
 
206
                remote_addr = self._normaddress(remote_addr, family)
 
207
 
 
208
            conn = self._connection_ntuple(fd, family, _type, local_addr,
 
209
                                           remote_addr, status)
 
210
            connections.append(conn)
 
211
 
 
212
        return connections
 
213
 
 
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
 
218
        raised as necessary.
 
219
        """
 
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),
 
225
                                 (stdout, stderr))
 
226
        if stderr:
 
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
 
234
                # processes;
 
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)
 
243
            else:
 
244
                # this must be considered an application bug
 
245
                raise RuntimeError(stderr)
 
246
        if not stdout:
 
247
            p = psutil.Process(self.pid)
 
248
            if not p.is_running():
 
249
                raise NoSuchProcess(self.pid, self.process_name)
 
250
            return ""
 
251
        return stdout
 
252
 
 
253
    @staticmethod
 
254
    def _which(program):
 
255
        """Same as UNIX which command. Return None on command not found."""
 
256
        def is_exe(fpath):
 
257
            return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
 
258
 
 
259
        fpath, fname = os.path.split(program)
 
260
        if fpath:
 
261
            if is_exe(program):
 
262
                return program
 
263
        else:
 
264
            for path in os.environ["PATH"].split(os.pathsep):
 
265
                exe_file = os.path.join(path, program)
 
266
                if is_exe(exe_file):
 
267
                    return exe_file
 
268
        return None
 
269
 
 
270
    @staticmethod
 
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(':')
 
276
        else:
 
277
            if "]" in addr:
 
278
                ip, port = re.findall('\[([^]]+)\]:([0-9]+)', addr)[0]
 
279
            else:
 
280
                ip, port = addr.split(':')
 
281
        if ip == '*':
 
282
            if family == socket.AF_INET:
 
283
                ip = "0.0.0.0"
 
284
            elif family == socket.AF_INET6:
 
285
                ip = "::"
 
286
            # OS X can have some procs e.g. SystemUIServer listening on *:*
 
287
            else:
 
288
                raise ValueError("invalid IP %s" %addr)
 
289
            if port == "*":
 
290
                return 0
 
291
        return (ip, int(port))
 
292
 
 
293