#!/usr/bin/python
#
# proc-surfer.py
VERSION = "1.0.15"
# (c) 2005-2006 Gene Thomas
# gene@genethomas.com
# http://genethomas.com
#
# provides a HTML interface to the /proc filesystem
# cross referencing:
# - processes
# - files
# - sockets
# - pipes
# - users
# - disk usage
# - top
# - IP addresses
# - DNS domain names
# lists relevant section of proc's man page alongside the output
#
# the latest version is available from
# http://genethomas.com/software/proc-surfer.py
#
# usage: proc-surfer.py [http-port]
# if no http-port is given a free one will be used and
# firefox launched to browse it.
#
# disk-usage is saved for speed, to re-scan hit refresh in your browser.
#
# GET URIs:
# /
# /proc
# show table of /proc files and directories
# all pocesses
# the proc man page
# /proc/1234 is process pid 1234
# show details of process
# /proc/path
# show contents of file or directory listing
# /socket
# show all sockets
# /socket/1234 is socket inode 1234
# show details of socket
# and table of all processes with given socket open
# /pipe
# show all pipes
# /pipe/1234 is pipe inode 1234
# show table of all processes with given pipe open
# /file
# show all open and memory mapped files
# /file/path is file with given /path
# show table of all processes with given file open
# /ip/ip-address
# show ip address/host details
# ip4 1.2.3.4 or ip6 1:23:3 format
# /domain/dns-domain-name
# show dns domain entry details
# fuly qualified or not
# /top
# show top output
# /source
# show the souce code of this script
# /du/path
# disk uage of /path
# /user
# all users
# /user/1234 is user with uid 1234
# details of given user
# /nautilus/dir
# start nautilus browsing dir
# /gnome-terminal/dir
# start gnome-terminal in dir
#
# POST arguments
# action is the action to perform
# hup
# SIGHUP the process then display it
# URL /proc/1234 is to act on process 1234
# kill
# kill (kill -9) the process then display it
# URL /proc/1234 is to act on process 1234
# term
# terminate the process then display it
# URL /proc/1234 is to act on process 1234
# nice
# renice the process
# value= the new nice value
# URL /proc/1234 is to act on process 1234
# rm
# context=du to return to /du screen
# otherwise /file
# remove the given file or directory
# URL /path/to/file removes /path/tp/file
# shows file of parent directory
# terminate-server
# terminate the web server
#
# colour is used as follows:
# blue links
# red errors
# orange warnings
# grey undisplayable text
# green man page extracts
#
# developed against Linux kernal 2.6.12-9-amd64-generic and 2.6.15-23-amd64-generic
# as much as possible names and file formats are not hard coded,
# areas with potential issues porting to a new kernal are
# tagged with @fragile
import BaseHTTPServer
import os
import cgi
import re
import string
import sys
import socket
import urllib
import signal
import select
import stat
import time
import errno
import traceback
direc, execName = os.path.split(sys.argv[0])
PID_FILE="/tmp/" + execName + ".pid"
PROTOCOLS = []
# tables within /proc/net
for protocol in ["raw", "tcp", "udp", "scpt", "unix"]:
if os.access("/proc/net/" + protocol, os.R_OK):
PROTOCOLS.append(protocol)
protocol = protocol + "6"
if os.access("/proc/net/" + protocol, os.R_OK): # check IPv6 versions
PROTOCOLS.append(protocol)
MAX_DATA = 64 * 1024
GREEN = "#008000"
PASSWD_FILE_HEADINGS = ["name", "password", "id", "group id", "comment", "home", "shell"]
# url is /page/rest
# so we can detect changes
last_page = ""
last_rest = ""
current_page = ""
current_rest = ""
du_cache = {}
# key is str path or directory
# value is size in bytes
# 114GB disk had 7656 entries,
# taking virtual memeory from 28836 kB to 29796kB
# which is 960Kb or 3% extra, negligible
def escape(input, wrap=0, preformatted=False):
""" @param framgment mean limit lies to given number of characters
0 means don't wrap
@param preformatted
True means leave \t and \n intact
"""
#if preformatted:
# wrap = 0
r = ""
n = 0
l = 0
for s in input:
if s == "<":
r = r + "<"
l = l + 1
elif s == ">":
r = r + ">"
l = l + 1
elif s == ">":
r = r + "&"
l = l + 1
elif s == "\"":
r = r + """
l = l + 1
elif s < " " or s > "~":
if preformatted and (s == "\t" or s == "\n"):
r = r + s
l = l + 1
else:
r = r + "%" + ("%02X" % ord(s)) + ""
l = l + 3
else:
r = r + s
l = l + 1
if wrap != 0 and l > n + wrap:
r = r + "
"
n = l
return r
def color(color, str):
""" @param str color
@param html str str
"""
return "" + str + ""
noWaiting = False
def safePopen(cmd):
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
# turns of SIGCHILD handler which breaks popen() which does a wait() itself
try:
fd = os.popen(cmd)
text = ""
try:
# col -b is remove backspaces
# -x is output multiple spaces instead of tabs
text = fd.read()
finally:
fd.close()
return text
finally:
signal.signal(signal.SIGCHLD, sigChildHandler)
procManPage = ""
def readManPage():
# read "man proc"
# returns (None, str) on success
# (int errono, undefined) on failure
# caches page
global procManPage
if len(procManPage) != 0:
return None, procManPage
# hide stderr so message from spawning "man proc":
# - Reformatting proc(5), please wait...
# - gunzip: stdout: Broken pipe
# is hidden
# redirect stderr to /dev/null
null = file("/dev/null", "w")
stderr_fileno = os.dup(sys.stderr.fileno())
os.dup2(null.fileno(), sys.stderr.fileno())
procManPage = safePopen("man proc | col -bx")
# col -b is remove backspaces
# -x is output multiple spaces instead of tabs
# \xE2\x80\x99 is UTF-8 for '
procManPage = procManPage.replace("\xE2\x80\x99", "'")
# \xE2\x80\x98 is UTF-8 for `
procManPage = procManPage.replace("\xE2\x80\x98", "`")
# restore stderr
os.dup2(stderr_fileno, sys.stderr.fileno())
return None, procManPage
def getManSection(procPath):
""" extract the given section from the man page
@return "" is not found
@fragile, replies on format of man page
"""
r, page = readManPage()
if r == None:
pos = page.find("\n " + procPath)
if pos != -1:
end = page.find("\n /proc", pos + 8 + len(procPath))
if end == -1:
end = page.find("SEE ALSO", pos + 8 + len(procPath))
if end == -1:
end = len(page)
return page[pos + 8 + len(procPath):end].strip(" \t\n")
return ""
def processRef(pid):
return '' + str(pid) + ""
def fileRef(path):
return '' + escape(path) + ""
def ipRef(ip, convertIp = True):
hostname = ip
if convertIp:
hostname = getFirstHostname(ip)
return '' + escape(hostname) + ""
def domainRef(domain):
return '' + escape(domain) + ""
def socketRef(inode):
""" @param int inode"""
if inode == 0:
return ""
else:
return '' + str(inode) + ""
def protocolRef(proto):
""" @param str protocol"""
return '' + escape(proto) + ""
def pipeRef(inode):
""" @param int inode"""
return '' + str(inode) + ""
def userRef(uid, name=""):
""" @param int uid"""
if name == "":
name = str(uid)
return '' + escape(name) + ""
def prepHeaderLine(line):
""" the socket tables, e.g. /net/tcp headers are almost
whitespace separated, need to hack the following changes
in
@fragile relies on format of socket tables headers """
return line.strip("\n").replace("tr tm->when", "tr:tm->when").replace("tx_queue rx_queue", "tx_queue:rx_queue")
def isSocketAddressHeader(header):
return header[-len("_address"):] == "_address"
def getSocketFields(proto):
# given str protocol
# return list str fields
# @fragile relies on names of columns in socket tables
if proto == "unix":
return ["path"]
elif proto[-1] == "6":
return ["local_address", "remote_address"]
else:
return ["local_address", "rem_address"]
def findRow(path, field, value):
# throw if not found
# return list of field values
inf = file(path, "r")
headerLine = prepHeaderLine(inf.readline())
headers = headerLine.split()
index = headerLine.lower().split().index(field.lower())
# case insensitive
# throw RangeError is not in list
while 1:
line = inf.readline()
if line == "":
raise Exception("field '" + field + "' value '" + value + "' not found in '" + path + "'")
data = line.strip("\n").split()
if data[index] == value:
r = []
for i in range(len(headers)):
if i >= len(data):
r.append((headers[i], ""))
else:
r.append((headers[i], data[i]))
for extra in data[len(headers):]:
r.append((None, extra))
return r;
def findRowByFun(path, headerFunc, value, valueTranformFunc = None):
# if func(header.lower()) == True and value == value return row
# throw if not found
# return list of (header, value)
inf = file(path, "r")
headerLine = prepHeaderLine(inf.readline())
headers = headerLine.split()
indexes = []
n = 0
for header in headerLine.lower().split():
if headerFunc(header):
indexes.append(n)
n = n + 1
if len(indexes) == 0:
raise Exception("by func no valid headers in '" + path + "'")
while True:
line = inf.readline()
if line == "":
raise Exception("by func value '" + value + "' not found in '" + path + "'")
data = line.strip("\n").split()
for index in indexes:
if valueTranformFunc != None:
v = valueTranformFunc(data[index])
else:
v = data[index]
if v == value:
r = []
for i in range(len(headers)):
if i >= len(data):
r.append((headers[i], ""))
else:
r.append((headers[i], data[i]))
for extra in data[len(headers):]:
r.append((None, extra))
return r;
def extractListValue(theList, header):
# theList [(str header, str value)]
# header str
# return value or None
for h, v in theList:
if h == header:
return v
return None
def parseTable(path):
# returns (list of headers, list of list of values)
inf = file(path, "r")
headerLine = prepHeaderLine(inf.readline())
headers = headerLine.split()
r = []
while 1:
line = inf.readline()
if line == "":
return headers, r;
r.append(line.strip("\n").split())
def fuser(path):
# returns list of pids, list of error strings
r = []
errors = []
for pid in os.listdir("/proc"):
if not pid.isdigit():
continue
try:
for fd in os.listdir("/proc/" + pid + "/fd"):
lpath = "/proc/" + pid + "/fd/" + fd
link = os.readlink(lpath)
# not relative to lpath, sockets have link to "socket:[1234]"
if link == path:
r.append(int(pid))
except Exception, e:
if int(pid) != os.getpid():
errors.append(str(e))
# ignore errors relating to our own pid
r.sort()
return r, errors
def allFdRefsPrefix(prefix):
# returns list of links, list of error strings
r = []
errors = []
for pid in os.listdir("/proc"):
if not pid.isdigit():
continue
try:
for fd in os.listdir("/proc/" + pid + "/fd"):
lpath = "/proc/" + pid + "/fd/" + fd
link = os.readlink(lpath)
# not relative to lpath, sockets have link to "socket:[1234]"
#if link.startswidth(prefix):
if link[:len(prefix)] == prefix:
if r.count(link) == 0:
r.append(link)
except Exception, e:
if int(pid) != os.getpid():
errors.append(str(e))
# ignore errors relating to our own pid
r.sort()
return r, errors
def getFirstHostname(ip):
# ip is ip address
# returns ip if fails
# returns first hostname
try:
hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(ip)
except Exception, e:
return ip
return hostname
def getFirstIpAddress(host):
# host is hostname
# returns host if fails
# returns first IP address
try:
r = socket.getaddrinfo(host, 0);
except Exception, e:
return host
if len(r) == 0:
return host
family, socktype, proto, canonname, sockaddr = r[0]
return connonname
def isAnyIpConnection(s):
"""@return true is s is the "any" TCP etc.. address
works with human readble 1.2.3.4:123 or Linux stack 0A3474B2:BA12 formats
"""
for c in s:
if not c in ["0", ":", ".", "[", "]"]:
return False
return True
def extractIpFromKernalAddress(val):
# str val "B855D6CD:0016"
# return str "1.2.3.4"
ip, port = splitSocketAddress(val)
return ip
def splitSocketAddress(s):
""" in: str "B855D6CD:0016"
out: str ip, int port
"[1234:5678:9ABC:DEF0:2424:2222:3456:0023]", 21
"1.2.3.4", 234
out str s, None
if can't decode
"""
tok = str.split(s, ":", 1)
if len(tok) == 1:
return s, None
elif len(tok[0]) == 32/4:
# ip v4 address
ip = socket.htonl(int(tok[0], 16))
r = ""
for i in range(0, 4):
if r != "":
r = r + "."
r = r + str((ip >> (i * 8)) & 0xFF)
# ip in host order
if socket.htonl(1) != 1:
# little endian
t = r.split(".")
t.reverse()
r = string.join(t, ".")
return r, int(tok[1], 16)
elif len(tok[0]) == 128/4:
# see RFC 2374, section 2.3
r = "["
for i in range(0, 128/4, 4):
if r != "[":
r = r + ":"
part = tok[0][i: i+4].lstrip("0")
if part == "":
part = "0"
r = r + part
# RFC has five 0s then FFFF
# i'm seeing four 0s, FFFF, then 0
# [:0:0:0:0:FFFF:0:1800:A8C0]
# last 4 octets in host order
# FFFF:0 in host order??
# @fragile relies on dodgy half network
# half host ordering of IP addresses
# recognise both
if r.startswith("[0:0:0:0:FFFF:0:") or r.startswith("[0:0:0:0:0:FFFF:"):
# IP v4 address
tok2 = r[len("[0:0:0:0:FFFF:0:"):].split(":")
r = ""
for t in tok2:
if r != "":
r = r + "."
n = int(t, 16)
r = r + str(n >> 8) + "." + str(n & 0xFF)
if socket.htonl(1) != 1:
# little endian
t = r.split(".")
t.reverse()
r = string.join(t, ".")
r = "[::FFFF:" + r
r = r + "]"
return r, int(tok[1], 16)
else:
return s, None
def formatSocketAddress(s):
""" in: str B855D6CD:0016
out: HTML str 205.214.85.184:22
[1234:5678:9ABC:DEF0:2424:2222:3456:0023]:21
0.0.0.0 or [0:0:0:0:0:0:0:0] IP address returned as ANY
ANY:0 returned as "" (remote address of listening sockets)
"""
ip, port = splitSocketAddress(s)
if port == None:
return escape(s)
# 0.0.0.0:0 is nothing (remote address of listening socket)
if isAnyIpConnection(ip):
if port == 0:
return ""
else:
return "ANY:" + str(port)
return ipRef(ip) + ":" + str(port)
def formatProcessCmdLine(pid, blankOnFail = False):
""" @param in pid """
cmdLineFile = "/proc/" + str(pid) + "/cmdline"
try:
cmdLine = file(cmdLineFile).read()
if cmdLine == "":
return "" + escape(getProcessAttribute(pid, "Name")) + ""
else:
return formatCmdLine(cmdLine, blankOnFail)
except Exception, e:
if blankOnFail:
return ""
else:
return "Error reading command line of process " + str(pid) + ": " + escape(str(e)) + ""
def formatCmdLine(data, blankOnFail = False):
# output in /proc/[number][/cmdline] in HTML
# is \0 separated with trailing \0
if len(data) == 0:
if blankOnFail:
return ""
else:
return "no command line"
else:
tok = data.rstrip("\0").split("\0")
data = ""
for t in tok:
if t == "" or t.find(" ") != -1:
data = data + " '" + t + "'"
else:
data = data + " " + t
return escape(data)
def getAllPids():
""" @returns list int all process id on system """
dirs = os.listdir("/proc")
pids = []
for f in dirs:
if f.isdigit():
pids.append(int(f))
return pids
def getProcessParent(pid):
""" @param int pid
@throw if can't get
"""
return int(getProcessAttribute(pid, "ppid"));
def getProcessUser(pid):
""" @param int pid
@throw if can't get
"""
data = getProcessAttribute(pid, "uid")
# has 4 numbers, not sure what this means
return int(data.split()[0]);
def getProcessAttribute(pid, attribName):
""" read info from /status of PID
@param int pid
@param str attribName
@throw if can't get
"""
attrib = attribName.lower() + ":"
statusFile = "/proc/" + str(pid) + "/status"
data = file(statusFile).read().lower()
# status more robust under change than stat
# PPid: 0
i = data.find(attrib)
if i == -1:
raise Exception("Can not find " + attribName + " in " + statusFile)
start = i + len(attrib)
end = data.find("\n", start)
if end == -1:
end = len(data)
return data[start: end].strip()
compareListsIndex = None
def compareLists(a, b):
""" compare two lists, use to sort 'top' output
@param list a
@param list b
handle floats, 123m
"""
try:
# try to compare as floats
if compareListsIndex >= len(a):
af = 0
else:
ai = a[compareListsIndex]
if len(ai) > 1 and ai[-1] == "m":
af = float(ai[:-1]) * 1024
else:
af = float(ai)
if compareListsIndex >= len(b):
bf = 0
else:
bi = b[compareListsIndex]
if len(bi) > 1 and bi[-1] == "m":
bf = float(bi[:-1]) * 1024
else:
bf = float(bi)
return cmp(af, bf)
except:
# text compare
if compareListsIndex >= len(a):
ai = ""
else:
ai = a[compareListsIndex]
if compareListsIndex >= len(b):
bi = ""
else:
bi = b[compareListsIndex]
return cmp(ai, bi)
statIndexes = {}
# key is str field
# value in int index
def getStatField(pid, field):
""" @param int pid
@param str field
"""
global statIndexes
if len(statIndexes) == 0:
sec = getManSection("/proc/[number]/stat")
if sec == "":
raise Exception("No man page section to set field offset for '" + field + "'")
i = 0
for line in sec.split("\n"):
if line.find("%") != -1:
mantok = line.lstrip().split(" ", 2)
statIndexes[mantok[0].lower()] = i
i = i + 1
if not field in statIndexes:
raise Exception("No such stat field as '" + field + "'")
data = file("/proc/" + str(pid) + "/stat").read()
return data.split()[statIndexes[field]];
def pluralS(num):
if num == 1:
return ""
else:
return "s"
def formatSize(size):
if size == 1:
return "1 byte"
elif size < 1024:
return str(size) + " bytes"
elif size < 1024 * 1024:
return ("%.1f" % (size/1024.0)) + " KiB"
elif size < 1024 * 1024 * 1024:
return ("%.1f" % (size/1024.0/1024)) + " MiB"
elif size < 1024 * 1024 * 1024 * 1024:
return ("%.1f" % (size/1024.0/1024/1024)) + " GiB"
else:
return ("%.1f" % (size/1024.0/1024/1024/1024)) + " TiB"
def formatFilePermissionsPart(perm):
""" @param int octal permision bits """
r = ""
# S_IROTH 00004 others have read permission
# S_IWOTH 00002 others have write permisson
# S_IXOTH 00001 others have execute permission
# rwx
if perm & 4:
r += "r"
else:
r += "-"
if perm & 2:
r += "w"
else:
r += "-"
if perm & 1:
r += "x"
else:
r += "-"
return r
def formatFilePermissions(perm):
r = ""
if stat.S_ISDIR(perm):
r += "d"
elif stat.S_ISLNK(perm):
r += "l"
elif stat.S_ISSOCK(perm):
r += "s"
elif stat.S_ISFIFO(perm):
r += "p"
elif stat.S_ISCHR(perm):
r += "c"
elif stat.S_ISBLK(perm):
r += "b"
else:
r += "-"
# user
r += formatFilePermissionsPart((perm >> 6) & 0x7)
# group
r += formatFilePermissionsPart((perm >> 3) & 0x7)
# others
r += formatFilePermissionsPart((perm >> 0) & 0x7)
# S_ISUID 0004000 set UID bit
# S_ISGID 0002000 set GID bit (see below)
# S_ISVTX 0001000 sticky bit (see below)
# u g o
# t rwx rwx rwx
# 0 123 456 789
if perm & 04000:
if r[3] == "x":
r = r[0:3] + "s" + r[4:]
else:
r = r[0:3] + "S" + r[4:]
if perm & 02000:
if r[6] == "x":
r = r[0:6] + "s" + r[7:]
else:
r = r[0:6] + "S" + r[7:]
if perm & 01000:
if r[9] == "x":
pass
else:
r = r[0:9] + "T" + r[10:]
return r
def formatUser(uid):
try:
return userRef(uid, getUserName(uid))
except Exception, e:
return "" + userRef(uid) + ": " + escape(str(e)) + ""
def getUserName(uid):
""" @throw on failure """
data = file("/etc/passwd").read()
for line in data.split("\n"):
tok = line.split(":") # \: escape?
#man 5 passwd:
#Login name
#Optional encrypted password
#Numerical user ID
#Numerical group ID
#User name or comment field
#User home directory
#User command interpreter
#root:x:0:0:root:/root:/bin/bash
#0 1 2 3 4 5 6
if len(tok) > 2 and tok[2].isdigit() and int(tok[2]) == uid:
return tok[0]
return str(uid)
def formatGroup(gid):
try:
data = file("/etc/group").read()
for line in data.split("\n"):
tok = line.split(":") # \: escape?
#group_name:passwd:GID:user_list
if len(tok) > 2 and tok[2].isdigit() and int(tok[2]) == gid:
return tok[0]
return str(gid)
except Exception, e:
return "" + str(gid) + ": " + escape(str(e)) + ""
month = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def formatTime(t):
# 3
proc-surfer.py v' + VERSION + ' © 2005-2006 Gene Thomas
') self.write("") self.write("") def heading(self, h): self.write('" + message + "
") r, page = readManPage() #r, page = None, "" #!@# self.write('')
self.link("/proc")
self.link("processes")
if r == None:
self.link("man page")
self.writeUserWarningLink()
self.write("
")
self.link("/top", "top")
self.link("/file", "files")
self.link("/socket", "sockets")
self.link("/pipe", "pipes")
self.link("/du", "disk usage")
self.link("/user", "users")
self.write('
| ' + escape(f) + c + "" + (15 * " ") + " | ") self.write("
All processes have parents!
") noParents = pids self.children = {} self.heading("processes") self.write("") # green
self.writeEscape(page, preformatted=True)
self.write("")
else:
self.write("<-- no 'man proc', exist status " + str(r) + " -->")
self.write('" + message + "
") self.writeManSection("/proc/[number]") path = "/proc/" + str(pid) + "/" direc = os.listdir(path) direc.sort() # table of contents self.write("| up' + (15 * " ") + " | ") else: self.write('' + escape(f) + "" + (15 * " ") + " | ") self.write("
| ') self.write(" | nice: | ") self.write(' | ') self.write("
| ") haveMan = getManSection("/proc/[number]/" + f) != "" # doesn't hit the disk so not too inefficient to do twice self.write('') if haveMan: self.write('') self.writeEscape(f) if haveMan: self.write('') self.write('') self.write(" | ") self.writeProcessSection(0, "/proc/" + str(pid), f) self.write("
|---|
") for f in os.listdir("/proc"): try: if not f.isdigit(): continue ppid = getProcessParent(str(f)) if ppid == pid: children.append(int(f)) except Exception, e: self.write("Can not find ppid of " + str(pid) + ": " + str(e) + "") if len(children) == 0: self.write("none") else: children.sort() self.write("
") # man page sections self.write("
| Error reading link " + escape(path) + ": " + escape(str(e)) + " | ") self.write("
| " + escape(f) + " | ") try: self.writeProcessSection(level + 1, path, f) except Exception, e: self.write("Error reading " + escape(path) + ": " + escape(str(e)) + " font> | ") self.write("
|---|
")
self.writeEscape(data, preformatted=True)
self.write("")
except Exception, e:
self.write("Error reading file " + escape(path) + ": " + escape(str(e)) + "")
self.write("| " + str(fd) + " | ") self.writeLinkRow(path + "/" + str(fd)) self.write("
|---|
")
self.writeEscape(data, preformatted=True)
self.write("")
return
# second (command name) arg is in brackets so can contain spaces
# may contain )s too so this is unsolvable
# replace whitespace with _
open = data.find(" (")
close = data.rfind(") ")
if open == -1 or close == -1:
tok = data.split()
else:
tok = data[0:open].split() + [data[open + 2: close]] + data[close + 2:].split()
i = 0
self.write("")
# is rendered with a space above and below
# even if there is no \n in the text
# so spaces out the table
self.writeManSectionText(manText, preformat=False)
self.write(" | ||||
| " + escape(mantok[0]) + " | ") if i < len(tok): self.write("
| ")
else:
self.write("no value | ") i = i + 1 self.write("") self.writeManSectionText(manText, preformat=False) self.write(" | |
|---|---|---|---|---|
| extra value | " + escape(tok[i]) + " |
")
self.writeEscape(data, preformatted=True)
self.write("")
return
# man section:
# intro
# bla:
# value description
# value description
dataTok = data.split()
headerTok = sec[index:].split("\n")[1:]
n = len(dataTok)
if len(headerTok) > n:
n = len(headerTok)
self.write("| " + escape(header[0]) + " | ") else: self.write("extra value | ") if i < len(dataTok): self.write("" + escape(dataTok[i]) + " | ") else: self.write("") if i < len(headerTok) and len(header) > 1: self.write(" | " + color(GREEN, escape(header[1].strip()) ) + " | ") self.write("
|---|
| ") self.writeEscape(h) self.write(" | ") self.write("
|---|
| " + escape(field) + " | ") self.write("
| " + escape(line) + " | ") else: key = line[0:pos].strip() self.write("" + escape(key) + " | ") value = line[pos+1:].strip() self.write("") self.writeProcessAttribute(key, value) self.write(" | ") self.write("
|---|
| ") self.writeEscape(kv[0]) self.write(" | ") self.write("") if (len(kv) > 1): if kv[0].endswith("PATH"): start = 0 for t in kv[1].split(":"): if start == 0: start = 1 else: self.write(" |
| ") self.writeEscape(t) else: self.writeEscape(kv[1], wrap=50) else: self.write("no = in environment variable: '" + escape(e) + "'") self.write(" | ") self.write("
")
else:
self.write("")
for line in sec.split("\n"):
line = line.strip()
#line = string.join(re.split("[ ]{2,}", line), " ") # multiple spaces to single space
self.writeEscape(line, preformatted=True)
if not preformat:
self.write("
")
if preformat:
self.write("")
else:
self.write("")
self.write("")
def get_proc_file(self, path):
procPath = "/proc/"+ path
self.procPathHeader(procPath)
try:
if path.startswith("net/") and path[len("net/"):] in PROTOCOLS:
headers, data = parseTable(procPath)
self.write("| " + escape(col) + " | ") self.write("
|---|
| ") if i >= len(headers): self.writeEscape(col) elif headers[i][-len("_address"):] == "_address": self.write(formatSocketAddress(col)) elif headers[i].lower() == "inode": self.write(socketRef(int(col))) else: self.writeEscape(col) self.write(" | ") i = i + 1 self.write("
Error running top, exit status " + str(r & 0xFF) + ", signal " + str((r >> 8) & 0x7F) + ", core " + str((r & 0x80)==0x80) + "
") # self.footer() # return reverse = False if len(sortBy) > 0 and sortBy[0] == '-': reverse = True sortBy = sortBy[1:] indexes = {} header = True body = False data = [] for line in text.split("\n"): if header: self.writeEscape(line) self.write("| ') if col == sortBy and reverse: key = col else: key = "-" + col # defaults to sort descending as we usually want the highest at the top if col == sortBy: self.write('') self.write('' + escape(col) + "") if col == sortBy: self.write('') self.write(' | ') indexes[col] = i i += 1 self.write("|||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ") if i == pidIndex: self.write('') self.writeEscape(col) if i == pidIndex: self.write("") self.write(" | ") i = i + 1 self.write("
| ' + socketRef(inode) + " | ") self.write("" + protocolRef(proto) + " | ") self.write("
| protocol | " + protocolRef(proto) + " |
| ") if header != None: self.writeEscape(header) self.write(" | ") self.writeSocketField(header, data) self.write(" |
" + message + "
") self.write('up') self.link("open files") self.link("root directory") self.write('disk usage
') self.heading("open files") self.write("" + message + "
") self.write("") if path == "/": self.prefix = "" else: self.prefix = path if path != "/": tok = path.rstrip("/").split("/")[:-1] if tok == ['']: href = "/du" else: href = "/du" + escape(string.join(tok, "/")) else: href = "/" self.write('up') self.write('directory') self.write('nautilus") self.write('terminal") self.showDirTime = time.time() + 1.5 filesSize = 0 numFiles = 0 sizes = [] # list of (str name, int size in bytes) maxSize = 0 total = fileSize(path) try: direc = os.listdir(path) except Exception, e: self.write("
Error directory " + escape(path) + ": " + escape(str(e)) + "
") self.footer() return global last_page, last_rest global current_page, current_rest global du_cache if last_page == "du" and last_rest == current_rest: # re-requesting the same page clears the cache self.write("rescanning...") du_cache = {} for f in direc: fpath = self.prefix + "/" + f if os.path.islink(fpath): pass elif os.path.isdir(fpath): size = self.dirSize(fpath) sizes.append((f, size)) if size > maxSize: maxSize = size total += size else: filesSize += fileSize(fpath) numFiles += 1 filesName = str(numFiles) + " file" + pluralS(numFiles) if numFiles != 0: sizes.append((filesName, filesSize)) if filesSize > maxSize: maxSize = filesSize total += filesSize #self.write('') #self.write("""""") #self.write("world')>") #self.write("") sizes.sort(compareSize) sizes.reverse() self.write("") self.write("| ") else: self.write(' | ') self.write(" | " + formatSize(size) + " | ")
if maxSize != 0:
self.write(" | ' + escape(name) + " | ") else: self.write('" + escape(name) + "/ | ") self.write("||
| " + formatSize(total) + " | total |
File system:
| " + formatSize(fsfree) + " | free | |
| " + formatSize(fsused) + " | used | |
| " + formatSize(fstotal) + " | total |
" + formatSize(r.f_frsize) + " block size
")
if r.f_frsize != r.f_bsize:
self.write("
" + formatSize(r.f_bsize) + " preferred block size") self.write("
") self.write("All sizes are rounded up to the disk block size to show actual disk space used.
The total includes space used by the directory itself.
For speed directory sizes are saved, hit the Reload button on your browser to refresh these.
" + message + "
") if path != "/": tok = path.rstrip("/").split("/")[:-1] if tok == ['']: href = "#root%20directory" else: href = string.join(tok, "/") self.write('up") else: self.write('up') self.link("details") self.link("open") if not os.path.isdir(path): self.link("memory mapped") self.link("contents") if os.path.isdir(path): self.write('disk usage') self.write('nautilus") self.write('terminal") self.heading("details") try: r = os.stat(path) self.write("| name | " + escape(path) + " |
|---|---|
| type | " + fileType(path) + " |
| mode | " + formatFilePermissions(r.st_mode) + " |
| num links | " + str(r.st_nlink) + " |
| inode | " + str(r.st_ino) + " |
| device | " + str(r.st_dev) + " |
| user | " + formatUser(r.st_uid) + " |
| group | " + formatGroup(r.st_gid) + " |
| size | " + formatCommaSize(r.st_size) + " |
| blocks | " + formatCommaSize(r.st_blocks) + " |
| blocksize | " + formatCommaSize(r.st_blksize) + " |
| dev type | " + str(r.st_rdev) + " |
| disk space | " + formatCommaSize(r.st_blocks *r.st_blksize) + " |
| created | " + formatFullTime(r.st_ctime) + " |
| modified | " + formatFullTime(r.st_mtime) + " |
| accessed | " + formatFullTime(r.st_atime) + " |
Error stating file " + escape(path) + ": " + escape(str(e)) + "
") self.heading("open") if len(fdPids) == 0: self.write("none") else: fdPids.sort() self.write("fifo (named pipe)
") return if stat.S_ISSOCK(os.stat(path).st_mode): self.write("socket
") return fd = os.open(path, os.O_NONBLOCK) try: data = os.read(fd, MAX_DATA+1) except: os.close(fd) raise os.close(fd) if (len(data) > MAX_DATA): try: size = os.stat(path).st_size self.write("" + str(size) + " bytes
") except Exception, e: raise Exception("Can not determine size of file '" + path + ": " + str(e)) else: self.write("")
while len(data) > 1 and data[len(data)-1] == "\n":
data = data[:len(data)-1]
for line in data.split("\n"):
self.writeEscape(line, preformatted=True)
self.write("")
except Exception, e:
self.write("Error reading file " + escape(path) + ": " + escape(str(e)) + "
") def writeDirectoryContents(self, path): try: try: contents = os.listdir(path) except Exception, e: self.write("Error reading directory " + escape(path) + ": " + escape(str(e)) + "
") return contents.sort() if path == "/": prefix = "" else: prefix = path if len(contents) == 0: self.write("none") else: self.write('| ') self.write(' | mode | ') self.write("" + SPACING + "size | ") self.write("" + SPACING + "user | ") self.write("" + SPACING + "group | ") self.write("" + SPACING + " | ") self.write("last modified | ") self.write("" + SPACING + "name | ") self.write('||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ') # drwx------ 2 root root 4096 Dec 17 02:49 orbit-root # miss out links as I don't care self.write(" | " + formatFilePermissions(r.st_mode) + " | ") sizeText = "" if os.path.isdir(fpath): global du_cache if fpath in du_cache: sizeText = formatCommaSize(du_cache[fpath]) totalSize += du_cache[fpath] else: sizeText = "" + formatCommaSize(r.st_size) + "" totalSize += r.st_size totalSizeComlete = False else: sizeText = formatCommaSize(r.st_size) totalSize += r.st_size self.write("" + SPACING + sizeText + " | ") self.write("" + SPACING + formatUser(r.st_uid) + " | ") self.write("" + SPACING + formatGroup(r.st_gid) + " | ") self.write("" + SPACING + " | " + formatTime(r.st_mtime)) # 1 + 3 columns except Exception, e: self.write("Error stating file " + escape(fpath) + ": " + escape(str(e)) + " | ") if os.path.isdir(fpath): self.write('' + SPACING + '' + escape(f) + "/" + link + " | ") else: self.write('' + SPACING + '' + escape(f) + "" + link + " | ") self.write("|||||||
| " + SPACING + formatCommaSize(totalSize) + " | " + SPACING + "total | ||||||||||||||
| " + SPACING + formatCommaSize(totalSize) + " | " + SPACING + "total | ||||||||||||||
Error reading directory " + escape(path) + ": " + escape(str(e)) + "
") def get_users(self): self.header("users") self.write("| " + escape(h) + " | ") else: self.write("" + escape(h) + " | ") n += 1 try: data = file("/etc/passwd").read() for line in data.split("\n"): self.write("||
|---|---|---|---|
| " + userRef(int(tok[2]), col) + " | ") elif n in [2, 3]: # uid, gid self.write("" + escape(col) + " | ") elif n in [5, 6]: # home and shell self.write("" + fileRef(col) + " | ") else: self.write("" + escape(col) + " | ") n += 1 #man 5 passwd: #0 Login name #1 Optional encrypted password #2 Numerical user ID #3 Numerical group ID #4 User name or comment field #5 User home directory #6 User command interpreter #root:x:0:0:root:/root:/bin/bash #0 1 2 3 4 5 6 self.write("
error reading /etc/passwd: " + escape(str(e)) + "
") self.footer() def get_ip(self, ip): self.header("ip address " + ip) error = None try: hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(ip) except Exception, e: aliaslist = [] ipaddrlist = [] hostname = None error = str(e) self.write("up") self.link("hostname") self.write("Error getting hostname for ip '" + escape(ip) + "': " + escape(error) + "
") else: self.write(domainRef(hostname)) if len(aliaslist) > 0: self.heading("aliases") self.write("| " + str(num) + " | ") if line == "no reply": self.write("no reply | ") else: tok = line.split() if len(tok) < 5: tok = tok[0:2] + ([""] * (5 - len(tok))) + tok[2:] for t in tok: if t.startswith("(") and t.endswith(")"): self.write("" + ipRef(t[1:-1], False) + " | ") # leave as IP, first column shows hostname else: self.write("" + escape(t) + " | ") else: # no number self.write("" + escape(line) + " | ") self.write("
|---|
Error running tracepath '" + ip + "', exit status " + str(r & 0xFF) + ", signal " + str((r >> 8) & 0x7F) + ", core " + str((r & 0x80)==0x80) + "
") self.footer() def get_domain(self, domain): self.header("domain name " + domain) self.write("up") self.heading("IP address") error = None try: ips = [] for address in socket.getaddrinfo(domain, 0): family, socktype, proto, canonname, (ip, port) = address # socket.getaddrinfo("google.com", 0)= [ #(2, 1, 6, '', ('64.233.187.99', 0)), #(2, 2, 17, '', ('64.233.187.99', 0)), #(2, 1, 6, '', ('64.233.167.99', 0)), #(2, 2, 17, '', ('64.233.167.99', 0)), #(2, 1, 6, '', ('72.14.207.99', 0)), #(2, 2, 17, '', ('72.14.207.99', 0)) ] #/usr/include/bits/socket.h # enum __socket_type # SOCK_STREAM = 1 // TCP? # SOCK_DGRAM = 2 // UDP? # /* Protocol families. */ # #define PF_INET 2 # ips repeated with differnt socktype, proto if not ip in ips: self.write(ipRef(ip, False) + "Error getting IP addreses for domain name '" + escape(domain) + "': " + escape(str(e)) + "
") self.footer() def get_user(self, uid): try: self.header("user " + getUserName(uid)) except Exception, e: self.header("user " + str(uid)) self.link("details") self.link("processes") self.heading("details") try: data = file("/etc/passwd").read() self.write("| " + escape(h) + " | ") if n in [5, 6]: # home and shell self.write("" + fileRef(col) + " | ") elif n == 2: self.write("" + escape(col) + " | ") else: self.write("" + escape(col) + " | ") self.write("
|---|
error reading /etc/passwd: " + escape(str(e)) + "
") if not done: self.write("user " + str(uid) + " not found in /etc/passwd
") self.heading("processes") pids = [] for f in os.listdir("/proc"): if f.isdigit(): pids.append(int(f)) self.children = {} # key is int parent pid # value is list int child pids # list int of PIDs noParents = [] # list of PIDs with no parent userPids = [] for pid in pids: try: t = getProcessUser(pid) if getProcessUser(pid) != uid: continue userPids.append(pid) ppid = getProcessParent(pid) if ppid == 0 or getProcessUser(ppid) != uid: noParents.append(pid) else: if ppid in self.children: self.children[ppid].append(pid) else: self.children[ppid] = [pid] except Exception, e: self.write("Can not find ppid of " + str(pid) + ": " + str(e) + "") if len(noParents) == 0 and len(userPids) != 0: self.write("All processes have parents!
") noParents = userPids self.children = {} # caches blanked in do_GET if len(noParents) == 0: self.write("none
") else: self.write("Page not found: " + escape(self.path) + "
") if message != "": self.write("" + message + "
") self.footer() def writeError(self, message): if not self.writtenHeader: self.write("HTTP/1.1 500 Error") self.write("Content-Type: text/html") self.write() self.writtenHeader = True self.title("proc-surfer", "error"); self.write("" + escape(message) + "
") self.footer() def do_get(self): path = urllib.unquote(self.path) # convert %20 to " " etc.. if path == "": self.get_root() return tok = path[1:].split("/", 1) page = tok[0] rest = "" if len(tok) > 1: rest = tok[1] # so can detect changes # /du/ caches results global current_page, current_rest global last_page, last_rest save_last_page = last_page save_last_rest = last_rest last_page = current_page last_rest = current_rest current_page = page current_rest = rest if page == "": self.get_root() return elif page == "proc": if rest == "": self.get_root() return elif rest.isdigit(): self.get_process(int(rest)) else: if os.path.isdir(path): self.get_proc_dir(rest) else: self.get_proc_file(rest) return elif page == "socket": if rest == "": self.get_sockets() return else: self.get_socket(int(rest)) elif page == "pipe": if rest == "": self.get_pipes() return else: self.get_pipe(int(rest)) elif page == "file": if rest == "": self.get_files() return else: self.get_file("/" + rest) elif page == "du": if rest == "": self.get_du("/") return else: self.get_du("/" + rest) elif page == "user": if rest == "": self.get_users() return else: self.get_user(int(rest)) elif page == "ip": self.get_ip(rest) elif page == "domain": self.get_domain(rest) elif page == "nautilus": #os.system("nautilus '/" + rest + "'") os.spawnvp(os.P_NOWAIT, "nautilus", ["nautilus", "/" + rest]) elif page == "gnome-terminal": cwd = os.getcwd() os.chdir("/" + rest) os.system("gnome-terminal") os.chdir(cwd) elif page == "source": self.get_source() elif page == "top": self.get_top(rest) else: # invalid - undo saving of page/rest current_page = last_page current_rest = last_rest last_page = save_last_page last_rest = save_last_rest self.writeNotFound() def init(self): self.writtenHeader = False self.cache_cmdline = {} # key is int pid # value is html str contents of /proc/