#!/usr/bin/python
#
# proc-surfer.py
VERSION = "1.0.32"
COPYRIGHT_YEARS = "2005-2017"

# By Gene Thomas
# gene@genethomas.com
# http://genethomas.com
#
# provides a HTML interface to the /proc filesystem
# cross referencing:
#     - processes
#     - files
#     - directories
#     - 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
#
# usage: proc-surfer.py [-p http-port] [-R]
#     if no http-port is given a free one will be used
#     launches the web browser
#     -R,--remote to allow remote access, a security hole
#
# 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 /proc 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
#     /guishell/dir
#        start nautilus/Windows Explorer browsing dir
#     /terminal/dir
#        start gnome-terminal/Command Prompt  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 (#FF7000)
#    grey   undisplayable text
#    green  man page extracts
#
# developed against Linux kernels:
#     2.6.12-9-amd64-generic
#     2.6.17-11-generic
#     2.6.15-23-amd64-generic
#     3.5.0-45-generic          Ubuntu 10.4 LTS
#     3.11.10-301.fc20.x86_64   Fedora Core 20
# 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
# Disk Usage, files and directories also tested against Windows XP
#
# Version History
# 1.0.32  Added --remote switch to listen on all IPs
#         Fix man page parsing for Ubuntu 16.04.3 LTS (Linux 4.4.0-92-generic)
# 1.0.30  Fix spaces in file and directory names
#         Tests in tests/proc-surfer
# 1.0.29  Fix bad link "up" in /proc/ sub dir pages
# 1.0.28  Use -p to specify port
# 1.0.27  Fix escaping paths in /file/...
#         Show text as UTF-8 if it is by default
# 1.0.26  Make more robust handling failure reading /stat and /status files
# 1.0.25  Fix UTF-8 in man page handling
# 1.0.24  Fix indenting of man page contents
# 1.0.23  Always show url on startup
# 1.0.22  Fix parseing /fd section of proc man
#           
# 1.0.21  Fix for Linux 3.5.0-4/Ubuntu 12.04.0 and Python 2.7.3
#         Spawning child process fails writing to socket
#         Flexible man page parsing
#         Show normal errors in grey, e.g. /proc/addr/[pid]/exec
#
# 1.0.20  Detect reparse points on Windoze (os.path.islink() doesn't work on windows)
#
# 1.0.19  Files and Directories working on Windoze
#
# @idea:  use css
# @idea:  check if user meant to hit [delete] button!
# @idea:  if not root state in /files that not all files shown
#
# Depends on:
#         man-pages (/usr/share/man/man5/proc.5.gz)
#         col
#
# Implementation
#
# strings are UTF-8

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
import ctypes
import subprocess
import argparse

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 = 1024 * 1024
GREEN = "#008000"
ORANGE = "#FF7000"
PASSWD_FILE_HEADINGS = ["name", "password", "id", "group id", "comment", "home", "shell"]

# url is /page/rest
# so we can detect changes
debug = False
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, utf8=None):
    """ @param framgment mean limit lies to given number of characters
               0 means don't wrap
        @param preformatted
               True means leave \t and \n intact
        @param input is utf?8
               True:  input is UTF-8 text
               False: url escape non ASCII if False
               None:  see if input is valid UTF-8
    """   
    if type(input) != type(""):
        input = str(input)
        
    if utf8 == None:
        try:
            input.decode("utf-8")
        except Exception, e:
            utf8 = False
        else:
            if "\0" in input:
                utf8 = False
            else:
                utf8 = True
    
    if utf8:
        # could split an UTF-8 char
        wrap = 0
    r = ""
    n = 0
    l = 0
    for s in input:
        if s == "<":
            r = r + "&lt;"
            l = l + 1
        elif s == ">":
            r = r + "&gt;"
            l = l + 1
        elif s == "&":
            r = r + "&amp;"
            l = l + 1
        elif s == "\"":
            r = r + "&quot;"
            l = l + 1
        elif s >= " " and s <= "~":
            # ASCII
            r = r + s
            l = l + 1
        else:
            # control char or non ASCII
            if utf8 and s != "\0" and ord(s) >= 0x80:
                # leave UTF-8 bytes intact
                r = r + s
                l = l + 1
            elif preformatted and (s == "\t" or s == "\n"):
                r = r + s
                l = l + 1
            else:
                r = r + "<font color=grey>%" + ("%02X" % ord(s)) + "</font>"
                l = l + 3
        if wrap != 0 and l > n + wrap:
            r = r + "<br/>"
            n = l
    return r

def urlEscape(url, utf8=False):
    """
        str url

        escape a URL part
    """
    return escape(urllib.quote_plus(url, ""))

def urlEscapePath(url, utf8=False):
    """
        str url

        escape a URL / separated filesystem path
        / left as is, / never in a path part
    """
    return escape(urllib.quote_plus(url, "/"))

def color(color, str):
    """ @param str color
        @param html str str
    """
    return "<font color=\"" + color + "\">" + str + "</font>"

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
    
    if os.name == "nt":
	return None, ""

    # 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

    # restore stderr
    os.dup2(stderr_fileno, sys.stderr.fileno())
    
    return None, procManPage

def removeIndent(text):
    """
       remove identation that every line has
       
       str text
    """
    minIndent = None
    for line in text.split("\n"):
        if line == "" or line.isspace():
            continue
        m = re.search(r'(^[ ]+)', line)
        thisIndent = 0
        if m == None:
            # a line is not indented
            # so leave all as is
            return text
            #return "[" + line + "][" + text + "]"
        else:
             thisIndent = m.end(0)
        if minIndent == None or thisIndent < minIndent:
            minIndent = thisIndent
    if minIndent == None:
        return text
    r = []
    for line in text.split("\n"):
        if line == "" or line.isspace():
            line == ""
        else:
            line = line[minIndent:]
        #r.append("'" + line + "'")
        r.append(line)
    return "\n".join(r)
            
assert removeIndent("   text") == "text"
assert removeIndent("  text\n next") == " text\nnext"
  
def getManSection(procPath):
    """ extract the given section from the man page
        @return "" if not found
                not all proc paths have man page sections
        @fragile, relies on format of man page
    """
    global debug
    
    if debug: print "getManSection(" + procPath + ")"
    r, page = readManPage()
    if debug: print " r", r
    if debug: print " page", len(page.split("\n")), "lines"
    if debug: print " find", page.find(procPath)
    if r != None:
        return ""
    
    # section e.g. /proc/[number]/cwd
    #              /proc/[pid]/wchan (since Linux 2.6.0)
    #          not /proc/[pid]/ in paragraph indented by lots
    regex = r'^([ \t]{0,10})' + re.escape(procPath) + r'( |\t|$)'
    if debug: print " regex '" + regex + "'"
    if debug: print " re-search", re.search(re.escape(procPath), page)
    m = re.search(regex, page, re.MULTILINE)
    if m == None:
        if debug: print " not found"
        return ""
    else:
        if debug: print " found"
        # next /proc section
        # same indent
        indent = m.group(1)
        rest = page[m.end(0):]
        # line may be "/proc/[pid]/coredump_filter (since kernel 2.6.23)"
        firstLine, nl, rest = rest.partition("\n")
        firstLine = firstLine.strip(" ")
        mend = re.search(r'^' + re.escape(indent) + '/proc', rest, re.MULTILINE)
        if mend == None:
            # next man page section, e.g. "NOTES", "SEE ALSO"
            mend = re.search("^[A-Z]", rest, re.MULTILINE)
            if mend == None:
                return rest.strip(" \t\n")
        text = rest[0:mend.start()].rstrip(" \t\n")
        text = firstLine + "\n" + removeIndent(text)
        text = text.strip("\n")
        if debug: print " text", len(text.split("\n")), "lines"
        if len(text.split("\n")) > 500:
            if debug: print "too long, something is wrong, ignore"
            text = ""
        #if debug:
        #    for line in text.split("\n"):
        #        print " text:", line
        return text

def processRef(pid):
    return '<a href="/proc/' + str(pid) + '">' + str(pid) + "</a>"

def fileRef(path, name=None):
    """
       name   optional displayed name override
    """
    if name == None:
        name = path
    return '<a href="/file' + urlEscapePath(path) + '">' + escape(name, utf8=True) + "</a>"

def ipRef(ip, convertIp = True):
    hostname = ip
    if convertIp:
       hostname = getFirstHostname(ip)
    return '<a href="/ip/' + escape(ip) + '">' + escape(hostname) + "</a>"

def domainRef(domain):
    return '<a href="/domain/' + escape(domain) + '">' + escape(domain) + "</a>"

def socketRef(inode):
    """ @param int inode"""
    if inode == 0:
        return ""
    else:
        return '<a href="/socket/' + str(inode) + '">' + str(inode) + "</a>"

def protocolRef(proto):
    """ @param str protocol"""
    return '<a href="/proc/net/' + urlEscape(proto) + '">' + escape(proto) + "</a>"

def pipeRef(inode):
    """ @param int inode"""
    return '<a href="/pipe/' + str(inode) + '">' + str(inode) + "</a>"

def userRef(uid, name=""):
    """ @param int uid"""
    if os.name == "nt":
        return ""
    if name == "":
        name = str(uid)
    return '<a href="/user/' + str(uid) + '">' + escape(name) + "</a>"

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
    if os.name == "nt":
        return [], []
    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.startswith(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 <i>ANY</i>
        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 "<i>ANY</i>:" + str(port)
    return ipRef(ip) + ":" + str(port)

def formatProcessCmdLine(pid, blankOnFail = False):
    """ @param in pid 
        does not throw
    """
    cmdLineFile = "/proc/" + str(pid) + "/cmdline"
    try:
        cmdLine = file(cmdLineFile).read(1024 * 1024)
        # read max 1MiB        
    except IOError, e:
        pass
    else:
        if cmdLine == "":
            e = Exception("Empty " + cmdLineFile)
        else:
            return formatCmdLine(cmdLine)
      
    try:
        name = getProcessAttribute(pid, "Name")
    except Exception, e2:
        if blankOnFail:
	    return ""
        else:
            return "<font color=red>Error reading command line of process " + str(pid) + ": " + escape(str(e)) + ", " + escape(str(e2)) + "</font>"
    else:
        return "<i>" + escape(name) + "</i>"
         

def formatCmdLine(data, blankOnFail = False):
    """
        output in /proc/[number|pid][/cmdline] in HTML
        is \0 separated with trailing \0
        
        does not throw
    """
    if len(data) == 0:
	return "<i>no command line</i>"
    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 """
	if os.name == "nt":
	   return []
	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")
    # status/uid = 4 numbers Real, effective, saved set, and file system UIDs (GIDs)
    return int(data.split()[0]);

def getProcessAttribute(pid, attribName):
         """ read info from /proc[pid]/status of PID
             @param int pid 
             @param str attribName
             @throw if can't get
             
             @fragile depends on format of /proc/[pid]/status
             
             /proc[pid]/status is internet text (email header) format
             
             Key: value
             
          """
         # to test robustness if /proc[pid]/status not readable
         #raise Exception("[No " + attribName + "]")
         
         attrib = attribName.lower() + ":"
         statusFile = "/proc/" + str(pid) + "/status"
         data = file(statusFile).read(MAX_DATA).lower()
         if len(data) == MAX_DATA:
              raise Exception(str(MAX_DATA) + " or more bytes in " + statusFile)
         # read max 1MiB
         # 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

	THROWS if not found

	@fragile depends on format of /proc/[pid]/stat man page section
	
	/proc/[pid]/stat is space separated list
	the column names are in the man page
    """
    global debug, statIndexes
    if debug: print "getStatField(" + str(pid) + ", " + field + ")"
    if len(statIndexes) == 0:
        if debug: print " getting stat fields"
        sec = getManSection("/proc/[number]/stat") 
        if sec == "":
            sec = getManSection("/proc/[pid]/stat") 
        if sec == "":
            raise Exception("No man page section /proc/[number|pid]/stat to get stat column for '" + field + "'")
        i = 0
	# find e.g. "nice %d"
	#           "(19) nice  %ld
	#           "(43) guest_time  %lu  (since Linux 2.6.24)"
	#  but not "The format for this field was %lu before Linux 2.6."
        for line in sec.split("\n"):
            line = re.sub("[(][^)]*[)]", "", line)
            line = re.sub("[ \t]+", " ", line)
            mantok = line.strip().split(" ")            
            for n, tok in enumerate(mantok):
                if tok.startswith("%") and n == len(mantok) - 1:
                    assert n != 0
                    fieldName = mantok[n-1].lower()
                    if debug: print " stat field '" + fieldName + "' = ", i
                    statIndexes[fieldName] = i
                    i = i + 1
                    break

    if not field in statIndexes:
        raise Exception("No such stat field as '" + field + "'")

    data = file("/proc/" + str(pid) + "/stat").read(MAX_DATA)
    if len(data) == MAX_DATA:
	raise Exception(str(MAX_DATA) + " or more bytes in /proc/[pid]/stat")
    # read max 1 MiB
    if debug: print " index", statIndexes[field]
    value = data.split()[statIndexes[field]]
    if debug: print " value", value
    return value;

def isLink(path):
    """
        str path
            os path
        
        os.path.islink() that works on windows
    """
    if os.name == "nt":
        # http://www.flexhex.com/docs/articles/hard-links.phtml
        FILE_ATTRIBUTE_REPARSE_POINT = 0x400
        # http://msdn.microsoft.com/en-us/library/aa364944(VS.85).aspx
        if type(path) == type(u""):
            attr = ctypes.windll.kernel32.GetFileAttributesW(path)
        else:
            attr = ctypes.windll.kernel32.GetFileAttributesA(path)
        if attr == -1:
            return False
        return (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0
    else:
        return os.path.islink(path)

def fixPath(path):
	if os.name == "nt":
	   path = path.replace("\\", "/")	   
	
	if os.name == "nt" and isWindowsDrivePath(path) and len(path) == 4:
	   # /C:/
	   pass
	elif path != "/" and path.endswith("/"):
	   # /Dir/ -> /Dir
	   path = path[:-1]
	   
	return path
	
def isWindowsDrivePath(path):
    # /c:/Program Files
    return os.name == "nt" and len(path) > 2 and path[0] == "/" and path[2] == ":"

def osPath(path):
    # Windows:  "/c:/Program Files" ->   "c:\\Program Files
    if os.name == "nt":
        if isWindowsDrivePath(path):
	    path = path[1:]
	    #if not path.endswith("/") and not path.endswith("\\"):
	    #    path += "/"
	path = path.replace("/", "\\")
	return path.decode("utf-8")
    else:
        return path

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):
    """
       return HTML for user uid
    """
    try:
        return userRef(uid, getUserName(uid))
    except Exception, e:
        return "<font color=red>" + userRef(uid) + ": "  + escape(str(e)) + "</font>"
    
def getUserName(uid):
        """ @throw on failure """
	if os.name == "nt":
	    return ""
        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:
	if os.name == "nt":
	    return ""
        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 "<font color=red>" + str(gid) + ": "  + escape(str(e)) + "</font>"

month = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def formatTime(t):
    # 3 <td> columns
    global month
    lt = time.localtime(t)
    if lt.tm_year == time.localtime().tm_year:
        # Dec  3 01:35 
        return '<td class=right>' + month[lt.tm_mon] + ('</td><td class=right>%2d</td><td class=right>%02d:%02d</td>' % (lt.tm_mday, lt.tm_hour, lt.tm_min))
    else:
        # Mar 27  2004
        return '<td class=right>' + month[lt.tm_mon] + ('</td><td class=right>%2d</td><td class=right>%5d</td>' % (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 "<font color=red>Can not determine file type: " + escape(str(e)) + "</font>"

def recursiveRemove(path):
    """ recisively remove @param str path 
         path is is nos form """
    # make is read only
    try:
	os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
    except:
	pass
    if os.path.isdir(path):    
        for f in os.listdir(path):
            fpath = os.path.join(path, f)
	    recursiveRemove(fpath)
        os.rmdir(path)
    else:
        os.remove(path)

def fileSize(fpath):
    r = os.stat(fpath)
    if os.name == "nt":
	return r.st_size 
    else:
	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.name != "nt" and os.getuid() != 0:
            # orange
            self.write("<p><a name=user_warning><font color='#FF7000'>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.</font></a></p>")

    def writeUserWarningLink(self):
        if os.name != "nt" and os.getuid() != 0:
            self.write('<a href="#user_warning">warning</a>')

    def writeEscape(self, s = "", wrap=0, preformatted=False, utf8=False):
        # write a line of HTML
        # escaping SGML special characters and non primntable ASCII
        self.wfile.write(escape(s, wrap=wrap, preformatted=preformatted, utf8=utf8) + "\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; charset=UTF-8")
	    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 = '/<a href="/">proc</a>/'
        # path = /proc/
        for p in path.split("/")[2:-1]:
            link = link + "/" + p
            heading = heading + '<a href="/proc' + urlEscapePath(link) + '">' + escape(p, utf8=True) + "</a>/"
        self.header(path, heading + "<b>" + escape(path.split("/")[-1], utf8=True) + "</b>")

    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 or Windows c:/Program Files
        """
        if path == "/":
	    self.header(textPrefix)
            return
	    
	# /c:/Program Files
        link = ""        
        heading = "" #'<a href="/"' + page + '">/</a>'
        if os.name == "nt" and isWindowsDrivePath(path) and len(path) == 4:
	  # /c:/
	  last = osPath(path).encode("utf-8")
	else:
          for p in path.split("/")[:-1]:
            if link == "" and p == "":
		if os.name == "nt":
		    continue
   	        link = p
            else:
 	        link = link + "/" + p
            heading += '<a href="/' + urlEscapePath(page) + urlEscapePath(link) + '">' + escape(p, utf8=True)
            if p == "":
                heading += os.path.sep + "</a>" # root dir / is part of the link
            else:
                heading += "</a>" + os.path.sep
	  last = path.split("/")[-1]
        self.header(textPrefix + " " + path, textPrefix + " " + heading + "<b>" + escape(last, utf8=True) + "</b>")

    def writeProcessRow(self, pid):
        """ @param int pid"""
        self.write('<td>' + processRef(pid) + "</td><td>" + formatProcessCmdLine(pid) + "</td>")

    def writeFileRow(self, path):
        self.write("<td>" + fileRef(path) + "</td>")

    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("<b>" + escape(value, wrap) + "</b>")
            else:
                self.writeEscape(value, wrap)

    def writeSocketRow(self, inode, wrap=0):
        self.write('<td>' + socketRef(inode) + "</td>")
        row = None
        proto = None
        # @fragile 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("<td>" + protocolRef(proto) + "</td>")
            
        self.write("<td>")
        # 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("&#x2194;") # <->
                     self.writeSocketField(header, value, wrap)
                     # newline sepatated fields
        self.write("</td>")

    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('<td>' + pipeRef(inode) + "</td>")
           start = True
           self.write("<td>")
           for (pid, cmdline) in cmdlines:
                if not start:
                    self.write("<br/>")
                else:
                    start = False 
                self.write("<a href=/proc/"+ str(pid) + ">" + cmdline + "</a>")
           self.write("</td>")

    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("<html>")
	self.write('<style type="text/css">')
        self.write('.right { text-align: right }')
        self.write('th, td { vertical-align: top; text-align: left }')
	self.write('</style>')
        if window != "":
            self.write("<head>")
            self.write("<title>" + escape(window) + "</title>")
            self.write("</head>")
        self.write("<body style='font-family: sans-serif'>")
        if page != "":
            self.write("<h2>" + page + "</h2>")
	#neither work in firefox
	#self.write('<p><a href="javascript: window.close()">Close this Window</a></p>')
        #self.write("<p><form><button type=submit name=close onclick='window.close()'>Close</button></form></p>")

    def footer(self):
        self.writeUserWarning()
        self.write('<p>')

	if os.name == "nt":
	    # hard to Ctrl-C out on Windoze
	    self.write('<form action=/ method=post>')
	    self.write('<input type=submit name=action value=terminate-server>')
	    self.write('</form>')

	self.write('<a href="/source">proc-surfer.py</a> v' + VERSION + ', &copy; ' + escape(COPYRIGHT_YEARS) + ' <a href="http://genethomas.com">Gene Thomas</a></p>')
        self.write("</body>")
        self.write("</html>")

    def heading(self, h):
        self.write('<h3><a name="' + escape(h) + '">' + escape(h) + "</a></h3>")

    def link(self, dest, text = ""):
        """ str dest
            str html text
        """
        if text == "":
	    self.write('<a href="#' + escape(dest) + '">' + escape(dest) + "</a>")
        else:
	    self.write('<a href="' + escape(dest) + '">' + text + "</a>")


    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("<p>" + message + "</p>")
	r, page = readManPage()	
	self.write('<p>')
	self.link("/proc")
        self.link("processes")
        if r == None:
            self.link("man page")
        self.writeUserWarningLink()
        self.write("<br>")
        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('</p>')
	
	self.heading("/proc")

	if os.name == "nt":
	    self.write("<p>On windows only");
	    self.link("/du", "disk usage")
	    self.write("works.</p>");

	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("<p><table>")
        for i in range(0, len(items), COLS):
	    self.write("<tr>")
	    for f in items[i: i + COLS]:
               c = ""
               if os.path.isdir("/proc/" + f):
                   c = "/"
               self.write('<td><a href="/proc/' + urlEscape(f) + '">' + escape(f) + c + "</a>" + (15 * "&nbsp;") + "</td>")
	    self.write("</tr>")	    
	self.write("</table></p>")

        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)
             except Exception, e:
                 self.write("<font color=red>Can not find ppid of " + str(pid) + ": " + str(e) + "</font><br>")
             else:
                 if ppid == 0:
                    noParents.append(pid)
                 else:
                    if ppid in self.children:
                         self.children[ppid].append(pid)
                    else:
                         self.children[ppid] = [pid]

        if len(noParents) == 0:
            self.write("<p><font color=red>All processes have parents!</font></p>")
            noParents = pids
            self.children = {}
        
        self.heading("processes")
        self.write("<table>")

	# caches blanked in do_GET

        noParents.sort()
        for pid in noParents:
             self.writeProcessAndChildren(pid)

        self.write("</table>")

        if r == None:
            self.heading("man page")           
            self.write("<font color=" + GREEN + "><pre>") # green
            self.writeEscape(page, preformatted=True, utf8=True)
            self.write("</pre></font>")
        else:
            self.write("<-- no 'man proc', exist status " + str(r) + " -->")

	self.write('<td><form action=/ method=post>')
	self.write('<input type=submit name=action value=terminate-server>')
	self.write('</form></td>')
        self.footer()

       except Exception, e:
         self.write("<font color=red>Error displaying /proc: " + str(e) + "</font>")
         self.footer() 

    def writeProcessAndChildren(self, pid):
             """ int pid
             """
             ok, cmdline = self.getProcessInformation(pid)

             self.write("<tr>")
             self.write('<td><a name="pid-' + str(pid) + '">')
             if not ok:
                 self.write('<i>')
             self.write(processRef(pid))
             if not ok:
                 self.write('</i>')
             self.write('</a></td><td>')
             try:
                 user = getProcessUser(pid)
             except Exception, e:
                 self.write(" <font color=red>Can not determine user of process " + str(pid) + ": " + escape(str(e)) + "</font><br>")
             else:
                 self.write(" " + formatUser(user))
             self.write(cmdline)
             self.write("</td>")
             self.write("</tr>");
             
             if pid in self.children:
                 self.write("<tr><td></td><td><table>");
                 children = self.children[pid]
                 children.sort()
                 for child in children:
                     self.writeProcessAndChildren(child)
                 self.write("</table></td></tr>")

    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 <tr> row if exception is bad
            """
	    if os.name != "nt" and 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("<tr><td><font color=red>" + op + ": " + escape(str(theErrno)) + " " + what + "<font></td></tr>")
		    return False
            else:
                 self.write("<tr><td><font color=red>" + op + ": " + escape(str(e)) + "</font></td></tr>")
	 	 return False

    def writeLink(self, heading, path):
        # write an html heading and link data
        self.heading(heading)
        self.write("<table><tr>")
        self.writeLinkRow(path)
        self.write("</tr></table>")
        if not self.writeManSection("/proc/[number]/" + path.split("/", 3)[3]):
            self.writeManSection("/proc/[pid]/" + path.split("/", 3)[3])

    def writeLinkRow(self, path):
        # write link suitable to be between <tr></tr>
        try:
            link = os.readlink(path)
            if len(link) >= 1 and link[0] == "/":
                if os.path.isdir(link):
                    self.write("<td>directory</td>")
                else:
                    self.write("<td>file</td>")
                self.writeFileRow(link)
            else:
                tok = link.split(":")
                if len(tok) == 1:
                    self.write("<td>" + escape(link) + "</td>")
                else:
                    # socket:1234
                    # socet:[1234]
                    # pipe:4321
                    # pipe:[4321]
		    # anon_inode:[eventfd]
		    try:
                        inode = int(tok[1].strip("[]"))
		    except Exception, e:
                        inode = None
                    if tok[0] == "socket" and inode != None:
                        self.write("<td>socket</td><td><table><tr>")
                        try:
                            self.writeSocketRow(inode)
                        finally:
                            self.write("</tr></table></td>")
                    elif tok[0] == "pipe" and inode != None:
                        self.write("<td>pipe</td><td><table><tr>")
                        try:
                            self.writePipeRow(inode)
                        finally:
                            self.write("</tr></table></td>")
                    else:
                        self.write("<td>" + escape(link) + "</td>")
        except Exception, e:
            self.write("<td colspan=2><font color=red>Error reading link " + escape(path) + ": " + escape(str(e)) + "</font></td>")
            # colspan=0 (all columns in colgroup) doesn't work
            # colspan=2 covers FD type (e.g. socket) and the description

    def do_sig_hup(self, pid):
	try:
	    os.kill(pid, signal.SIGHUP)
	except Exception, e:
	    self.get_process(pid, "<font color=red>Error sending SIGHUP to process " + str(pid) + ": " + str(e) + "</font>")
	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()
	if os.name != "nt":
	    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, "<font color=red>Error sending SIGHUP to process " + str(pid) + ": " + str(e) + "</font>")
	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]           
	    fpath = osPath(path)
            recursiveRemove(fpath)
            parent, tail = os.path.split(path)
            if context == "du":
                self.get_du(parent, "Removed " + escape(fpath, utf8=True))
	    else:
	    	self.get_file(parent, "Removed " + escape(fpath, utf8=True))
	except Exception, e:
            if context == "du":
	        self.get_du(path, "<font color=red>Error removing " + escape(fpath, utf8=True) + ": " + str(e) + "</font>")
            else:
                self.get_file(path, "<font color=red>Error removing " + escape(fpath, utf8=True) + ": " + str(e) + "</font>")
      
    def do_sig_term(self, pid):
	try:
	    os.kill(pid, signal.SIGTERM)
	except Exception, e:
	    self.get_process(pid, "<font color=red>Error sending SIGTERM to process " + str(pid) + ": " + str(e) + "</font>")
        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, "<font color=red>Error setting nice value of process " + str(pid) + ": " + str(e) + "</font>")
	else:
	    # ran renice ok
	    try:
	        nice = int(getStatField(pid, "nice"))
	    except Exception, e:
	          self.get_process(pid, "<font color=red>Error getting nice value of process " + str(pid) + " after being set: " + str(e) + "</font>")
	    else:
	        # got nice value ok
                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, '<a href="/#pid-' + str(pid) + '">process</a> ' + str(pid) + ": " + formatProcessCmdLine(pid))

	if message != "":
	    self.write("<p>" + message + "</p>")

        if not self.writeManSection("/proc/[number]"):
	    self.writeManSection("/proc/[pid]")
        path = "/proc/" + str(pid) + "/"

        direc = os.listdir(path)
        direc.sort()

	# table of contents
        self.write("<p><table>")
	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("<tr>")
	    for f in items[i: i + COLS]:
                if f == "up":
                    self.write('<td><a href=/>up</a>' + (15 * "&nbsp;") + "</td>")
                else:
		    self.write('<td><a href="#' + urlEscape(f) + '">' + escape(f) + "</a>" + (15 * "&nbsp;") + "</td>")
	    self.write("</tr>")	    
	self.write("</table></p>")
        
	# signal actions
	nice = None
        niceError = ""
	try:
	    nice = int(getStatField(pid, "nice"))
        except Exception, e:
            # problem decoding man page?
            niceError = str(e)
        self.write("<p><table><tr>") # need table to get all on one line
	for sig in ["hup", "term", "kill"]:
	    self.write('<td><form action="/proc/' + str(pid) + '" method=post>')
	    self.write('<input type=submit name=action value=' + sig + '>')
	    self.write('</form></td>')
        if nice != None:
            self.write("<td>nice:</td><td>")
	    self.write('<form name=renice action="/proc/' + str(pid) + '" method=post>')
	    self.write('<input type=hidden name=action value=nice>')
            self.write('<select name=value onchange="renice.submit()">')
            for v in range(10, -21, -1):
                if nice == v:
                    self.write("<option selected>" + str(v) + "</option>")
                else:
                    self.write("<option>" + str(v) + "</option>")
            self.write("</select>")
	    self.write('</form></td>')
	    self.write("</tr></table></p>")
        else:
            self.write("<font color=red>Error obtaining nice value: " + escape(niceError) + "</font>") 

        self.write("<table>")
        for f in direc:
            self.write("<tr>")
            self.write("<th>")
            haveMan = getManSection("/proc/[number]/" + f) != ""
	    if not haveMan:
                haveMan = getManSection("/proc/[pid]/" + f) != ""
            
            # doesn't hit the disk so not too inefficient to do twice
            self.write('<a name="' + escape(f) + '">')
            if haveMan:
                self.write('<a href="#man-' + urlEscape(f) + '">')
            self.writeEscape(f)
            if haveMan:
                self.write('</a>')
            self.write('</a>')
            self.write("</th>")
            self.writeProcessSection(0, "/proc/" + str(pid), f)
            self.write("</tr>")
        self.write("</table>")

        # child processes
        self.heading("children")
        children = []
        # list int of PIDs
	self.write("<p>")
        for f in os.listdir("/proc"):
             if not f.isdigit():
                 continue                 
             try:
                 ppid = getProcessParent(str(f))
             except Exception, e:
                 self.write("<font color=red>Can not find ppid of " + str(f) + ": " + str(e) + "</font><br>")
             else:
                 if ppid == pid:
                     children.append(int(f))
             
        if len(children) == 0:
            self.write("<i>none</i>")
        else:
            children.sort()
            self.write("<table>")
            for pid in children:
                self.write("<tr>")
                self.writeProcessRow(pid)
                self.write("</tr>")
            self.write("</table>")
	self.write("<p>")

        # man page sections
        self.write("<hr>")
        for f in direc:
            sec = getManSection("/proc/[number]/" + f)
            if sec == "":
		sec = getManSection("/proc/[pid]/" + f)
            if sec != "":
                self.write("<h3><a name=man-" + escape(f) + ">" + escape(f) + "</a></h3>")
                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 <tr></tr>
            recursive
            /proc/1234 item
            /proc/1234/sub item
            @param int level
            @param direc = directory
            @item  file system item within 'directory'
                    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 isLink(path):
            # link
            self.write("<td><table><tr>")
            try:
                self.writeLinkRow(path)
            except Exception, e:
                self.write("<td colspan=0><font color=red>Error reading link " + escape(path) + ": " + escape(str(e)) + "</font></td>")
            self.write("</tr></table></td>")
        elif os.path.isdir(path):
            # directory
            try:
                self.write("<td>")
                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("<table>")
                    for f in d:
                        self.write("<tr>")
                        self.write("<th>" + escape(f) + "</th>")
                        try:
                            self.writeProcessSection(level + 1, path, f)
                        except Exception, e:
                            self.write("<td><font color=red>Error reading " + escape(path) + ": " + escape(str(e)) + "</ font></td>")
                        self.write("</tr>")
                    self.write("</table>")
            except Exception, e:
                self.write("<font color=red>Error reading directory " + escape(path) + ": " + escape(str(e)) + "</font>")
            self.write("</td>")
        elif os.path.isfile(path):
            # file
            try:
                self.write("<td>")
                data = file(path).read(MAX_DATA)
                while len(data) > 1 and data[len(data)-1] == "\n":
                    data = data[:len(data)-1]
                if len(data) == MAX_DATA:
                    self.write("<font color=grey><i>" + str(len(data)) + ' or more bytes, see ' + fileRef(path) + "</i></font>")
                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)
                    # parsing man page too fragile
                    elif item == "environ":
                        self.writeEnviron(data)
                    elif item == "cmdline":
                        self.write(formatCmdLine(data))
                    else:
                        self.write("<pre>")
                        self.writeEscape(data, preformatted=True)
                        self.write("</pre>")
            except IOError, e:
		if e.errno == 22:
		    # Invalid Argument
	            # read() returns EINVAL, read() man page says "unsuitable for reading"
		    self.write("<font color=grey><i>" + escape(path) + " is unsuitable for reading: " + escape(str(e)) + "</i></font>")
                elif e.errno == 5 and re.match("/proc/[0-9]+/mem", path) != None:
		    # I/O Error on /proc/[pid]/mem
	            # process memory does not start at 0
		    self.write("<font color=grey><i>" + escape(path) + " (the process's memory) can not be read from offset 0: " + escape(str(e)) + "</i></font>")
                else:
                    self.write("<font color=red>Error reading file " + escape(path) + ": " + escape(str(e)) + "</font>")
            except Exception, e:
                self.write("<font color=red>Error reading file " + escape(path) + ": " + escape(str(e)) + "</font>")
            self.write("</td>")
        else:
            self.write("<td>")
            self.write("Not file or directory")
            self.write("</td>")
            

    def writeFds(self, path):
         fds = []
         for d in os.listdir(path):
             fds.append(int(d))
         fds.sort()
         self.write("<table>")
         for fd in fds:
              self.write("<tr><th>" + str(fd) + "</th>")
              self.writeLinkRow(path + "/" + str(fd))
              self.write("</tr>")
         self.write("</table>")

    def writeTasks(self, path):
         pids = []
         for d in os.listdir(path):
             pids.append(int(d))
         pids.sort()
         self.write("<table>")
         for pid in pids:
             self.write("<tr>")
	     self.writeProcessRow(pid)
             self.write("</tr>")
         self.write("</table>")


    def writeStat(self, data):
         # PID (the name) FIELD2 FIELD3 ...
         
         global debug
         
         sec = getManSection("/proc/[number]/stat")
         if sec == "":
             sec = getManSection("/proc/[pid]/stat")
         if sec == "":
             self.write("<font color=red>No man section /proc/[number|pid]/stat</font><br>")
             self.write("<pre>")
             self.writeEscape(data, preformatted=True)
             self.write("</pre>")
             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:
	    self.write("<font color=red>Error parsing /proc/[pid]/stat, no (process name)</font><br>")
	    statTok = data.split()
	 else:
	    statTok = data[0:open].split() + [data[open + 2: close]] + data[close + 2:].split()
         if debug: print " statTok", statTok
         
         i = 0
         self.write("<table><tr>")
         manText = ""
         for line in sec.split("\n"):
             if debug: print " line", '"' + line + '"'
             line = re.sub("[ \t]+", " ", line)
             fieldName = None
             fieldNameIndex = None
             fline = re.sub("[(][^)]*[)]", "", line)
             mantok = fline.strip().split(" ")
             if debug: print " mantok", mantok
             for n, tok in enumerate(mantok):
                if tok.startswith("%") and n == len(mantok) - 1:
                    assert n != 0
                    fieldNameIndex = n - 1
                    fieldName = mantok[fieldNameIndex].lower()
                    break

             if fieldName == None:
                 line = line.strip()
                 if line == "":
                     continue
                 if len(manText) != 0:
                     manText = manText + "\n"
                 manText = manText + line
                 continue
             if i != 0:
                 self.write("<td>")
                 # <pre> 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("</td></tr><tr>")
             manText = line
             # format line
             self.write("<th>" + escape(fieldName) + "</th>")
             if i < len(statTok):
                 self.write("<td><table><tr><td>")
                 self.writeProcessAttribute(fieldName, statTok[i])
                 self.write("</td></tr></table></td>")
             else:
                 self.write("<td><font color=red>no value</font></td>")
             i = i + 1
         self.write("<td>")
	 self.writeManSectionText(manText, preformat=False)
         self.write("</td></tr>")
         for extra in statTok[i:]:
              self.write("<tr><td><font color=red>extra value</font></td><td>" + escape(statTok[i]) + "</td></tr>")
              i = i + 1
         self.write("</table>")                 

    def writeBasicManTable(self, item, data):
        sec = getManSection("/proc/[number]/" + item)
	if sec == "":
            sec = getManSection("/proc/[pid]/" + item)
	index = sec.find(":")
        if sec == "" or index == -1:
             self.write("<font color='#FF7000'>Can not extract man page section /proc/[pid]/" + escape(item) + "</font><br>")
             self.write("<pre>")
             self.writeEscape(data, preformatted=True)
             self.write("</pre>")
             return
	# man section:

	# intro bla:
        # value description
        # value description
        # @fragile
        
	dataTok = data.split()
	headerTok = sec[index:].split("\n")[1:]
	n = len(dataTok)
	if len(headerTok) > n:
	    n = len(headerTok)
	self.write("<table>")
	for i in range(n):
            self.write("<tr>")
	    if i < len(headerTok):
		header = headerTok[i].strip().split(" ", 1)
		self.write("<th>" + escape(header[0]) + "</th>")
	    else:
		self.write("<th><font color=red>extra value</font></th>")
	    if i < len(dataTok):
		self.write("<td>" + escape(dataTok[i]) + "</td>")
	    else:
		self.write("<td></td>")
	    if i < len(headerTok) and len(header) > 1:
	    	self.write("<td>" + color(GREEN,  escape(header[1].strip()) ) + "</td>")
            self.write("</tr>")	    
	self.write("</table>")

    def writeHeaderlessTable(self, item, data):

                        self.write("<table>")
			sec = getManSection("/proc/[number]/" + item)
			if sec == "":
                            sec = getManSection("/proc/[pid]/" + item)
	                index = sec.find(":")
			if sec != "" and index != -1:
			    for line in sec[index:].split("\n")[1:]:
				if line.strip() != "":
				    self.write("<tr>")
				    for h in line.split():
					self.write("<th>")
					self.writeEscape(h)
					self.write("</th>")
				    self.write("</tr>")
				    break

                        for line in data.split("\n"):
                            pos = line.find(":")
                            self.write("<tr>")
			    for field in line.split():
				if len(field) > 0 and field[0] == "/":
				    self.writeFileRow(field)
				else:
				    self.write("<td>" + escape(field) + "</td>")
                            self.write("</tr>")
                        self.write("</table>")

    def writeHorrTable(self, data):
                        self.write("<table>")
                        for line in data.split("\n"):
                            pos = line.find(":")
                            self.write("<tr>")
                            if pos == -1:
                                 self.write("<td>" + escape(line) + "</td>")
                            else:
                                 key = line[0:pos].strip()
                                 self.write("<th>" + escape(key) + "</th>")
                                 value = line[pos+1:].strip()
                                 self.write("<td>")
                                 self.writeProcessAttribute(key, value)
                                 self.write("</td>")
                            self.write("</tr>")
                        self.write("</table>")

    def writeProcessAttribute(self, key, value):
				self.write("<td>")
                                if key.lower() == "pid":
                                     self.write("<b>" + escape(value) + "</b>")
                                elif key.lower() in ["tgid", "ppid"]:
                                     if int(value) != 0: # ppid of init is 0
					 self.write("<table><tr>")
                                         self.writeProcessRow(int(value))
					 self.write("</tr></table>")
                                     else:
                                         self.write("<i>none</i>")
                                elif key.lower() == "uid":
                                     for uid in value.split():
				         self.write(formatUser(int(uid)))
                                else:
                                     self.write(escape(value, preformatted=True))
				self.write("</td>")

    def writeEnviron(self, data):
              env = data.split("\0")
              self.write("<table>")
	      for e in env:
		if e == "":
                     continue
                kv = e.split("=", 1)  
                self.write("<tr>")
                self.write("<td>")
                self.writeEscape(kv[0])
                self.write("</td>")
                self.write("<td>")
                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("</td></tr><tr><td></td><td>")
                             self.writeEscape(t)
                    else:
                         self.writeEscape(kv[1], wrap=50)
                else:
                    self.write("<font color=red>no = in environment variable: '" + escape(e) + "'</font>")
                self.write("</td>")
                self.write("</tr>")
              self.write("</table>")
       
    def writeManSection(self, procPath):
        """ @param procPath /proc path to document
            return True if found
        """
        text = getManSection(procPath)
        if text == "":
            return False
        self.writeManSectionText(text)
        return True
        
    def writeManSectionText(self, sec, preformat=True):
        """ @param str sec the text"""
        if sec == "":
            return
        self.write("<font color=#008000>") # green
        if preformat:
            self.write("<pre>")
        else:
            self.write("<tt>")
        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, utf8=True)
            if not preformat:
                self.write("<br/>")
        if preformat:
            self.write("</pre>")
        else:
            self.write("</tt>")
        self.write("</font>")

    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("<table>")

	       self.write("<tr>")
               for col in headers:
                   self.write("<th>" + escape(col) + "</th>")
	       self.write("</tr>")

               for row in data:
                   self.write("<tr>")
                   i = 0
                   for col in row:
                       self.write("<td>")
                       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("</td>")
                       i = i + 1                
                   self.write("</tr>")
               self.write("</table>")
            else:
                self.writeFileContents(procPath)
        except Exception, e:
            self.write("<font color=red>Error reading file " + escape(procPath) + ": " + escape(str(e)) + "</font>")

        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 == ['']:
                hrefParts = []
            else:
	        hrefParts = []
		for part in tok:
		    hrefParts.append(urlEscape(part))
        else:
            hrefParts = []
        self.write('<a href="/proc/' + "/".join(hrefParts) + '">up</a><br/>')
        try:
            d = os.listdir(procPath)
            d.sort()
            for f in d:
                c = ""
                if os.path.isdir(procPath + "/" + f):
                    c = "/"
		hrefParts = []
		for part in path.split("/"):
		    hrefParts.append(urlEscape(part))
                self.write('<a href="/proc/' + "/".join(hrefParts) + "/" + urlEscape(f) + '">' + escape(f) + c + "</a><br/>")
        except Exception, e:
            self.write("<font color=red>Error reading directory " + escape(procPath) + ": " + escape(str(e)) + "</font>")
        self.writeManSection(procPath)

        self.footer()

    def get_source(self):
        # serve up the source code        
        self.write("HTTP/1.1 200 OK")
        # Content-Type and Disposition so downloads rather than show contents
        self.write("Content-Type: application/octet-stream; charset=UTF-8")
        self.write("Content-Disposition: attachment; filename=\"proc-surfer\"")
        self.write("")
        self.wfile.write(file(sys.argv[0], "r").read())

    def get_top(self, sortBy):
        # top
        self.header("top")	

        self.write('<p><a href=/>up</a></p>')

        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("<p><font color=red>Error running top, exit status " + str(r & 0xFF) + ", signal " + str((r >> 8) & 0x7F) + ", core " + str((r & 0x80)==0x80)  + "</font></p>")
	#    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("<br/>")
		if line == "":
		    header  = False		    
	    elif body:
		    if line != "":
			data.append(line.split())
	    else:
		    tok = line.split()
		    self.write('<table><tr>')
		    i = 0
		    for col in tok:
			self.write('<th>')
			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('<i>')
			self.write('<a href="/top/' +  urlEscape(key) + '">' + escape(col) + "</a>")
			if col == sortBy:
			    self.write('</i>')
			self.write('</th>')
			indexes[col] = i
			i += 1
		    self.write("</tr>")
		    body = True
	if not body:
	    self.write('<table>')

	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("<tr>")
	    for col in row:
	    	    self.write("<td>")
		    if i == pidIndex:
			self.write('<a href="/proc/' + urlEscape(col) + '">')
		    self.writeEscape(col)
		    if i == pidIndex:
			self.write("</a>")
	    	    self.write("</td>")
		    i = i + 1
	    self.write("</tr>")
	self.write('</table>')

    def get_sockets(self):
	self.header("sockets")	
        self.write('<p><a href=/>up</a></p>')
        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("<table>")
	inodes = socket.keys()
	inodes.sort()
        for inode in inodes:
             (proto, values) = socket[inode]
             self.write("<tr>")
             self.write('<td>' + socketRef(inode) + "</td>")
             self.write("<td>" + protocolRef(proto) + "</td>")
             self.write("<td><table><tr>")
             for (header, value) in values:
                 if (header == "remote_address" or header == "rem_address") and not isAnyIpConnection(value):
                     self.write("&#x2194;") # <->
                 self.writeSocketField(header, value)
             self.write("</tr></table></td>")
             self.write("</tr>")
        self.write("</table>")

	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('<p><a href=/socket>up</a></p>')
        self.write("<table>")
        self.write("<tr><td><b>protocol</b></td><td>" + protocolRef(proto) + "</td></tr>")
        for (header, data) in row:
            self.write("<tr><td><b>")
            if header != None:
                self.writeEscape(header)
            self.write("</b></td><td>")
            self.writeSocketField(header, data)
            self.write("</td></tr>")
        self.write("</table>")

        self.heading("processes")
        pids, errors = fuser("socket:[" + str(inode) + "]")
        if len(pids) == 0:
            self.write("<i>none</i>")
        else:
            pids.sort()
            self.write("<table>")
	    for pid in pids:
                self.write("<tr>")
                self.writeProcessRow(pid)
                self.write("</tr>")
            self.write("</table>")

        self.footer()

    def get_pipes(self):
        self.header("pipes")
        self.write('<p><a href=/>up</a></p>')
        for pid in getAllPids():
             self.getProcessInformation(pid)
        self.write("<table>")
	inodes = self.cache_pipes.keys()
	inodes.sort()
        for inode in inodes:
             self.write("<tr>")
             self.writePipeRowPidCmdline(inode, self.cache_pipes[inode])
             self.write("</tr>");
        self.write("</table>")

	self.footer()

    def get_pipe(self, inode):
        self.header("pipe " + str(inode))
        
        self.write('<p><a href=/pipe>up</a></p>')
	self.heading("processes")
        pids, errors = fuser("pipe:[" + str(inode) + "]")
        if len(pids) == 0:
            self.write("<i>none</i>")
        else:
            self.write("<table>")
            pids.sort()
            for pid in pids:
                self.write("<tr>")
	        self.writeProcessRow(pid)
                self.write("</tr>")
            self.write("</table>")       
        self.footer()

    def get_files(self, message=""):
	    self.header("files")

	    if message != "":
	    	self.write("<p>" + message + "</p>")

            self.write('<p><a href=/>up</a>')
            self.link("open files")

            self.link("root directory")
            self.write('<a href="/du">disk usage</a></p>')

            self.heading("open files")
	    self.write("<table>")
	    for pid in getAllPids():
		self.getProcessInformation(pid)
		try:
		    data = file("/proc/" + str(pid) + "/maps").read(MAX_DATA)
		    if len(data) == MAX_DATA:
		        raise Exception(str(MAX_DATA) + " or more bytes")
		    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("<tr>")
		self.writeFileRow(f)
		self.write("</tr>");
	    self.write("</table>")

            self.heading("root directory")
            self.writeDirectoryContents("/")

	    self.footer()

    def dirSize(self, path):
        # str path of directory
	# path is osPath style, e.g. c:\Program Files, not /c:/Program Files
        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 = os.path.join(path, f)
            if isLink(fpath):
                # isfile or isdir
                pass
            elif os.path.isdir(fpath):
                size += self.dirSize(fpath)
            elif os.path.isfile(fpath):
        	try:
            	    size += fileSize(fpath)
                except Exception, e:
                    pass
            else:
                # non file
                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(osPath(dirpath))
                except Exception, e:
                    direc = []

	        direc.sort()
		for f in direc:
		    fpath = os.path.join(osPath(dirpath), f)
                    if os.path.isdir(fpath):
                        if not started:
                            self.write("|")
                            started = True
			prefix = self.prefix
			if not prefix.endswith("/"):
			    prefix += "/"
                        self.write('<a href="/du' + escape(prefix + f) + "\">" + escape(f) + "</a> ")
		self.flush()
        du_cache[path] = size
        return size + fileSize(path)

    def get_du(self, path, message=""):
        """ @param str path """
        
	path = fixPath(path)
	       
	self.pathHeader("disk usage", "du", path)

	if message != "":
	    self.write("<p>" + message + "</p>")

        self.write("<p>")
        
        if path == "/":
            self.prefix = ""
        else:
            self.prefix = path

        if path != "/":
            tok = path.rstrip("/").split("/")[:-1]
            if tok == ['']:
                href = "/du"
            else:
                href = "/du" + urlEscapePath(string.join(tok, "/"))
        else:
            href = "/"
        self.write('<a href="' + href + '">up</a>')
        
        self.write('<a href="/file' + urlEscapePath(path) + '#contents">directory</a>')
	if os.name == "nt":
	    if path != "/":
                self.write('<a href="/guishell' + urlEscapePath(path) + "\">Explorer</a>")
                self.write('<a href="/terminal' + urlEscapePath(path) + "\">Command Prompt</a>")
	else:
            self.write('<a href="/guishell' + urlEscapePath(path) + "\">nautilus</a>")
            self.write('<a href="/terminal' + urlEscapePath(path) + "\">terminal</a>")
        
        self.showDirTime = time.time() + 1.5
	            
	
	if os.name == "nt" and path == "/":
	    # list of possible drives
            self.write(" | ")
	    try:
	        home = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]
	        self.write('<a href="/du/' + urlEscapePath(fixPath(home)) + '">' + escape(os.path.basename(home)) + '</a> ')
 	    except Exception, e:
	        pass
	    for driveNum in range(ord('A'), ord('Z') + 1):
	        drive = chr(driveNum)	     
	        self.write('<a href="/du/' + drive + ':/">' + drive + ':\\</a> ')
            self.footer()
	    return
	     
	filesSize = 0
	numFiles = 0
        sizes = []
        # list of (str name, int size in bytes)
        maxSize = 0
        total = fileSize(osPath(path))
        try:          
            direc = os.listdir(osPath(path))
        except Exception, e:
            self.write("<p><font color=red>Error directory " + escape(path) + ": " + escape(str(e)) + "</font></p>")
            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 = os.path.join(osPath(path), f)             
             if os.path.isdir(fpath):
                 # Junctions shown as 0 size
                 if isLink(fpath):
                     size = 0
                 else:
                     size = self.dirSize(fpath)
		 fstr = f
		 if type(fstr) == type(u""):
		     fstr = f.encode("utf-8")
                 sizes.append((fstr, size))
                 if size > maxSize:
                     maxSize = size
                 total += size
             elif os.path.isfile(fpath):
                 if not isLink(fpath):
                    filesSize += fileSize(fpath)
                 numFiles += 1
             else:
                # non file
                pass                
	filesName = str(numFiles) + " file" + pluralS(numFiles)
        if numFiles != 0:
	    filesNameStr = filesName
	    if type(filesNameStr) == type(u""):
		filesNameStr = filesNameStr.encode("utf-8")
            sizes.append((filesName, filesSize))
        if filesSize > maxSize:
            maxSize = filesSize
        total += filesSize

        #self.write('<body onload=document.write("&lt;b&gt;hel&lt;/b&gt;lo")')
        #self.write('<body onload="document.clear()">')
        #self.write("""<body onload='document.open(); document.write("<P>The only content</P>."); document.close();'>""")
        #self.write("world')>")
        #self.write("</body>")

        sizes.sort(compareSize)
        sizes.reverse()

        self.write("</p>")
        self.write("<p><table rules=groups>")
        self.write("<tbody>")
        for (name, size) in sizes:
             self.write("<tr>")
             fpath = path
             if not path.endswith("/") and not name.startswith("/"):
                 fpath += "/" 
             fpath += name
		# URL path
		# Windows: /c:/Program Files
                
             # delete button
             if name == filesName:
                 # "10 files"
                 self.write("<td></td>")
             else:
	         ospath = osPath(fpath)
		 if type(ospath) == type(u""):
		     ospath = ospath.encode("utf-8")
	         self.write('<td><form action="/du' + urlEscapePath(fpath) + '" method=post onsubmit="return confirm(\'Are you sure you want to remove ' + escape(ospath.replace("\\", "\\\\")) + '?\')">')
	         self.write('<input type=hidden name=action value=rm>')
	         self.write('<input type=hidden name=context value=du>')
	         self.write('<input type=submit value=delete>')
	         self.write('</form></td>')

             self.write("<td style=\"text-align: right\">" + ("" if isLink(osPath(fpath)) else formatSize(size)) + "</td><td>")
             if maxSize != 0:
                 self.write("<table width=200><tr>")
                 self.write("<td><table width=" + str(100 * size / maxSize) + "% bgcolor=green><tr><td>&nbsp;</td></tr></table></td>")
                 self.write("</tr></table>")
             if name == filesName:
                 self.write('</td><td><a href="/file' + urlEscapePath(self.prefix) + '#contents"><i>' + escape(name) + "</i></a></td>")
             else:
                 link = ""
                 if isLink(osPath(fpath)):
                     link = " (link)"
                 url = escape(self.prefix)
                 if not url.endswith("/"):
                     url += "/"
                 url += name
                 self.write('</td><td><a href="/du' + urlEscapePath(url) + "\">" + escape(name) + "/</a>" + link + "</td>")
             self.write("</tr>")
        self.write("</tbody>")
        self.write("<tbody>")
        self.write("<tr><td></td><td style=\"text-align: right\"><b>" + formatSize(total) + "</b></td><td></td><td><b>total</b></td></tr>")
        self.write("</tbody>")
	self.write("</table></p>")

	self.write("<p>File system:</p>")
	if os.name == "nt":
	    dpath = path
	    if dpath == "":
		dpath = "\\"
	    dirLines = subprocess.Popen(["dir", "/-C", osPath(dpath)], shell=True, stdout=subprocess.PIPE).stdout.readlines()
	    # /-C don't put commas in numbers
	    #08-Dec-2003  11:22    <DIR>          WINDOWS
            #  2 File(s)           1392 bytes
            #  13 Dir(s)     10679640064 bytes free            
	    fsfree = long(dirLines[-1].split()[2])

	    self.write("<p>")
	    self.write("<b>" + formatSize(fsfree) + " free</b>")
	    self.write("</p>")
	else:
	    r = os.statvfs(path)
	    fsfree = r.f_frsize * r.f_bfree
	    fstotal = r.f_frsize * r.f_blocks
	    fsused = fstotal - fsfree

	    self.write("<p><table rules=groups>")
	    self.write("<thead>")
	    self.write("<tr><td style=\"text-align: right\">" + formatSize(fsfree) + "</td><td></td><td>free</td></tr>")
	    self.write("<tr><td style=\"text-align: right\">" + formatSize(fsused) + "</td><td></td><td>used</td></tr></thead>")
	    self.write("</thead>")
	    self.write("<tbody>")
	    self.write("<tr><td style=\"text-align: right\"><b>" + formatSize(fstotal) + "</b></td><td></td><td><b>total</b></td></tr>")
	    self.write("</tbody>")
	    self.write("</table></p>")

	    self.write("<p>" + formatSize(r.f_frsize) + " block size<br/>")
	    if r.f_frsize != r.f_bsize:
		self.write("<p>" + formatSize(r.f_bsize) + " preferred block size")
	    self.write("</p>")

        self.write("<p><font color=" + GREEN + ">")
	if os.name != "nt":
	    self.write("All sizes are rounded up to the disk block size to show actual disk space used.<br>The total includes space used by the directory itself.<br/>")
	self.write("For speed directory sizes are saved, hit the Reload button on your browser to refresh these.</font></p>")
        self.footer()

    def get_file(self, path, message=""):
        """ @param str path 
            @param html str message
        """        
	
	path = fixPath(path)
	fpath = osPath(path)

	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(MAX_DATA)
		if len(data) == MAX_DATA:
		    raise Exception(str(MAX_DATA) + " bytes or more")
		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(fpath):
                self.pathHeader("open directory", "file", path)
            else:
                self.pathHeader("open file", "file", path)
	else:
            if os.path.isdir(fpath):
                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("<p>" + message + "</p>")

        if path != "/":
            tok = path.rstrip("/").split("/")[:-1]
            if tok == ['']:
                href = "#root%20directory"
            else:
                href = string.join(tok, "/")
            self.write('<a href="/file' + escape(href) + "\">up</a>")
        else:
            self.write('<a href=/">up</a>')

	if path == "/" and os.name == "nt":
	    try:
	        home = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]
	        self.write('<a href="/du/' + escape(fixPath(home)) + '">' + escape(os.path.basename(home)) + '</a> ')
            except Exception, e:
	        pass
            for driveNum in range(ord('A'), ord('Z') + 1):
	        drive = chr(driveNum)	     
	        self.write('<a href="/file/' + drive + ':/">' + drive + ':\\</a> ')
            self.footer()
            return

        self.link("details")
        self.link("open")
        if not os.path.isdir(fpath):
            self.link("memory mapped")
        self.link("contents")
        if os.path.isdir(fpath):
            self.write('<a href="/du' + urlEscapePath(path) + '">disk usage</a>')
	    if os.name == "nt":
                self.write('<a href="/guishell' + urlEscapePath(path) + "\">Explorer</a>")
                self.write('<a href="/terminal' + urlEscapePath(path) + "\">Command Prompt</a>")
	    else:
                self.write('<a href="/guishell' + urlEscapePath(path) + "\">nautilus</a>")
                self.write('<a href="/terminal' + urlEscapePath(path) + "\">terminal</a>")

        self.heading("details")        
        try:
            r = os.lstat(fpath)
            self.write("<table>")
            #drwxr-xr-x  2 overtoun overtoun   4096 2005-12-16 21:16 index_files
            self.write("<tr><th>name</th><td>" + escape(fpath, utf8=True) + "</td></tr>")
	    if os.name != "nt":
                self.write("<tr><th>type</th><td>" + fileType(fpath) + "</td></tr>")
            self.write("<tr><th>mode</th><td>" + formatFilePermissions(r.st_mode) + "</td></tr>")
            self.write("<tr><th>num links</th><td>" + str(r.st_nlink) + "</td></tr>")
            self.write("<tr><th>inode</th><td>" + str(r.st_ino) + "</td></tr>")
            self.write("<tr><th>device</th><td>" + str(r.st_dev) + "</td></tr>")
            self.write("<tr><th>user</th><td>" + formatUser(r.st_uid) + "</td></tr>")
            if os.name != "nt":
                self.write("<tr><th>group</th><td>" + formatGroup(r.st_gid) + "</td></tr>")
                self.write("<tr><th>size</th><td>" + formatCommaSize(r.st_size) + "</td></tr>")

	    if os.name != "nt":
                # Linux specific
                self.write("<tr><th>blocks</th><td>" + formatCommaSize(r.st_blocks) + "</td></tr>")
                self.write("<tr><th>blocksize</th><td>" + formatCommaSize(r.st_blksize) + "</td></tr>")
                self.write("<tr><th>dev type</th><td>" + str(r.st_rdev) + "</td></tr>")
                self.write("<tr><th>disk space</th><td>" + formatCommaSize(r.st_blocks *r.st_blksize) + "</td></tr>")

            self.write("<tr><th>status change</th><td>" + formatFullTime(r.st_ctime) + "</td></tr>")
            self.write("<tr><th>modified</th><td>" + formatFullTime(r.st_mtime) + "</td></tr>")
            self.write("<tr><th>accessed</th><td>" + formatFullTime(r.st_atime) + "</td></tr>")
            self.write("</table></p>")
        except Exception, e:
            self.write("<p><font color=red>Error stating file " + escape(fpath) + ": " + escape(str(e)) + "</font></p>")

        self.heading("open")
        if len(fdPids) == 0:
            self.write("<i>none</i>")
        else:
            fdPids.sort()
            self.write("<table>")       
            for pid in fdPids:
                self.write("<tr>")
	        self.writeProcessRow(pid)
                self.write("</tr>")
            self.write("</table>")

	if len(mmapPids) == 0:
            if not os.path.isdir(path):
	        self.heading("memory mapped")	        
                self.write("<i>none</i>")
        else:
            self.heading("memory mapped")	
            mmapPids.sort()
	    self.write("<table>")       
            for pid in mmapPids:
                self.write("<tr>")
	        self.writeProcessRow(pid)
                self.write("</tr>")
            self.write("</table>")

        self.heading("contents")
        if os.path.isdir(fpath):
	    self.writeDirectoryContents(path)
        else:
	    self.writeFileContents(path)

	self.footer()

    def writeFileContents(self, path):
            try: 
	        fpath = osPath(path)
                if stat.S_ISFIFO(os.stat(fpath).st_mode):
                    self.write("<p><font color=grey><i>fifo (named pipe)</i></font></p>")
                    return
                if stat.S_ISSOCK(os.stat(fpath).st_mode):
                    self.write("<p><font color=grey><i>socket</i></font></p>")
                    return
		if os.name == "nt":
  		    mode = 0
		else:
 		    mode = os.O_NONBLOCK
		fd = os.open(fpath, mode)
		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(fpath).st_size
                        self.write("<p><font color=grey><i>" + str(size) + " bytes</i></font></p>")
		    except Exception, e:
			raise Exception("Can not determine size of file '" + path + ": " + str(e))
                else:
                    self.write("<pre>")
                    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("</pre>")
            except Exception, e:
                self.write("<p><font color=red>Error reading file " + escape(path) + ": " + escape(str(e)) + "</font></p>")

    def writeDirectoryContents(self, path):
        try:
            try:
                contents = os.listdir(osPath(path))
            except Exception, e:
                self.write("<p><font color=red>Error reading directory " + escape(path) + ": " + escape(str(e)) + "</font></p>")
                return

            contents.sort()
            if path == "/":
                prefix = ""
            else:
                prefix = path
            if len(contents) == 0:
                self.write("<i>none</i>")
            else:
	        self.write('<table rules=groups><tbody>')
		# cellspacing=10 is above and below as well as left and right
                SPACING ="&nbsp;" * 2
	        self.write('<tr>')
                self.write('<th></th>')
                self.write('<th>mode</th>')
                self.write("<th class=right>" + SPACING + "size</th>")
		if os.name != "nt":
                    self.write("<th class=right>" + SPACING + "user</th>")
                    self.write("<th class=right>" + SPACING + "group</th>")
                self.write("<th>" + SPACING + "</th>")
                self.write("<th colspan=3>last modified</th>")
                self.write("<th>" + SPACING + "name</th>")
	        self.write('</tr>')
                totalSize = 0
                totalSizeComlete = True
                for f in contents:
                    self.write("<tr>")
                    fpath = os.path.join(osPath(path), f)
		    linkPath = prefix + "/" + f
                    link = "" # HTML
                    try:
                        if isLink(fpath):
                            dest = os.readlink(fpath)
                            destFull = os.path.join(os.path.dirname(fpath), dest)
                            if not os.access(destFull, os.F_OK):
                                # bad link
			        link = escape(" -> ") + "<font color=red>" + escape(dest) + "</font>"
                                r = os.lstat(fpath)
                            else:
                                link = escape(" -> ") + fileRef(destFull, name=dest)
                            r = os.lstat(fpath)
                        else:
                            r = os.stat(fpath)
	    		self.write('<td><form action="/file' + urlEscapePath(linkPath) + '" method=post onsubmit="return confirm(\'Are you sure you want to remove ' + escape(fpath, utf8=True) + '?\')">')
	    		self.write('<input type=hidden name=action value=rm>')
	    		self.write('<input type=submit value=delete>')
	    		self.write('</form></td>')
                        # drwx------  2 root     root       4096 Dec 17 02:49 orbit-root
                        # miss out links as I don't care
                        self.write("<td>" + formatFilePermissions(r.st_mode) + "</td>")
                        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 = "<i>" + formatCommaSize(r.st_size) + "</i>"
                                totalSize += r.st_size
                                totalSizeComlete = False
                        else:
                            sizeText = formatCommaSize(r.st_size)
                            totalSize += r.st_size
                        self.write("<td class=right>" + SPACING + sizeText + "</td>")
			if os.name != "nt":
                            self.write("<td class=right>" + SPACING + formatUser(r.st_uid) + "</td>")
                            self.write("<td class=right>" + SPACING + formatGroup(r.st_gid) + "</td>")
                        self.write("<td>" + SPACING + "</td>" + formatTime(r.st_mtime)) # 1 + 3 columns
                    except Exception, e:
                        self.write("<td colspan=8><font color=red>Error stating file " + escape(fpath) + ": " + escape(str(e)) + "</font></td>")
                    if os.path.isdir(fpath):
			self.write('<td>' + SPACING + '<a href="/file' + urlEscapePath(linkPath) + '#contents"><b>' + escape(f, utf8=True) + "/</b></a>" + link + "</td>")
		    else:
	                self.write('<td>' + SPACING + '<a href="/file' + urlEscapePath(linkPath) + '">' + escape(f, utf8=True) + "</a>" + link + "</td>")
	                
                    self.write("</tr>")

                self.write("</tbody><tbody>") 
                if totalSizeComlete:
                    self.write("<tr><td colspan=2></td><td class=right>" + SPACING + formatCommaSize(totalSize) + "<td colspan=6></td><td><b>" + SPACING + "total</b></td></tr>") 
                else:
		    self.write("<tr><td colspan=2></td><td class=right><i>" + SPACING + formatCommaSize(totalSize) + "</i><td colspan=6></td><td><b><i>" + SPACING + "total</i></b></td></tr>") 
                self.write("</tbody></table>") 

        except Exception, e:
            self.write("<p><font color=red>Error reading directory " + escape(path) + ": " + escape(str(e)) + "</font></p>")

    def get_users(self):

        self.header("users")
  
        self.write("<p><table>")

        n = 0
        for h in PASSWD_FILE_HEADINGS:
            if n in [2, 3]:
                # uid gid
	        self.write("<th class=right>" + escape(h) + "</th>") 
            else:
                self.write("<th>" + escape(h) + "</th>")
            n += 1

        try:
            data = file("/etc/passwd").read()
            for line in data.split("\n"):
                self.write("<tr>")
		tok = line.split(":") # \: escape?
                n = 0
                for col in tok:
                    if n == 0 and len(tok) > 2 and tok[2].isdigit():
			self.write("<td>" + userRef(int(tok[2]), col) + "</td>")
                    elif n in [2, 3]:
                        # uid, gid
                        self.write("<td class=right>" + escape(col) + "</td>")
                    elif n in [5, 6]:
                        # home and shell
                        self.write("<td>" + fileRef(col) + "</td>")
                    else:
                        self.write("<td>" + escape(col) + "</td>")
                    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("</tr>")
            self.write("</table>")

        except Exception, e:
            self.write("</table><p><font color=red>error reading /etc/passwd: "  + escape(str(e)) + "</font></p>")

        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("<a href=/>up</a>")
        self.link("hostname")
        self.write("<br>")
	if len(aliaslist) > 0:
    	    self.link("aliases")
	    self.write("<br>")

    	self.link("open sockets")
 	self.write("<br>")
        self.link("route")
	self.write("<br>")
        
        self.heading("hostname")
        if hostname == None:
            self.write("<p><font color=red>Error getting hostname for ip '" + escape(ip) + "': " + escape(error) + "</font></p>")
        else:
            self.write(domainRef(hostname))

        if len(aliaslist) > 0:
            self.heading("aliases")
 	    self.write("<ul>")
            for a in aliaslist:
                self.write("<li>" + domainRef(a) + "</li>")
 	    self.write("</ul>")

	self.heading("open sockets")
        # !@#
        # @fragile depends on names of socket protocols
        self.write("<table>")
        for p in PROTOCOLS:
            try:
                row = findRowByFun("/proc/net/" + p, isSocketAddressHeader, ip, extractIpFromKernalAddress)
                #self.writeEscape(str(row) + "<br>")
                inode = extractListValue(row, "inode")
                #self.writeEscape(str(inode) + "<br>")
                if inode != None:
                     self.write("<tr>")
		     self.writeSocketRow(inode)
                     self.write("</tr>")
                break
            except Exception, e:
                #self.write(escape(str(e))  + "<br>")
                pass # not in this protocol            
        self.write("</table>")
	# 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("<table>")
            while True:
                line =  fd.readline()
                if line == "":
                    break            
                line = line[:-1] # strip '\n'
	        self.write("<tr>")
                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("<th>" + str(num) + "</th>")
                    if line == "no reply":
                        self.write("<td><i>no reply</i></td>")
                    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("<td>" + ipRef(t[1:-1], False) + "</td>") # leave as IP, first column shows hostname
                            else:
                                 self.write("<td>" + escape(t) + "</td>")
                else:
                    # no number
                    self.write("<td>&nbsp;</td><td>" + escape(line) + "</td>")
	        self.write("</tr>")
                self.flush();
        finally:
            signal.signal(signal.SIGCHLD, sigChildHandler)

        self.write("</table>")

	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("<p><font color=red>Error running tracepath '" + ip + "', exit status " + str(r & 0xFF) + ", signal " + str((r >> 8) & 0x7F) + ", core " + str((r & 0x80)==0x80)  + "</font></p>")

	self.footer()

    def get_domain(self, domain):
        self.header("domain name " + domain)

	self.write("<a href=/>up</a>")
	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) + "<br>")
                    ips.append(ip)
        except Exception, e:
            self.write("<p><font color=red>Error getting IP addreses for domain name '" + escape(domain) + "': " + escape(str(e)) + "</font></p>")

	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("<table>")
 	    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("<tr><th>" + escape(h) + "</th>")
                    	if n in [5, 6]:
                            # home and shell
                            self.write("<td>" + fileRef(col) + "</td>")
                    	elif n == 2:
                            self.write("<td><b>" + escape(col) + "</b></td>")
                    	else:
                            self.write("<td>" + escape(col) + "</td>")
                        self.write("</tr>")
                        n += 1
                    done = True
                    break
            self.write("</table>")

        except Exception, e:
            self.write("<p><font color=red>error reading /etc/passwd: "  + escape(str(e)) + "</font></p>")

        if not done:
            self.write("<p><font color=red>user " + str(uid) + " not found in /etc/passwd</font></p>")

        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:
                 # @idea: separate into separ trys
		 if getProcessUser(pid) != uid:
                     continue 		 
 	     except Exception, e:
                 self.write("<font color=red>Can not find user of process" + str(pid) + ": " + str(e) + "</font><br>")
             else:
                 userPids.append(pid)
             try:	 
                 ppid = getProcessParent(pid)
             except Exception, e:   
                  self.write("<font color=red>Can not find ppid of process " + str(pid) + ": " + str(e) + "</font><br>")
             else:
                 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]
                 
        if len(noParents) == 0 and len(userPids) != 0:
            self.write("<font color=red>All processes have parents!</font><br>")
            noParents = userPids
            self.children = {}
        
	# caches blanked in do_GET

        if len(noParents) == 0:
            self.write("<p><i>none</i></p>")
        else:
            self.write("<table>")
            noParents.sort()           
            for pid in noParents:
                self.writeProcessAndChildren(pid)
            self.write("</table>")

        self.footer()
            
    def writeNotFound(self, message=""):
	if not self.writtenHeader:        
           self.write("HTTP/1.1 404 Not Found")
           self.write("Content-Type: text/html; charset=UTF-8")
           self.write()
	   self.writtenHeader = True
           self.title("proc-surfer", "page not found");
        self.write("<p><font color=red>Page not found: " + escape(self.path) + "</font></p>")
        if message != "":
            self.write("<p>" + message + "</p>")
        self.footer()

    def writeError(self, message):
	if not self.writtenHeader:        
            self.write("HTTP/1.1 500 Error")
            self.write("Content-Type: text/html; charset=UTF-8")
            self.write()
	    self.writtenHeader = True
            self.title("proc-surfer", "error");
        self.write("<p><font color=red>" + escape(message) + "</font></p>")
        self.footer()

    def do_get(self):
        path = urllib.unquote_plus(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 == "guishell":
	     #os.system("nautilus '/" + rest + "'")
	     if os.name == "nt":
	        cwd = os.getcwdu()
		os.chdir(osPath(rest))
		    # so don't pass Unicode on command line
	        os.spawnv(os.P_NOWAIT, "c:\\windows\\explorer.exe", ["explorer", "."])
		os.chdir(cwd)
	     else:
                os.spawnvp(os.P_NOWAIT, "nautilus", ["nautilus", "/" + rest])
        elif page == "terminal":
	     cwd = os.getcwdu()
	     if os.name == "nt":
	        path = osPath(rest)
                os.chdir(path)
		try:
		    comspec = os.environ["COMSPEC"]
		except:
		    compspec = r"C:\WINDOWS\system32\cmd.exe"		
		# spawnv does not create a new window!
		#os.spawnv(os.P_NOWAIT, comspec, [comspec])
	     else:
	        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/<pid>/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_plus(self.path) # convert %20 to " " etc..
            # content=HTML encoded query string
            # action=<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>
                pid = int(path[1:].split("/")[1])
	        self.do_sig_hup(pid)
	    elif action == "kill":
                # path = /proc/<pid>
                pid = int(path[1:].split("/")[1])
	        self.do_sig_kill(pid)
	    elif action == "term":
                # path = /proc/<pid>
                pid = int(path[1:].split("/")[1])
    	        self.do_sig_term(pid)
	    elif action == "nice":
                # path = /proc/<pid>
                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):
        if False:
	    # test basic functinality
            self.write("HTTP/1.1 200 OK")
            self.write("Content-Type: text/plain")
            self.write()
	    self.write("hello")
	    return

	#print self.headers
        path = urllib.unquote_plus(self.path) # convert %20 to " " etc..
        if os.name == "nt" or path == "/du" or path.startswith("/du/"):
	    # Windows can't fork()
	    # 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 anything
	    self.do_safe_get()
	else:
	    self.do_safe_get()
	    return

	    # @idea: concurrent serving, breaks failing to write to socket: [Errno 32] Broken pipe
	    
	    # fork or can only serve one 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

if os.name != "nt":
    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
host = "localhost"
# localhost for security
# browsers can delete files!
page = ""

parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", type=int, default=0, help = "The port for the the web server to listen on, by default a random port is chosen")
parser.add_argument("-R", "--remote", action="store_true", help = """Listen on all IPs, this allows one to browse proc-surfer from a remove machine,
this a security problem as the remote user can delete files and kill processes without loggin in""")
parser.add_argument("-d", "--debug", action="store_true", help = "Turn on debug tracing")
parser.add_argument("page", nargs="?", default = "", help="The proc-surfer page to start browsing at, e.g. /du")
args = parser.parse_args()
port = args.port
page = args.page
debug = args.debug
if args.remote:
    host = ""    

httpd = BaseHTTPServer.HTTPServer((host, port), ProcSurferHttpHandler)
url = "http://localhost"
if httpd.server_port != 80:
   url += ":" + str(httpd.server_port)
if page == "":
    pass
elif page.startswith("/"):
    url += page
else:
    url += "/" + page

child_pid = None
if os.name == "nt":
    os.system("start " + url)
else:
    child_pid = os.fork()
    if child_pid == 0:
        # we are the child process
        browser = "gnome-open"
        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, url)
        # 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

print "This proc-surfer at http://" + (socket.gethostname() if host == "" else host) + (":" + str(httpd.server_port) if httpd.server_port != 80 else "")

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
   sys.stdout.write("sigAlarmHanler " + str(sig) + "\n")
   #print os.kill(child_pid)
   sys.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)

#if os.name != "nt":
    #signal.signal(signal.SIGCHLD, sigChildHandler)
    # if don't we can't wait() and free zombie children

httpd.serve_forever()

while False:
    #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" 

