| Index: third_party/psutil/psutil/_psposix.py
|
| diff --git a/third_party/psutil/psutil/_psposix.py b/third_party/psutil/psutil/_psposix.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cd860a5ff554ecfabccd7e79d85f05039d0cf022
|
| --- /dev/null
|
| +++ b/third_party/psutil/psutil/_psposix.py
|
| @@ -0,0 +1,240 @@
|
| +#!/usr/bin/env python
|
| +#
|
| +# $Id: _psposix.py 800 2010-11-12 21:51:25Z g.rodola $
|
| +#
|
| +
|
| +"""Routines common to all posix systems."""
|
| +
|
| +import os
|
| +import errno
|
| +import subprocess
|
| +import psutil
|
| +import socket
|
| +import re
|
| +import sys
|
| +import warnings
|
| +
|
| +try:
|
| + from collections import namedtuple
|
| +except ImportError:
|
| + from psutil.compat import namedtuple # python < 2.6
|
| +
|
| +from psutil.error import AccessDenied, NoSuchProcess
|
| +
|
| +
|
| +def pid_exists(pid):
|
| + """Check whether pid exists in the current process table."""
|
| + if pid < 0:
|
| + return False
|
| + try:
|
| + os.kill(pid, 0)
|
| + except OSError, e:
|
| + return e.errno == errno.EPERM
|
| + else:
|
| + return True
|
| +
|
| +
|
| +class LsofParser:
|
| + """A wrapper for lsof command line utility.
|
| + Executes lsof in subprocess and parses its output.
|
| + """
|
| + socket_table = {'TCP' : socket.SOCK_STREAM,
|
| + 'UDP' : socket.SOCK_DGRAM,
|
| + 'IPv4' : socket.AF_INET,
|
| + 'IPv6' : socket.AF_INET6}
|
| + _openfile_ntuple = namedtuple('openfile', 'path fd')
|
| + _connection_ntuple = namedtuple('connection', 'fd family type local_address '
|
| + 'remote_address status')
|
| +
|
| + def __init__(self, pid, name):
|
| + self.pid = pid
|
| + self.process_name = name
|
| +
|
| + def get_process_open_files(self):
|
| + """Return files opened by process by parsing lsof output."""
|
| + # Options:
|
| + # -i == network files only
|
| + # -a == ANDing of all options
|
| + # -p == process with given PID only
|
| + # -n == do not resolve IP addresses
|
| + # -P == do not resolve port numbers
|
| + # -w == suppresses warnings
|
| + # -F0nPt == (0) separate lines with "\x00"
|
| + # (n) file name
|
| + # (t) file type
|
| + # (f) file descriptr
|
| + cmd = "lsof -a -p %s -n -P -F0ftn" % self.pid
|
| + stdout = self.runcmd(cmd)
|
| + if not stdout:
|
| + return []
|
| + files = []
|
| + lines = stdout.split("\n")
|
| + del lines[0] # first line contains the PID
|
| + for line in lines:
|
| + if not line:
|
| + continue
|
| + line = line.strip("\x00")
|
| + fields = {}
|
| + for field in line.split("\x00"):
|
| + key, value = field[0], field[1:]
|
| + fields[key] = value
|
| + if not 't' in fields:
|
| + continue
|
| + _type = fields['t']
|
| + fd = fields['f']
|
| + name = fields['n']
|
| + if 'REG' in _type and fd.isdigit():
|
| + if not os.path.isfile(os.path.realpath(name)):
|
| + continue
|
| + ntuple = self._openfile_ntuple(name, int(fd))
|
| + files.append(ntuple)
|
| + return files
|
| +
|
| + def get_process_connections(self):
|
| + """Return connections opened by a process by parsing lsof output."""
|
| + # Options:
|
| + # -i == network files only
|
| + # -a == ANDing of all options
|
| + # -p == process with given PID only
|
| + # -n == do not resolve IP addresses
|
| + # -P == do not resolve port numbers
|
| + # -w == suppresses warnings
|
| + # -F0nPt == (0) separate lines with "\x00"
|
| + # (n) and show internet addresses only
|
| + # (P) protocol type (TCP, UPD, Unix)
|
| + # (t) socket family (IPv4, IPv6)
|
| + # (T) connection status
|
| + # (f) file descriptors
|
| + cmd = "lsof -p %s -i -a -F0nPtTf -n -P" % self.pid
|
| + stdout = self.runcmd(cmd)
|
| + if not stdout:
|
| + return []
|
| + connections = []
|
| + lines = stdout.split()
|
| + del lines[0] # first line contains the PID
|
| + for line in lines:
|
| + line = line.strip("\x00")
|
| + fields = {}
|
| + for field in line.split("\x00"):
|
| + if field.startswith('T'):
|
| + key, value = field.split('=')
|
| + else:
|
| + key, value = field[0], field[1:]
|
| + fields[key] = value
|
| +
|
| + # XXX - might trow execption; needs "continue on unsupported
|
| + # family or type" (e.g. unix sockets)
|
| + # we consider TCP and UDP sockets only
|
| + stype = fields['P']
|
| + if stype not in self.socket_table:
|
| + continue
|
| + else:
|
| + _type = self.socket_table[fields['P']]
|
| + family = self.socket_table[fields['t']]
|
| + peers = fields['n']
|
| + fd = int(fields['f'])
|
| + if _type == socket.SOCK_STREAM:
|
| + status = fields['TST']
|
| + # OS X shows "CLOSED" instead of "CLOSE" so translate them
|
| + if status == "CLOSED":
|
| + status = "CLOSE"
|
| + else:
|
| + status = ""
|
| + if not '->' in peers:
|
| + local_addr = self._normaddress(peers, family)
|
| + remote_addr = ()
|
| + # OS X processes e.g. SystemUIServer can return *:* for local
|
| + # address, so we return 0 and move on
|
| + if local_addr == 0:
|
| + continue
|
| + else:
|
| + local_addr, remote_addr = peers.split("->")
|
| + local_addr = self._normaddress(local_addr, family)
|
| + remote_addr = self._normaddress(remote_addr, family)
|
| +
|
| + conn = self._connection_ntuple(fd, family, _type, local_addr,
|
| + remote_addr, status)
|
| + connections.append(conn)
|
| +
|
| + return connections
|
| +
|
| + def runcmd(self, cmd):
|
| + """Expects an lsof-related command line, execute it in a
|
| + subprocess and return its output.
|
| + If something goes bad stderr is parsed and proper exceptions
|
| + raised as necessary.
|
| + """
|
| + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
|
| + stderr=subprocess.PIPE)
|
| + stdout, stderr = p.communicate()
|
| + if sys.version_info >= (3,):
|
| + stdout, stderr = map(lambda x: x.decode(sys.stdout.encoding),
|
| + (stdout, stderr))
|
| + if stderr:
|
| + utility = cmd.split(' ')[0]
|
| + if self._which(utility) is None:
|
| + msg = "this functionnality requires %s command line utility " \
|
| + "to be installed on the system" % utility
|
| + raise NotImplementedError(msg)
|
| + elif "permission denied" in stderr.lower():
|
| + # "permission denied" can be found also in case of zombie
|
| + # processes;
|
| + p = psutil.Process(self.pid)
|
| + if not p.is_running():
|
| + raise NoSuchProcess(self.pid, self.process_name)
|
| + raise AccessDenied(self.pid, self.process_name)
|
| + elif "lsof: warning:" in stderr.lower():
|
| + # usually appears when lsof is run for the first time and
|
| + # complains about missing cache file in user home
|
| + warnings.warn(stderr, RuntimeWarning)
|
| + else:
|
| + # this must be considered an application bug
|
| + raise RuntimeError(stderr)
|
| + if not stdout:
|
| + p = psutil.Process(self.pid)
|
| + if not p.is_running():
|
| + raise NoSuchProcess(self.pid, self.process_name)
|
| + return ""
|
| + return stdout
|
| +
|
| + @staticmethod
|
| + def _which(program):
|
| + """Same as UNIX which command. Return None on command not found."""
|
| + def is_exe(fpath):
|
| + return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
| +
|
| + fpath, fname = os.path.split(program)
|
| + if fpath:
|
| + if is_exe(program):
|
| + return program
|
| + else:
|
| + for path in os.environ["PATH"].split(os.pathsep):
|
| + exe_file = os.path.join(path, program)
|
| + if is_exe(exe_file):
|
| + return exe_file
|
| + return None
|
| +
|
| + @staticmethod
|
| + def _normaddress(addr, family):
|
| + """Normalize an IP address."""
|
| + assert family in (socket.AF_INET, socket.AF_INET6), "unsupported family"
|
| + if family == socket.AF_INET:
|
| + ip, port = addr.split(':')
|
| + else:
|
| + if "]" in addr:
|
| + ip, port = re.findall('\[([^]]+)\]:([0-9]+)', addr)[0]
|
| + else:
|
| + ip, port = addr.split(':')
|
| + if ip == '*':
|
| + if family == socket.AF_INET:
|
| + ip = "0.0.0.0"
|
| + elif family == socket.AF_INET6:
|
| + ip = "::"
|
| + # OS X can have some procs e.g. SystemUIServer listening on *:*
|
| + else:
|
| + raise ValueError("invalid IP %s" %addr)
|
| + if port == "*":
|
| + return 0
|
| + return (ip, int(port))
|
| +
|
| +
|
|
|