#!/usr/bin/python

# sysinfo.py
# written by Darren Kirby :: bulliver@badcomputer.org
# Released under the Artistic License

import sys, os
import re, glob
import time
from getopt import gnu_getopt, GetoptError

appname = sys.argv[0]
appversion = "0.3"
out = sys.stdout
start = time.time()

def getOptions():
    '''
    Process command line options/arguments
    If there are none, we show everything

    TODO:
    Allow html output option...
    If you want txt file output, just use shell redirect (ie: 'sysinfo.py > report.txt')
    '''
    try:
        opts, args = gnu_getopt(sys.argv[1:], "cmkhnfupa", ["cpu", "memory", "kernel", "hardware", "network", "filesystem", "user", 
                                                           "package", "help"])
    except GetoptError:
        print("Invalid option(s)")
        showUsage()
        sys.exit(2)
    flags = []
    for opt, arg in opts:
        if opt in ("-a", "--help"):
            showUsage()
            sys.exit(0)
        if opt in ("-c", "--cpu"):
            flags.append('c')
        if opt in ("-m", "--memory"):
            flags.append('m')
        if opt in ("-k", "--kernel"):
            flags.append('k')
        if opt in ("-h", "--hardware"):
            flags.append('h')
        if opt in ("-n", "--network"):
            flags.append('n')
        if opt in ("-f", "--filesystem"):
            flags.append('f')
        if opt in ("-u", "--user"):
            flags.append('u')
        if opt in ("-p", "package"):
            flags.append('p')
    if flags == []:
        flags = ['c','m','k','h','n','f','u','p']
    return flags

def percent(t,f):
     s = float(f) / float(t)
     s = str(s)
     if s == "1.0":
         return "100"
     else:
         return s[2:4]

def endReport(status):
    '''
    Timestamp, cleanup, and exit
    '''
    out.write("Report created: %s\n" % (time.asctime()))
    end = time.time()
    duration = end - start
    out.write("Report took %f seconds to create\n" % (duration))
    out.close()
    sys.exit(status)

def uptime():
    '''
    Get '/proc/uptime' and convert to d:h:m format
    If not, get value of 'uptime' command
    '''
    oneday = 86400
    onehour = 3600
    oneminute = 60

    try:
        uptimes = open("/proc/uptime").readline().split()
        uptimes = int(uptimes[0].split(".")[0])
    except:
        return os.popen("uptime").readline()

    days = uptimes / oneday
    upmind = uptimes - (days * oneday)
    hours = upmind / onehour
    upminh = upmind - (hours * onehour)
    minutes = upminh / oneminute

    if (days == 0) and (hours == 0):
        return "%im (%f)" % (minutes, uptimes)
    elif (days == 0):
        if int(minutes) <= 9:
            return "%ihr 0%im (%.2fs)" % (hours, minutes, uptimes)
        else:
            return "%ihr %im (%.2fs)" % (hours, minutes, uptimes)
    else:
        return "%id %ihr %im (%.2fs)" % (days, hours, minutes, uptimes)
 
def getIdeInfo():
    '''
    IDE information

    This should print the size of the HDD, but kernel only provides
    the number of sectors. How to find out bytes/sector?
    '''
    idepath = '/proc/ide/'
    devlist = os.listdir(idepath)
    devlist.sort()
    for dev in devlist:
        if 'hd' in dev:
            out.write("/dev/%s information:\n" % (dev))
            dtype = open(idepath + dev + "/media").readline()[:-1]
            out.write("\tType: %s" % (dtype))
            out.write("\tModel: %s" % (open(idepath + dev + "/model").readline()[:-1]))
            if dtype == "disk":
                out.write("\tCache: %s" % (open(idepath + dev + "/cache").readline()[:-1]))
                out.write("\tGeometry: %s" % (open(idepath + dev + "/geometry").readlines()[1][:-1]))
            out.write("\n")
    out.write("\n")

def getScsiInfo():
    '''
    SCSI disk summary is in /proc/scsi/scsi
    This good enough?
    '''
    out.write("SCSI information:\n")
    try:
        for line in open("/proc/scsi/scsi").readlines():
            out.write("\t%s" % (line))
    except:
        out.write("\tUnable to get SCSI information\n")
    out.write("\n")

def getSataInfo():
    '''
    I have no SATA devices...
    How to report it?
    '''
    pass

def getMetaInfo():
    '''
    This info will always be printed
    '''
    out.write("### System Information ###\n\n")
    try:
        distFile = glob.glob("/etc/*release")
        distro = open(distFile[0]).readline()
    except:
        distro = "Unknown" 
    meta = os.uname()
    out.write("Hostname:\t %s\n" % (meta[1]))
    out.write("OS:\t\t %s\n" % (meta[0]))
    out.write("Distribution:\t %s" % (distro))
    out.write("Platform:\t %s\n" % (meta[4]))
    out.write("\n")

def getCpuInfo():
    '''
    This is totally broken...

    It only works for single CPU systems.
    - need someones '/proc/cpuinfo' for an x86, amd64, ppc, other? SMP system
    - sparc changed cpuinfo output, shows clock ticks instead of CPU MHz
    '''
    out.write("### CPU information ###\n\n")
    cpu = open("/proc/cpuinfo")
    try:
        for line in cpu.readlines():
            if "model name" in line: 
                out.write("CPU model:\t%s" % (line.split(":")[1]))
            if "cpu MHz" in line:
                out.write("CPU speed:\t%s" % (line.split(":")[1]))
            if "cache size" in line:
                out.write("CPU cache:\t%s" % (line.split(":")[1]))
            if "bogomips" in line:
                out.write("Bogomips:\t%s" % (line.split(":")[1]))
#     else:  
#         #try:
#         for line in cpu.readlines():  # dirty hack for sparc
#             if "cpu" in line:
#                 if not "ncpus" in line:
#                     out.write("CPU model:\t%s" % (line.split(":")[1]))
#             if "ncpus probed" in line:
#                 out.write("\tCPUs probed: %s" % (line.split(":")[1])) # chomp
#             if "ncpus active" in line:
#                 out.write("\tCPUs active:%s\n" % (line.split(":")[1]))
#             if "Bogo" in line:
#                 print line.split(":")
#                 out.write("\tCPU %s bogomips:\t%s" % (line.split(":")[1], line.split(":")[0][:4]))
    except:
        out.write("No CPU info\n")
    cpu.close()
    out.write("\n")

def getMemInfo():
    '''
    Reads figures from /proc/meminfo
    Do people want +/- buffers/cache?
    If so might as well just print 'free -m' output!
    I like mine better ... it is cleaner (but less portable)
    '''
    out.write("### Memory information ###\n\n")
    for i in open("/proc/meminfo").readlines():
        if "Buffers:" in i:
            b = int(i.split()[-2])
        if re.match("^Cached",i):
            c = int(i.split()[-2])
        if "MemTotal" in i:
            memt = int(i.split()[-2]) / 1024
        if "MemFree" in i:
            memf = int(i.split()[-2]) 
        if "SwapTotal" in i:
            swpt = int(i.split()[-2]) / 1024
        if "SwapFree" in i:
            swpf = int(i.split()[-2]) / 1024
    memf = (memf + b + c) / 1024
    out.write("Total RAM:\t%iM\n" % (memt))
    out.write("Free RAM: \t%iM (%s%%)\n" % (memf, percent(memt,memf)))
    out.write("Total Swap:\t%iM\n" % (swpt))
    out.write("Free Swap:\t%iM (%s%%)\n" % (swpf, percent(swpt,swpf)))
    for line in open("/proc/swaps").readlines():
        if "/dev/" in line:
            swapl = line.split()
    out.write("Swap location:\t%s\tType: %s\n" % (swapl[0], swapl[1]))
    out.write("\n")

def getKernelInfo():
    '''
    Depends on proc filesystem
    '''
    out.write("### Kernel information ###\n\n")
    kernv = os.uname()
    out.write("Kernel version:\t%s\n" % (kernv[2]))
    out.write("Kernel uptime:\t%s\n" % (uptime()))
    out.write("Kernel build:\t%s\n" % (kernv[3]))
    out.write("Kernel cmdline:\t%s" % (open("/proc/cmdline").readline()))

    # Loaded modules...
    mod_l = []
    for module in open("/proc/modules").readlines():
        mod_l.append(module.split()[0])
    mod_l.reverse()
    out.write("Loaded modules:\n")
    if not mod_l:
        out.write("\tNone\n")
        return
    try:
        while mod_l != []:
            out.write("\t")
            for i in range(1,7):
                out.write("%s | " % (mod_l.pop()))
            out.write("\n")
    except IndexError:
        pass
    out.write("\n")

    # Supported FS...
    fs_l = []
    pfs_l = []
    for fs in open("/proc/filesystems").readlines():
        if not "nodev" in fs:
            fs_l.append(fs.split()[0])
        else:
            pfs_l.append(fs.split()[1])
    fsl_l = fs_l

    out.write("Filesystems supported:\n")
    try:
        while fs_l != []:
            out.write("\t")
            for i in range(1,7):
                out.write("%s | " % (fs_l.pop()))
            out.write("\n")
    except IndexError:
        pass

    out.write("\nOther possible supported filesystems (unloaded modules):\n")
    opfs_l = os.listdir("/lib/modules/" + kernv[2] + "/kernel/fs")
    opfss_l = []
    for i in opfs_l:
        if not i in fsl_l:  # Remove loaded modules
            opfss_l.append(i)
    try:
        while opfss_l != []:
            out.write("\t")
            for i in range(1,7):
                out.write("%s | " % (opfss_l.pop()))
            out.write("\n")
    except IndexError:
        pass

    out.write("\nPseudo Filesystems supported:\n")
    try:
        while pfs_l != []:
            out.write("\t")
            for i in range(1,7):
                out.write("%s | " % (pfs_l.pop()))
            out.write("\n")
    except IndexError:
        pass
 
    out.write("\n\n")

def getHardwareInfo():
    '''
    First, divide lspci output into categories
    Second, try to find out disk information

    TODO:
    -How to deal with RAID?
    -How to deal with USB devices? Monitors? Printers?
    -Lots of types of hardware, but we only deal with HDD, optical drives and PCI stuff...
    How to deal with other hardware?
    '''
    out.write("### Hardware information ###\n\n")
    hasIde = hasScsi = hasSata = 0
    try:
        pcidev = os.popen("lspci").readlines()
    except:
        out.write("Cannot find 'lspci'\n")
        return

    out.write("Host/Bus bridges:\n")
    for line in pcidev:
        if "bridge" in line:
            out.write("\t%s" % (line))

    out.write("IDE controllers:\n")
    flag = 0
    for line in pcidev:
        if "IDE" in line:
           flag = 1; hasIde = 1
           out.write("\t%s" % (line))
    if flag == 0:
        out.write("\tNone found\n")

    out.write("SCSI controllers:\n")
    flag = 0
    for line in pcidev:
        if "SCSI" in line:
            flag = 1; hasScsi = 1
            out.write("\t%s" % (line))
    if flag == 0:
        out.write("\tNone found\n")

    out.write("SATA controllers:\n")
    flag = 0
    for line in pcidev:
        if "Mass storage" in line:
            flag = 1; hasSata = 1
            out.write("\t%s" % (line))
    if flag == 0:
        out.write("\tNone found\n")

    out.write("USB controllers:\n")
    flag = 0 
    for line in pcidev:
        if "USB" in line:
            flag = 1
            out.write("\t%s" % (line))
    if flag == 0:
        out.write("\tNone found\n")

    out.write("Network adapters:\n")
    flag = 0
    for line in pcidev:
        if "Ethernet" in line:
            flag = 1
            out.write("\t%s" % (line))
    if flag == 0:
        out.write("\tNone found\n")

    out.write("Audio controllers:\n")
    flag = 0
    for line in pcidev:
        if "Multimedia" in line:
            flag = 1
            out.write("\t%s" % (line))
    if flag == 0:
        out.write("\tNone found\n")

    out.write("FireWire ports:\n")
    flag = 0
    for line in pcidev:
        if "FireWire" in line:
            flag = 1
            out.write("\t%s" % (line))
    if flag == 0:
        out.write("\tNone found\n")

    out.write("Display adapters:\n")
    flag = 0
    for line in pcidev:
        if "VGA" in line:
            flag = 1
            out.write("\t%s" % (line))

    out.write("\n")

    if hasIde == 1:
        getIdeInfo()
    if hasScsi == 1:
        getScsiInfo()
    if hasSata == 1:
        getSataInfo()

def getNetworkInfo():
    '''
    Depends on 'ifconfig', 'route', and proc filesystem
    '''
    out.write("### Network information ###\n\n")
    dev = []
    dev_l = open("/proc/net/dev").readlines()
    for d in dev_l[2:]:
        dev.append(d.split(":")[0])
    out.write("Network devices:")
    for d in dev:
        out.write(" %s " % (d.strip(" ")))
    out.write("\n")

    for d in dev:
        out.write("%s information:\n" % (d.strip(" ")))
        devinfo = os.popen("ifconfig " + d).readlines()
        for line in devinfo:
            if "HWaddr" in line:
                out.write("\tMAC address:\t%s\n" % (line.split()[4]))
            if "inet addr" in line:
                x = line.split()
                ips = []
                for ip in x[1:]:
                    ips.append(ip.split(":")[1])
                if len(ips) == 2:
                    out.write("\tIP address:\t%s\n" % (ips[0]))
                    out.write("\tNetmask:\t%s" % (ips[1]))
                else:
                    out.write("\tIP address:\t%s\n" % (ips[0]))
                    out.write("\tBroadcast:\t%s\n" % (ips[1]))
                    out.write("\tNetmask:\t%s" % (ips[2]))
        out.write("\n")

    gw = os.popen("route -n").readlines()
    out.write("Default gw:\t%s\n" % (gw[-1].split()[1]))
    ns = open("/etc/resolv.conf").readlines()
    for line in ns:
        if "nameserver" in line:
            out.write("Nameserver:\t%s\n" % (line.split()[1]))
    out.write("\n")

def getMountInfo():
    '''
    Depends on 'df'
    '''
    out.write("### Partition/diskspace information ###\n\n")
    out.write("Mounted devices:\n")
    mounts = os.popen("mount").readlines()
    r =[]; p = []; n = []
    for line in mounts:
        if "/dev/" in line.split()[0]:
            r.append(line)
        if ":" in line.split()[0]:
            n.append(line)
        if not "/dev/" in line.split()[0]:
            if not ":" in line.split()[0]:
                p.append(line)
    r.sort(); p.sort(); n.sort()
    for mount in r:
        out.write("\t")
        out.write(mount)
    if n != []:
        out.write("Network mounts:\n")
        for mount in n:
            out.write("\t")
            out.write(mount)
    out.write("Psuedo mounts:\n")
    for mount in p:
        out.write("\t")
        out.write(mount)
    out.write("Disk usage:\n")
    for line in os.popen("df -h").readlines():
        out.write("\t" + line)
    out.write("\n")

def getUserInfo():
    '''
    This depends on 'id' and 'groups' command
    Can you get this info in pure python?
    '''
    out.write("### User/group information ###\n\n")
    out.write("Human users:\n")
    users = []; ruser = []; groups = []
    for line in open("/etc/passwd").readlines():
        if (line.split(":")[0] in line.split(":")[5]) and ("/home/" in line.split(":")[5]) and not ("/false" in line.split(":")[-1]):
            ruser.append(line.split(":")[0])
        else:
            users.append(line.split(":")[0])
    users.sort()
    for user in ruser:
        guid = os.popen("id " + user).readline().split()
        uid = guid[0].split("(")[0]; gid = guid[1].split("(")[0]
        out.write("\t%s: %s %s Groups: %s" % (user, uid, gid, os.popen("groups " + user).readline()))
    out.write("System users:\n")
    for user in users:
        guid = os.popen("id " + user).readline().split()
        uid = guid[0].split("(")[0]; gid = guid[1].split("(")[0]
        out.write("\t%s: %s %s Groups: %s" % (user, uid, gid, os.popen("groups " + user).readline()))

    out.write("Groups:\n") 
    for line in open("/etc/group").readlines():
        t = (line.split(":")[0], line.split(":")[2])
        groups.append(t)
    groups.sort()
    groups.reverse()
    try:
        while groups != []:
            out.write("\t")
            for i in range(1,7):
                out.write("%s(%s) | " % (groups.pop()))
            out.write("\n")
    except IndexError:
        pass 
    out.write("\n")

def getGentooPackageInfo():
    '''
    First check for epm, if not use equery
    epm is _way_ faster (because it uses a DB) in my tests
    Also, equery breaks columned output
    '''
    out.write("\tInstalled Gentoo packages:\n")

    if os.path.exists("/usr/bin/epm"):
        for line in os.popen("epm -qa | sort | column -x").readlines():
            out.write("\t%s" % (line))
        endReport(0)

    if os.path.exists("/usr/bin/equery"):
        for line in os.popen("equery list -i | column -x").readlines():
            out.write("\t%s" % (line))
        endReport(0)
    else:
        out.write("Neither epm nor equery found...\n")
        out.write("Unable to provide package list\n")
        endReport(1)

def getArchPackageInfo():
    '''
    Arch Linux packages
    '''
    out.write("\tInstalled Arch packages:\n")
    for line in os.popen("pacman -Q | column -x").readlines():
        out.write("\t%s" % (line))
    endReport(0)

def getRpmPackageInfo():
    '''
    This should work on any system that uses RPM
    '''
    out.write("\tInstalled RPM packages:\n")
    for line in os.popen("rpm -qa | sort | column -x").readlines():
         out.write("\t%s" % (line))
    endReport(0)

def getDpkgPackageInfo():
    '''
    Unsure of dpkg command or output
    Help anyone?
    '''
    out.write("dpkg output not yet implemented\n")
    endReport(0)
 
def getPackageInfo():
    out.write("\n### Package information ###\n\n")
    try:
        distFile = glob.glob("/etc/*release")
        distro = open(distFile[0]).readline()
    except:
        out.write("Can't confirm distribution, unable to provide package info\n")
        endReport(0)

    if "Gentoo" in distro:
        getGentooPackageInfo()
    if "Arch" in distro:
        getArchPackageInfo()
    if "Fedora" in distro:   
        getRpmPackageInfo()
    if "Redhat" in distro:
        getRpmPackageInfo()
    if "SuSe" in distro:
        getRpmPackageInfo()
    # What other distros use RPMs?
    if "Debian" in distro:
        getDpkgPackageInfo()
    if "Ubuntu" in distro:
        getDpkgPackageInfo()
    # slackware?

def showBanner():
    print "%s version %s" % (appname, appversion)
    print '''Written by Darren Kirby :: d@badcomputer.org :: http://badcomputer.org/unix/code/sysinfo.bot
Released under the Artistic License.
'''

def showUsage():
    showBanner()
    print "Usage: %s [options]" % (appname)
    print '''    Options:
       '-a'   or '--help'         display usage details (this!)
       '-c'   or '--cpu'          display CPU information
       '-m'   or '--memory'       display memory information
       '-k'   or '--kernel'       display kernel information
       '-h'   or '--hardware'     display hardware information
       '-n'   or '--network'      display network information
       '-f'   or '--filesystem'   display filesystem/mount information
       '-u'   or '--user'         display user/group information
       '-p'   or '--package'      display installed package information (will not work on all distros)
'''

def main():
    flags = getOptions()
    showBanner()
    getMetaInfo()
    if 'c' in flags:
        getCpuInfo()
    if 'm' in flags:
        getMemInfo()
    if 'k' in flags:
        getKernelInfo()
    if 'h' in flags:
        getHardwareInfo()
    if 'n' in flags:
        getNetworkInfo()
    if 'f' in flags:
        getMountInfo()
    if 'u' in flags:
        getUserInfo()
    if 'p' in flags:
        getPackageInfo()
    endReport(0)

if __name__ == '__main__':
    main()