#!/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 columns global month lt = time.localtime(t) if lt.tm_year == time.localtime().tm_year: # Dec 3 01:35 return '' + month[lt.tm_mon] + ('%2d%02d:%02d' % (lt.tm_mday, lt.tm_hour, lt.tm_min)) else: # Mar 27 2004 return '' + month[lt.tm_mon] + ('%2d%5d' % (lt.tm_mday, lt.tm_year)) def formatFullTime(t): global month lt = time.localtime(t) # 12:34:56 14 Sep 1975 return "%02d:%02d:%02d %2d %s %04d" % (lt.tm_hour, lt.tm_min, lt.tm_sec, lt.tm_mday, month[lt.tm_mon], lt.tm_year) def formatCommaSize(n): """ @param int n @retrun str n formatted 12345 -> "12,345" """ size = str(n) rsize = "" for i in range(len(size)-1, -1, -1): rsize += size[i] tok = [] for i in range(0, len(rsize), 3): s = rsize[i+2: i+3] + rsize[i+1: i+2] + rsize[i: i+1] tok.append(s) tok.reverse() return string.join(tok, ",") def compareSize((aname, asize), (bname, bsize)): # each item is (str name, int size in bytes) # numbers > 31 bit so can't just subtract the sizes if asize > bsize: return 1 elif asize < bsize: return -1 else: return 0 def fileType(path): # @returns HTML str try: data = safePopen("file '" + path + "'") if data.startswith(path + ": "): data = data[len(path) + 1:] data = data.rstrip("\n") return escape(data) except Exception, e: return "Can not determine file type: " + escape(str(e)) + "" def recursiveRemove(path): """ recisively remvoe @param str path """ for f in os.listdir(path): fpath = os.path.join(path, f) if os.path.isdir(fpath): recursiveRemove(fpath) else: os.remove(fpath) os.rmdir(path) def fileSize(fpath): r = os.stat(fpath) return (r.st_size + r.st_blksize - 1)/r.st_blksize*r.st_blksize ######################################################################################################################## class ProcSurferHttpHandler(BaseHTTPServer.BaseHTTPRequestHandler): # main HTTP server class # BaseHTTPServer called do_GET() when a client requests a page def write(self, s = ""): # write a line of HTML self.wfile.write(s + "\r\n"); def flush(self): # write a line of HTML self.wfile.flush() def writeUserWarning(self): if os.getuid() != 0: # orange self.write("

As you are not running proc-surfer.py as the root user so") self.write("some information relating to system and other users' processes can not be shown.

") def writeUserWarningLink(self): if os.getuid() != 0: self.write('warning') def writeEscape(self, s = "", wrap=0, preformatted=False): # write a line of HTML # escaping SGML special characters and non primntable ASCII self.wfile.write(escape(s, wrap, preformatted) + "\r\n"); def header(self, window, page = ""): """@param str window is text for window @param html str page is heading for page """ if not self.writtenHeader: self.write("HTTP/1.1 200 OK") self.write("Content-Type: text/html") self.write() # end headers self.writtenHeader = True if page == "" and window != "": page = escape(window) self.title("proc-surfer: " + window, page) def procPathHeader(self, path): """ @param str path is /proc/path""" link = "" heading = '/proc/' # path = /proc/ for p in path.split("/")[2:-1]: link = link + "/" + p heading = heading + '' + escape(p) + "/" self.header(path, heading + "" + escape(path.split("/")[-1]) + "") def pathHeader(self, textPrefix, page, path): """ write a path header with each part as a link @param str textPrefix is text to put in title @param str page is top level URL part @param str path is /a/path """ if path == "/": self.header(textPrefix) return link = "" heading = "" #'/' # path = /proc/ for p in path.split("/")[:-1]: if link == "" and p == "": link = p else: link = link + "/" + p heading += '' + escape(p) if p == "": heading += "/" # root dir / is part of the link else: heading += "/" self.header(textPrefix + " " + path, textPrefix + " " + heading + "" + escape(path.split("/")[-1]) + "") def writeProcessRow(self, pid): """ @param int pid""" self.write('' + processRef(pid) + "" + formatProcessCmdLine(pid) + "") def writeFileRow(self, path): self.write("" + fileRef(path) + "") def writeSocketField(self, header, value, wrap=0): # can't decorate fields, e.g. IP <-> as this is used # for vertical tables too if header == None: self.writeEscape(value, wrap) elif isSocketAddressHeader(header): self.write(formatSocketAddress(value)) elif header.lower() == "inode": self.write("" + escape(value, wrap) + "") else: self.writeEscape(value, wrap) def writeSocketRow(self, inode, wrap=0): self.write('' + socketRef(inode) + "") row = None proto = None # @frgaile depends on names of socket protocols for p in PROTOCOLS: try: row = findRow("/proc/net/" + p, "inode", str(inode)) proto = p break except: pass # not in this protocol if row == None: return self.write("" + protocolRef(proto) + "") self.write("") # put all fields in the same table cell so alignment is nice for field in getSocketFields(proto): for (header, value) in row: if header != None and header.lower() == field: if (header == "remote_address" or header == "rem_address") and not isAnyIpConnection(value): self.write("↔") # <-> self.writeSocketField(header, value, wrap) # newline sepatated fields self.write("") def writePipeRow(self, inode): """ @param int inode """ pids, errors = fuser("pipe:[" + str(inode) + "]") cmdlines = [] for pid in pids: cmdlines.append((pid, formatProcessCmdLine(pid))) self.writePipeRowPidCmdline(inode, cmdlines) def writePipeRowPidCmdline(self, inode, cmdlines): """ @param int inode @param list of (int pid, html str cmdlines) """ self.write('' + pipeRef(inode) + "") start = True self.write("") for (pid, cmdline) in cmdlines: if not start: self.write("
") else: start = False self.write("" + cmdline + "") self.write("") def title(self, window, page): """ write HTML header and title and stat body @param str window is text for window @param html str page is heading for page """ self.write("") self.write('') if window != "": self.write("") self.write("" + escape(window) + "") self.write("") self.write("") if page != "": self.write("

" + page + "

") #neither work in firefox #self.write('

Close this Window

') #self.write("

") def footer(self): self.writeUserWarning() self.write('

proc-surfer.py v' + VERSION + ' © 2005-2006 Gene Thomas

') self.write("") self.write("") def heading(self, h): self.write('

' + escape(h) + "

") def link(self, dest, text = ""): """ str dest str html text """ if text == "": self.write('' + escape(dest) + "") else: self.write('' + text + "") def get_root(self, message=""): # root page code optimised # so it only hits any given file once # this requires some duplication of code self.header("", "/proc") try: if message != "": 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('

') self.heading("/proc") dirs = os.listdir("/proc") dirs.sort() pids = [] items = [] for f in dirs: if f.isdigit(): pids.append(int(f)) else: items.append(f) COLS = 5 self.write("

") for i in range(0, len(items), COLS): self.write("") for f in items[i: i + COLS]: c = "" if os.path.isdir("/proc/" + f): c = "/" self.write('") self.write("") self.write("
' + escape(f) + c + "" + (15 * " ") + "

") self.children = {} # key is int parent pid # value is list int child pids # list int of PIDs noParents = [] # list of PIDs with no parent for pid in pids: try: ppid = getProcessParent(pid) if ppid == 0: 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: self.write("

All processes have parents!

") noParents = pids self.children = {} self.heading("processes") self.write("") # caches blanked in do_GET noParents.sort() for pid in noParents: self.writeProcessAndChildren(pid) self.write("
") if r == None: self.heading("man page") self.write("
") # green
            self.writeEscape(page, preformatted=True)
            self.write("
") else: self.write("<-- no 'man proc', exist status " + str(r) + " -->") self.write('
') self.write('') self.write('
') self.footer() except Exception, e: self.write("Error displaying /proc: " + str(e) + "") self.footer() def writeProcessAndChildren(self, pid): """ int pid """ ok, cmdline = self.getProcessInformation(pid) self.write("") self.write('') if not ok: self.write('') self.write(processRef(pid)) if not ok: self.write('') self.write('') try: self.write(" " + formatUser(getProcessUser(pid))) except Exception, e: pass self.write(cmdline) self.write("") self.write(""); if pid in self.children: self.write(""); children = self.children[pid] children.sort() for child in children: self.writeProcessAndChildren(child) self.write("
") def getProcessInformation(self, pid): """ @param int pid @return (bool ok, str cmdline) writes on failure """ cmdline = formatProcessCmdLine(pid) self.cache_cmdline[pid] = cmdline try: for fd in os.listdir("/proc/" + str(pid) + "/fd"): dest = os.readlink("/proc/" + str(pid) + "/fd/" + fd) if dest[0] == "/": if not dest in self.cache_files: self.cache_files.append(dest) elif dest.startswith("socket:["): inode = int(dest[len("socket:["):].rstrip("]")) if not inode in self.cache_sockets: self.cache_sockets.append(inode) elif dest.startswith("pipe:["): inode = int(dest[len("pipe:["):].rstrip("]")) if not inode in self.cache_pipes: self.cache_pipes[inode] = [(pid, cmdline)] else: self.cache_pipes[inode].append((pid, cmdline)) else: # eventpoll[7479] not covered # shows up under process though pass except Exception, e: return self.writeBadException("Error reading fds of process " + str(pid), pid, e), cmdline return True, cmdline def writeBadException(self, op, pid, e): """ @param str operation @param int pid or None, PID error relates to @param Exception e @return bool ok writes row if exception is bad """ if isinstance(e, EnvironmentError): # OSError or IOError theErrno, what = e if theErrno == errno.EACCES and os.getuid() != 0: # [Errno 13] Permission denied # already warned user about not being root return False elif theErrno == errno.ENOENT and pid == os.getpid(): # error reading own file descriptors # probably closed between readdir() and reading subdir return True else: self.write("" + op + ": " + escape(str(theErrno)) + " " + what + "") return False else: self.write("" + op + ": " + escape(str(e)) + "") return False def writeLink(self, heading, path): # write an html heading and link data self.heading(heading) self.write("") self.writeLinkRow(path) self.write("
") self.writeManSection("/proc/[number]/" + path.split("/", 3)[3]) def writeLinkRow(self, path): # write link suitable to be between try: link = os.readlink(path) if len(link) >= 1 and link[0] == "/": if os.path.isdir(link): self.write("directory") else: self.write("file") self.writeFileRow(link) else: tok = link.split(":") if len(tok) == 1: self.write("" + escape(link) + "") else: # socket:1234 # socet:[1234] # pipe:4321 # pipe:[4321] inode = int(tok[1].strip("[]")) if tok[0] == "socket": self.write("socket") try: self.writeSocketRow(inode) finally: self.write("
") elif tok[0] == "pipe": self.write("pipe") try: self.writePipeRow(inode) finally: self.write("
") else: self.write("" + escape(link) + "") except Exception, e: self.write("Error reading link " + escape(path) + ": " + escape(str(e)) + "") def do_sig_hup(self, pid): try: os.kill(pid, signal.SIGHUP) except Exception, e: self.get_process(pid, "Error sending SIGHUP to process " + str(pid) + ": " + str(e) + "") else: self.get_process(pid, "SIGHUP sent to process " + str(pid)) def do_terminate_server(self): self.header("terminating proc-surfer.py server") self.footer() #sys.exit() throws self.flush() try: os.remove(PID_FILE) except Exception, e: sys.stderr.write("Error remove PID file '" + PID_FILE + "': " + str(e) + "\n") os._exit(0) def do_sig_kill(self, pid): try: os.kill(pid, signal.SIGKILL) except Exception, e: self.get_process(pid, "Error sending SIGHUP to process " + str(pid) + ": " + str(e) + "") else: # give OS 5 seconds to kill SECS=5 for i in range(SECS): if not os.access("/proc/" + str(pid), os.F_OK): self.get_root("Process " + str(pid) + " killed") return time.sleep(1) self.get_process(pid, "SIGKILL sent to process " + str(pid) + " but process still alive after " + str(SECS) + " seconds") def do_remove(self, path, context=""): try: if len(path) > 0 and path[-1] == "/": path = path[:-1] if os.path.isdir(path): recursiveRemove(path) else: os.remove(path) parent, tail = os.path.split(path) if context == "du": self.get_du(parent, "Removed " + escape(path)) else: self.get_file(parent, "Removed " + escape(path)) except Exception, e: if context == "du": self.get_du(path, "Error removing " + escape(path) + ": " + str(e) + "") else: self.get_file(path, "Error removing " + escape(path) + ": " + str(e) + "") def do_sig_term(self, pid): try: os.kill(pid, signal.SIGTERM) except Exception, e: self.get_process(pid, "Error sending SIGTERM to process " + str(pid) + ": " + str(e) + "") else: # give OS 5 seconds to kill SECS=5 for i in range(SECS): if not os.access("/proc/" + str(pid), os.F_OK): self.get_root("Process " + str(pid) + " terminated") return time.sleep(1) self.get_process(pid, "SIGTERM sent to process " + str(pid) + " but process still alive after " + str(SECS) + " seconds") def do_nice(self, pid, value): try: r = os.system("renice " + str(value) + " -p " + str(pid)) except Exception, e: self.get_process(pid, "Error setting nice value of process " + str(pid) + ": " + str(e) + "") else: nice = int(getStatField(pid, "nice")) if nice == value: self.get_process(pid, "process " + str(pid) + " niced to " + str(value)) else: self.get_process(pid, "Attempt to renice process " + str(pid) + " to " + str(value) + " failed, nice still " + str(nice)) def get_process(self, pid, message = ""): """ int pid """ windowCmdline = formatProcessCmdLine(pid, True) if windowCmdline != "": windowCmdline = ": " + windowCmdline self.header("process " + str(pid) + windowCmdline, 'process ' + str(pid) + ": " + formatProcessCmdLine(pid)) if message != "": self.write("

" + message + "

") self.writeManSection("/proc/[number]") path = "/proc/" + str(pid) + "/" direc = os.listdir(path) direc.sort() # table of contents self.write("

") i = 0 items = direc + ["children"] if os.getuid() != 0: items.append("warning") items.sort() items = ["up"] + items COLS = 5 for i in range(0, len(items), COLS): self.write("") for f in items[i: i + COLS]: if f == "up": self.write('") else: self.write('") self.write("") self.write("
up' + (15 * " ") + "' + escape(f) + "" + (15 * " ") + "

") # signal actions nice = int(getStatField(pid, "nice")) self.write("

") # need table to get all on one line for sig in ["hup", "kill", "term"]: self.write('') self.write("') self.write("
') self.write('') self.write('
nice:") self.write('
') self.write('') self.write('") self.write('

") self.write("") for f in direc: self.write("") self.write("") self.writeProcessSection(0, "/proc/" + str(pid), f) 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("
") # child processes self.heading("children") children = [] # list int of PIDs 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("") for pid in children: self.write("") self.writeProcessRow(pid) self.write("") self.write("
") self.write("

") # man page sections self.write("


") for f in direc: sec = getManSection("/proc/[number]/" + f) if sec != "": self.write("

" + escape(f) + "

") self.writeManSectionText(sec) self.footer() def writeProcessSection(self, level, direc, item): """ write up a sub directory, file or link within a process all writing is inside a HTML table row recursive /proc/1234 item /proc/1234/sub item @param int level @param direc = directory @item file system item within 'direcory' path = direct/item does not throw """ tok = direc.split("/", 3) if len(tok) == 3: location = item else: location = tok[3] + "/" + item path = direc + "/" + item if os.path.islink(path): # link self.write("") try: self.writeLinkRow(path) except Exception, e: self.write("") self.write("
Error reading link " + escape(path) + ": " + escape(str(e)) + "
") elif os.path.isdir(path): # directory try: self.write("") if level > 10: raise Error("Too many levels of directories") if item == "fd": self.writeFds(path) elif item == "task": self.writeTasks(path) else: d = os.listdir(path) d.sort() self.write("") for f in d: self.write("") self.write("") try: self.writeProcessSection(level + 1, path, f) except Exception, e: self.write("") self.write("") self.write("
" + escape(f) + "Error reading " + escape(path) + ": " + escape(str(e)) + "
") except Exception, e: self.write("Error reading directory " + escape(path) + ": " + escape(str(e)) + "") self.write("") else: # file try: self.write("") data = file(path).read() while len(data) > 1 and data[len(data)-1] == "\n": data = data[:len(data)-1] if len(data) > MAX_DATA: self.write("" + str(len(data)) + ' bytes, see ' + fileRef(path) + "") else: if item == "status": self.writeHorrTable(data) elif item == "maps" or item == "mounts": self.writeHeaderlessTable(item, data) elif item == "stat": self.writeStat(data) elif item == "statm": self.writeBasicManTable(item, data) elif item == "environ": self.writeEnviron(data) elif item == "cmdline": self.write(formatCmdLine(data)) else: self.write("
")
                        self.writeEscape(data, preformatted=True)
                        self.write("
") except Exception, e: self.write("Error reading file " + escape(path) + ": " + escape(str(e)) + "") self.write("") def writeFds(self, path): fds = [] for d in os.listdir(path): fds.append(int(d)) fds.sort() self.write("") for fd in fds: self.write("") self.writeLinkRow(path + "/" + str(fd)) self.write("") self.write("
" + str(fd) + "
") def writeTasks(self, path): pids = [] for d in os.listdir(path): pids.append(int(d)) pids.sort() self.write("") for pid in pids: self.write("") self.writeProcessRow(pid) self.write("") self.write("
") def writeStat(self, data): sec = getManSection("/proc/[number]/stat") if sec == "": 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("") manText = "" for line in sec.split("\n"): if line.find("%") == -1: line = line.strip() if line == "": continue if len(manText) != 0: manText = manText + "\n" manText = manText + line continue if i != 0: self.write("") mantok = line.lstrip().split(" ", 2) # field %format text if len(mantok) > 2: manText = mantok[2] else: manText = "" # format line self.write("") if i < len(tok): self.write("") else: self.write("") i = i + 1 self.write("") for extra in tok[i:]: self.write("") i = i + 1 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]) + "
") self.writeProcessAttribute(mantok[0], tok[i]) self.write("
no value") self.writeManSectionText(manText, preformat=False) self.write("
extra value" + escape(tok[i]) + "
") def writeBasicManTable(self, item, data): sec = getManSection("/proc/[number]/" + item) index = sec.find(":") if sec == "" or index == -1: self.write("
")
             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("") for i in range(n): self.write("") if i < len(headerTok): header = headerTok[i].strip().split(" ", 1) self.write("") else: self.write("") if i < len(dataTok): self.write("") else: self.write("") if i < len(headerTok) and len(header) > 1: self.write("") self.write("") self.write("
" + escape(header[0]) + "extra value" + escape(dataTok[i]) + "" + color(GREEN, escape(header[1].strip()) ) + "
") def writeHeaderlessTable(self, item, data): self.write("") sec = getManSection("/proc/[number]/" + item) index = sec.find(":") if sec != "" and index != -1: for line in sec[index:].split("\n")[1:]: if line.strip() != "": self.write("") for h in line.split(): self.write("") self.write("") break for line in data.split("\n"): pos = line.find(":") self.write("") for field in line.split(): if len(field) > 0 and field[0] == "/": self.writeFileRow(field) else: self.write("") self.write("") self.write("
") self.writeEscape(h) self.write("
" + escape(field) + "
") def writeHorrTable(self, data): self.write("") for line in data.split("\n"): pos = line.find(":") self.write("") if pos == -1: self.write("") else: key = line[0:pos].strip() self.write("") value = line[pos+1:].strip() self.write("") self.write("") self.write("
" + escape(line) + "" + escape(key) + "") self.writeProcessAttribute(key, value) self.write("
") def writeProcessAttribute(self, key, value): self.write("") if key.lower() == "pid": self.write("" + escape(value) + "") elif key.lower() in ["tgid", "ppid"]: if int(value) != 0: # ppid of init is 0 self.write("") self.writeProcessRow(int(value)) self.write("") else: self.write("none") elif key.lower() == "uid": for uid in value.split(): self.write(formatUser(int(uid))) else: self.write(escape(value, preformatted=True)) self.write("") def writeEnviron(self, data): env = data.split("\0") self.write("
") for e in env: if e == "": continue kv = e.split("=", 1) self.write("") self.write("") self.write("") self.write("") self.write("
") self.writeEscape(kv[0]) 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("
") def writeManSection(self, procPath): """ @param procPath /proc path to document""" self.writeManSectionText(getManSection(procPath)) def writeManSectionText(self, sec, preformat=True): """ @param str sec the text""" if sec == "": return self.write("") # green if preformat: 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("") self.write("") for col in headers: self.write("") self.write("") for row in data: self.write("") i = 0 for col in row: self.write("") i = i + 1 self.write("") self.write("
" + escape(col) + "
") 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("
") else: self.writeFileContents(procPath) except Exception, e: self.write("Error reading file " + escape(procPath) + ": " + escape(str(e)) + "") self.writeManSection(procPath) self.footer() def get_proc_dir(self, path): procPath = "/proc/"+ path self.procPathHeader(procPath) if path != "/": tok = path.rstrip("/").split("/")[:-1] if tok == ['']: href = "/du" else: href = "/proc/" + escape(string.join(tok, "/")) else: href = "/" self.write('up
') try: d = os.listdir(procPath) d.sort() for f in d: c = "" if os.path.isdir(procPath + "/" + f): c = "/" self.write('' + escape(f) + c + "
") except Exception, e: self.write("Error reading directory " + escape(procPath) + ": " + escape(str(e)) + "") self.writeManSection(procPath) self.footer() def get_source(self): # serve up the source code self.write("HTTP/1.1 200 OK") self.write("Content-Type: text/plain") self.write("") self.wfile.write(file(sys.argv[0], "r").read()) def get_top(self, sortBy): # top self.header("top") self.write('

up

') text = safePopen("top -b -n 1") # -n 1 = i iteration then exit # -b batch mode, all processes if sortBy == "": sortBy = "-%CPU" #if not os.WIFEXITED(r) or not os.WEXITSTATUS(r) in [0, 1]: # # top returns 1 if ok # 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 line == "": header = False elif body: if line != "": data.append(line.split()) else: tok = line.split() self.write('') i = 0 for col in tok: self.write('') indexes[col] = i i += 1 self.write("") body = True if not body: 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('
') if sortBy in indexes: global compareListsIndex compareListsIndex = indexes[sortBy] data.sort(compareLists) if reverse: data.reverse() pidIndex = -1 if "PID" in indexes: pidIndex = indexes["PID"] for row in data: i = 0 self.write("") for col in row: self.write("") i = i + 1 self.write("") self.write('
") if i == pidIndex: self.write('') self.writeEscape(col) if i == pidIndex: self.write("") self.write("
') def get_sockets(self): self.header("sockets") self.write('

up

') socket = {} # key is int inode # values is (str protocol, list of (str header, str fields)) for proto in PROTOCOLS: fields = getSocketFields(proto) headers, data = parseTable("/proc/net/" + proto) cols = [] # list int of indexes into data[x] for f in fields: i = 0 for h in headers: if f.lower() == h.lower(): cols.append(i) break i = i + 1 inodeIndex = 0 for h in headers: if h.lower() == "inode": for row in data: inode = row[inodeIndex] values = [] for c in cols: if c >= len(row): values.append((headers[c], "")) else: values.append((headers[c], row[c])) socket[inode] = (proto, values) break inodeIndex = inodeIndex + 1 self.heading("sockets") self.write("") inodes = socket.keys() inodes.sort() for inode in inodes: (proto, values) = socket[inode] self.write("") self.write('") self.write("") self.write("") self.write("") self.write("
' + socketRef(inode) + "" + protocolRef(proto) + "") for (header, value) in values: if (header == "remote_address" or header == "rem_address") and not isAnyIpConnection(value): self.write("↔") # <-> self.writeSocketField(header, value) self.write("
") self.footer() def get_socket(self, inode): row = None proto = None for p in PROTOCOLS: try: row = findRow("/proc/net/" + p, "inode", str(inode)) proto = p except Exception, e: pass if row == None: self.writeNotFound() return self.header("socket " + str(inode)) self.write('

up

') self.write("") self.write("") for (header, data) in row: self.write("") self.write("
protocol" + protocolRef(proto) + "
") if header != None: self.writeEscape(header) self.write("") self.writeSocketField(header, data) self.write("
") self.heading("processes") pids, errors = fuser("socket:[" + str(inode) + "]") if len(pids) == 0: self.write("none") else: pids.sort() self.write("") for pid in pids: self.write("") self.writeProcessRow(pid) self.write("") self.write("
") self.footer() def get_pipes(self): self.header("pipes") self.write('

up

') for pid in getAllPids(): self.getProcessInformation(pid) self.write("") inodes = self.cache_pipes.keys() inodes.sort() for inode in inodes: self.write("") self.writePipeRowPidCmdline(inode, self.cache_pipes[inode]) self.write(""); self.write("
") self.footer() def get_pipe(self, inode): self.header("pipe " + str(inode)) self.write('

up

') self.heading("processes") pids, errors = fuser("pipe:[" + str(inode) + "]") if len(pids) == 0: self.write("none") else: self.write("") pids.sort() for pid in pids: self.write("") self.writeProcessRow(pid) self.write("") self.write("
") self.footer() def get_files(self, message=""): self.header("files") if message != "": self.write("

" + message + "

") self.write('

up') self.link("open files") self.link("root directory") self.write('disk usage

') self.heading("open files") self.write("") for pid in getAllPids(): self.getProcessInformation(pid) try: data = file("/proc/" + str(pid) + "/maps").read() for line in data.split("\n"): tok = line.split() if len(tok) > 5 : if len(tok[5]) > 0 and tok[5][0] == "/": if tok[5] not in self.cache_files: self.cache_files.append(tok[5]) except Exception, e: self.writeBadException("Error reading memory maps of process " + str(pid), pid, e) self.cache_files.sort() for f in self.cache_files: self.write("") self.writeFileRow(f) self.write(""); self.write("
") self.heading("root directory") self.writeDirectoryContents("/") self.footer() def dirSize(self, path): # str path of directory global du_cache if path in du_cache: return du_cache[path] size = 0 try: direc = os.listdir(path) except Exception, e: return 0 for f in direc: fpath = path + "/" + f if os.path.islink(fpath): pass elif os.path.isdir(fpath): size += self.dirSize(fpath) else: try: size += fileSize(fpath) except Exception, e: pass # detect when socket to browser goes down #r, w, e = select.select([self.rfile.fileno(), self.wfile.fileno()], [self.rfile.fileno(), self.wfile.fileno()], [self.rfile.fileno(), self.wfile.fileno()], 0) #sys.stdout.write("\r" + str(r) + " " + str(w) + " " + str(e)) # [] [4, 4] [] normal # [4, 4] [4, 4] [] after closes r, w, e = select.select([self.rfile.fileno()], [], [], 0) if len(r) != 0: # leave the cache intact as although it is incomplete # the entries it does have are valid raise Exception("aborted") if self.showDirTime != 0 and time.time() > self.showDirTime: # taken too long self.showDirTime = 0 started = False if self.prefix == "": dirpath = "/" else: dirpath = self.prefix try: direc = os.listdir(dirpath) except Exception, e: direc = [] direc.sort() for f in direc: if os.path.isdir(self.prefix + "/" + f): if not started: self.write("|") started = True self.write('" + escape(f) + " ") du_cache[path] = size return size + fileSize(path) def get_du(self, path, message=""): """ @param str path """ if path != "/" and path[-1] == "/": path = path[:-1] self.pathHeader("disk usage", "du", path) if message != "": 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("

") self.write("") for (name, size) in sizes: self.write("") fpath = self.prefix + "/" + name if name == filesName: self.write("") else: self.write('') self.write("") else: self.write('") self.write("") self.write("") self.write("") self.write("") self.write("") self.write("
') self.write('') self.write('') self.write('') self.write('
" + formatSize(size) + "") if maxSize != 0: self.write("") self.write("") self.write("
 
") if name == filesName: self.write('
' + escape(name) + "" + escape(name) + "/
" + formatSize(total) + "total

") self.write("

File system:

") r = os.statvfs(path) fsfree = r.f_frsize * r.f_bfree fstotal = r.f_frsize * r.f_blocks fsused = fstotal - fsfree self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") self.write("
" + formatSize(fsfree) + "free
" + formatSize(fsused) + "used
" + formatSize(fstotal) + "total

") self.write("

" + 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.

") self.footer() def get_file(self, path, message=""): """ @param str path @param html str message """ fdPids, errors = fuser(path) mmapPids = [] errors = [] # no header so can't print error yet for pid in getAllPids(): try: data = file("/proc/" + str(pid) + "/maps").read() for line in data.split("\n"): tok = line.split() if len(tok) > 5 and tok[5] == path: mmapPids.append(pid) break except Exception, e: errors.append(("Error reading memory maps of the process " + str(pid), pid, e)) if len(fdPids) != 0 or len(mmapPids) != 0: if os.path.isdir(path): self.pathHeader("open directory", "file", path) else: self.pathHeader("open file", "file", path) else: if os.path.isdir(path): self.pathHeader("directory", "file", path) else: self.pathHeader("file", "file", path) for msg, pid, e in errors: self.writeBadException(msg, pid, e) if message != "": self.write("

" + 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("") #drwxr-xr-x 2 overtoun overtoun 4096 2005-12-16 21:16 index_files self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") # Linux specific self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") 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) + "

") except Exception, e: self.write("

Error stating file " + escape(path) + ": " + escape(str(e)) + "

") self.heading("open") if len(fdPids) == 0: self.write("none") else: fdPids.sort() self.write("") for pid in fdPids: self.write("") self.writeProcessRow(pid) self.write("") self.write("
") if len(mmapPids) == 0: if not os.path.isdir(path): self.heading("memory mapped") self.write("none") else: self.heading("memory mapped") mmapPids.sort() self.write("") for pid in mmapPids: self.write("") self.writeProcessRow(pid) self.write("") self.write("
") self.heading("contents") if os.path.isdir(path): self.writeDirectoryContents(path) else: self.writeFileContents(path) self.footer() def writeFileContents(self, path): try: if stat.S_ISFIFO(os.stat(path).st_mode): 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('') # cellspacing=10 is above and below as well as left and right SPACING =" " * 2 self.write('') self.write('') self.write('') self.write("") self.write("") self.write("") self.write("") self.write("") self.write("") self.write('') totalSize = 0 totalSizeComlete = True for f in contents: self.write("") fpath = prefix + "/" + f link = "" # HTML try: if os.path.islink(fpath): dest = os.readlink(fpath) if not os.access(dest, os.F_OK): # bad link link = escape(" -> ") + "" + escape(dest) + "" r = os.lstat(fpath) else: link = escape(" -> " + dest) r = os.stat(fpath) else: r = os.stat(fpath) self.write('') # drwx------ 2 root root 4096 Dec 17 02:49 orbit-root # miss out links as I don't care self.write("") 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("") self.write("") self.write("") self.write("" + formatTime(r.st_mtime)) # 1 + 3 columns except Exception, e: self.write("") if os.path.isdir(fpath): self.write('") else: self.write('") self.write("") self.write("") if totalSizeComlete: self.write("") else: self.write("") self.write("
mode" + SPACING + "size" + SPACING + "user" + SPACING + "group" + SPACING + "last modified" + SPACING + "name
') self.write('') self.write('') self.write('
" + formatFilePermissions(r.st_mode) + "" + SPACING + sizeText + "" + SPACING + formatUser(r.st_uid) + "" + SPACING + formatGroup(r.st_gid) + "" + SPACING + "Error stating file " + escape(fpath) + ": " + escape(str(e)) + "' + SPACING + '' + escape(f) + "/" + link + "' + SPACING + '' + escape(f) + "" + link + "
" + SPACING + formatCommaSize(totalSize) + "" + SPACING + "total
" + SPACING + formatCommaSize(totalSize) + "" + SPACING + "total
") except Exception, e: self.write("

Error reading directory " + escape(path) + ": " + escape(str(e)) + "

") def get_users(self): self.header("users") self.write("

") n = 0 for h in PASSWD_FILE_HEADINGS: if n in [2, 3]: # uid gid self.write("") else: self.write("") n += 1 try: data = file("/etc/passwd").read() for line in data.split("\n"): self.write("") tok = line.split(":") # \: escape? n = 0 for col in tok: if n == 0 and len(tok) > 2 and tok[2].isdigit(): self.write("") elif n in [2, 3]: # uid, gid self.write("") elif n in [5, 6]: # home and shell self.write("") else: self.write("") 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("") self.write("
" + escape(h) + "" + escape(h) + "
" + userRef(int(tok[2]), col) + "" + escape(col) + "" + fileRef(col) + "" + escape(col) + "
") except Exception, e: 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("
") if len(aliaslist) > 0: self.link("aliases") self.write("
") self.link("open sockets") self.write("
") self.link("route") self.write("
") self.heading("hostname") if hostname == None: 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("") self.heading("open sockets") # !@# # @frgaile depends on names of socket protocols self.write("") for p in PROTOCOLS: try: row = findRowByFun("/proc/net/" + p, isSocketAddressHeader, ip, extractIpFromKernalAddress) #self.writeEscape(str(row) + "
") inode = extractListValue(row, "inode") #self.writeEscape(str(inode) + "
") if inode != None: self.write("") self.writeSocketRow(inode) self.write("") break except Exception, e: #self.write(escape(str(e)) + "
") pass # not in this protocol self.write("
") # route last as it takes ages # flush each line self.heading("route") #tracepath 71.117.252.26 #1: 192.168.0.3 (192.168.0.3) 0.176ms pmtu 1500 #1: no reply #7: 0.so-5-1-0.IR2.SAC2.ALTER.NET (210.80.49.5) asymm 8 255.037ms #8: POS2-0.IR2.SAC1.ALTER.NET (137.39.31.206) 425.217ms # Too many hops: pmtu 1500 # Resume: pmtu 1500 signal.signal(signal.SIGCHLD, signal.SIG_DFL) # turns of SIGCHILD handler which breaks popen() which does a wait() itself try: fd = os.popen("tracepath '" + ip + "'") #!@# self.write("") while True: line = fd.readline() if line == "": break line = line[:-1] # strip '\n' self.write("") pos = line.find(":") if pos != -1 and line[0:pos].strip().isdigit(): num = int(line[0:pos].strip()) line = line[pos+1:].strip() self.write("") if line == "no reply": self.write("") 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("") # leave as IP, first column shows hostname else: self.write("") else: # no number self.write("") self.write("") self.flush(); finally: signal.signal(signal.SIGCHLD, sigChildHandler) self.write("
" + str(num) + "no reply" + ipRef(t[1:-1], False) + "" + escape(t) + " " + escape(line) + "
") r = fd.close() if r != None and (not os.WIFEXITED(r) or not os.WEXITSTATUS(r) in [0, 1]): # top returns 1 if ok 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) + "
") ips.append(ip) except Exception, e: self.write("

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("") done = False for line in data.split("\n"): tok = line.split(":") # \: escape? if len(tok) > 2 and tok[2].isdigit() and int(tok[2]) == uid: n = 0 for col in tok: if n < len(PASSWD_FILE_HEADINGS): h = PASSWD_FILE_HEADINGS[n] else: h = "" self.write("") if n in [5, 6]: # home and shell self.write("") elif n == 2: self.write("") else: self.write("") self.write("") n += 1 done = True break self.write("
" + escape(h) + "" + fileRef(col) + "" + escape(col) + "" + escape(col) + "
") except Exception, e: 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("") noParents.sort() for pid in noParents: self.writeProcessAndChildren(pid) self.write("
") self.footer() def writeNotFound(self, message=""): if not self.writtenHeader: self.write("HTTP/1.1 404 Not Found") self.write("Content-Type: text/html") self.write() self.writtenHeader = True self.title("proc-surfer", "page not found"); 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//cmdline self.cache_files = [] # list of str paths self.cache_pipes = {} # key in int pipe inodes # value is list of (int pid, html str cmdline) self.cache_sockets = [] # list of int socket inodes def clear(self): # free all memory self.children = None self.cache_cmdline = None self.cache_files = None self.cache_pipes = None self.cache_sockets = None def do_POST(self): # headers is mimetools.Message self.init() try: path = urllib.unquote(self.path) # convert %20 to " " etc.. # content=HTML encoded query string # action= args = self.rfile.read(int(self.headers["content-length"])) query = cgi.parse_qs(args) action = query["action"][0] if action == "hup": # path = /proc/ pid = int(path[1:].split("/")[1]) self.do_sig_hup(pid) elif action == "kill": # path = /proc/ pid = int(path[1:].split("/")[1]) self.do_sig_kill(pid) elif action == "term": # path = /proc/ pid = int(path[1:].split("/")[1]) self.do_sig_term(pid) elif action == "nice": # path = /proc/ pid = int(path[1:].split("/")[1]) self.do_nice(pid, int(query["value"][0])) elif action == "terminate-server": self.do_terminate_server() elif action == "rm": if path.startswith("/file"): # path = /file/the/file/to/remove path = path[len("/file"):] elif path.startswith("/du"): # path = /du/the/file/to/remove path = path[len("/du"):] else: raise Exception("Non /file path for rm: : " + path) context = "" if "context" in query: context = query["context"][0] self.do_remove(path, context) else: raise Exception("Bad action: " + action) except Exception, e: self.writeError("POST error:" + str(e)) self.clear() # BaseHTTPServer.BaseHTTPRequestHandler method def do_GET(self): #print self.headers path = urllib.unquote(self.path) # convert %20 to " " etc.. if path == "/du" or path.startswith("/du/"): # don't fork() for /du/ as we cache the directory sizes in the parent # leaving SIGCHLD handler in place seem ok as /du/ doesn't spawn anythin self.do_safe_get() else: # fork or can only serve onr request at a time # /ip spawns tracepath which takes ages child_pid = os.fork() if child_pid != 0: # we are the parent self.rfile.close() self.wfile.close() return # we are the child sys.stdin.close() # don't capture Ctrl-Cs signal.signal(signal.SIGCHLD, ignoreSigChildHandler) # signal.SIG_IGN doesn't work # don't mess with popen() children try: self.do_safe_get() finally: os._exit(0) # exit child, no PID file to remove def do_safe_get(self): # catch exceptions and print html error message self.init() try: # gets caught by server main loop anyway self.do_get() except Exception, e: print "do_GET exception:" + str(e) type, value, tb = sys.exc_info() #traceback.print_tb(tb, None, sys.stdout) self.writeError("GET error: " + str(e)) self.clear() self.flush() ############################################################################################################ # # check that no other instance is running while os.access(PID_FILE, os.F_OK): try: pid = int(file(PID_FILE).read()) except Exception, e: sys.stderr.write("Error reading PID file '" + PID_FILE + "': " + str(e) + "\n") sys.stderr.write("Other instances of '" + execName + "' may be running\n"); break try: os.kill(pid, 0) except OSError, e: if e.errno == errno.ESRCH: # no such process, sweet break sys.stderr.write("Error checking existance of process " + str(pid) + ": " + str(e) + "\n") break try: cmdline = file("/proc/" + str(pid) + "/cmdline").read() except Exception, e: sys.stderr.write("Can not terminate command line of possible instance of '" + execName + "' (pid " + str(pid) + ")\n"); break if cmdline.find(execName) == -1: # must be some other process break try: os.kill(pid, signal.SIGKILL) # kill -9 so dies if children blocked, children die too except Exception, e: sys.stderr.write("Can not terminate other instance of '" + execName + "' (pid " + str(pid) + ")\n"); break sys.stderr.write("Termianted other instance of '" + execName + "' (pid " + str(pid) + ")\n"); try: pidFd = file(PID_FILE, "w") pidFd.write(str(os.getpid())) pidFd.close() except Exception, e: sys.stderr.write("Error writing PID file '" + PID_FILE + "': " + str(e) + "\n") sys.stderr.write("Other instances of '" + execName + "' may be running\n"); # ---------------- port = 0 # 0 to automatically allocate a free one and lauch firefox # browsing it host = "localhost" page = "" if len(sys.argv) > 1: if sys.argv[1] == "-?": print "syntax:", sys.argv[0], "[port | page]" print print "With port specified program will serve web pages from that port" print "Otherwise a web browser will be spawned reading from the program" print "url is relative within the served, e.g. /file/dev/dsp" sys.exit(0) elif sys.argv[1].isdigit(): port = int(sys.argv[1]) host = "" # any interface else: page = sys.argv[1] if page[0:1] != "/": page = "/" + page httpd = BaseHTTPServer.HTTPServer((host, port), ProcSurferHttpHandler) child_pid = None if port == 0: #os.system("firefox http://localhost:" + str(httpd.server_port)) child_pid = os.fork() if child_pid == 0: # we are the child process browser = "firefox" if "BROWSER" in os.environ: browser = os.environ["BROWSER"] sys.stdin.close() # otherwise browser gets stdin and Ctrl-C does work sys.stdout.close() sys.stderr.close() os.execlp(browser, browser, "http://localhost:" + str(httpd.server_port) + page) # we can't exit the server when the browser exits beacuse if firefox is already # running the new child will just tell the existing firefox to load the page in # an tab and exit else: sys.stderr.write("Point web browser to http://" + socket.gethostname()) if port != 80: sys.stderr.write(":" + str(port)) sys.stderr.write("\n") #httpd.serve_forever() def ignoreSigChildHandler(sig, stack): pass def sigChildHandler(sig, stack): # waiting makes os.popen() to read man page fail after we have terminated a process # turn off signal when need to do this try: pid, exitStatus = os.wait() except Exception, e: sys.stderr.write("error waiting in sig child: " + str(e) + "\n") def sigAlarmHanler(sig, stack): global child_pid sts.stdout.write("sigAlarmHanler " + str(sig) + "\n") #print os.kill(child_pid) sts.stdout.write(str(os.access("/proc/" + str(child_pid), os.F_OK))) #signal.signal(signal.SIGALRM, sigAlarmHanler) #signal.alarm(1) #os._exit(0) #os.wait() #signal.signal(signal.SIGCHLD, sigAlarmHanler) #signal.signal(signal.SIGALRM, sigAlarmHanler) #signal.alarm(1) signal.signal(signal.SIGCHLD, sigChildHandler) # if don't we can't wait() and free zombie children httpd.serve_forever() while True: #httpd.serve_forever() try: (r, w, e) = select.select([httpd.fileno()], [httpd.fileno()], [httpd.fileno()], 0.001) except Exception, e: continue # interrupt from SIGCHLD #print str((r, w, e)) if len(r) > 0: print "handling" httpd.handle_request() print "done"