#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010  Joanna Rutkowska <joanna@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
#

from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesHost
from qubes.qubes import QubesException
from optparse import OptionParser
import sys


fields = {
    "qid": {"func": "vm.qid"},

    "name": {"func": "('=>' if qvm_collection.get_default_template() is not None\
             and vm.qid == qvm_collection.get_default_template().qid else '')\
             + ('[' if vm.is_template() else '')\
             + ('<' if vm.is_disposablevm() else '')\
             + ('{' if vm.is_netvm() else '')\
             + vm.name \
             + (']' if vm.is_template() else '')\
             + ('>' if vm.is_disposablevm() else '')\
             + ('}' if vm.is_netvm() else '')"},

    "type": {"func": "'Tpl' if vm.is_template() else \
             ('Proxy' if vm.is_proxyvm() else \
             (' Net' if vm.is_netvm() else ''))"},

    "updbl" : {"func": "'Yes' if vm.updateable else ''"},

    "template": {"func": "'n/a' if vm.is_template() else\
                 ('None' if vm.template is None else\
                 qvm_collection[vm.template.qid].name)"},

    "netvm": {"func": "'n/a' if vm.is_netvm() and not vm.is_proxyvm() else\
              ('*' if vm.uses_default_netvm else '') +\
              qvm_collection[vm.netvm.qid].name\
                     if vm.netvm is not None else '-'"},

    "ip" : {"func": "vm.ip"},
    "ip back" : {"func": "vm.gateway if vm.is_netvm() else 'n/a'"},
    "gateway/DNS" : {"func": "vm.netvm.gateway if vm.netvm else 'n/a'"},

    "xid" : {"func" : "vm.get_xid() if vm.is_running() else '-'"},

    "mem" : {"func" : "(str(vm.get_mem()/1024) + ' MB') if vm.is_running() else '-'"},
    "cpu" : {"func" : "round (cpu_usages[vm.get_xid()]['cpu_usage'], 1) if vm.is_running() else '-'"},
    "disk": {"func" : "str(vm.get_disk_utilization()/(1024*1024)) + ' MB'"}, 
    "state": {"func" : "vm.get_power_state()"},

    "priv-curr": {"func" : "str(vm.get_disk_utilization_private_img()/(1024*1024)) + ' MB'"},
    "priv-max": {"func" : "str(vm.get_private_img_sz()/(1024*1024)) + ' MB'"},
    "priv-util": {"func" : "str(vm.get_disk_utilization_private_img()*100/vm.get_private_img_sz()) + '%' if vm.get_private_img_sz() != 0 else '-'"},

    "root-curr": {"func" : "str(vm.get_disk_utilization_root_img()/(1024*1024)) + ' MB'"},
    "root-max": {"func" : "str(vm.get_root_img_sz()/(1024*1024)) + ' MB'"},
    "root-util": {"func" : "str(vm.get_disk_utilization_root_img()*100/vm.get_root_img_sz()) + '%' if vm.get_root_img_sz() != 0 else '-'"},

    "label" : {"func" : "vm.label.name"},

    "kernel" : {"func" : "('*' if vm.uses_default_kernel else '') + str(vm.kernel) if hasattr(vm, 'kernel') else 'n/a'"},
    "kernelopts" : {"func" : "('*' if vm.uses_default_kernelopts else '') + str(vm.kernelopts) if hasattr(vm, 'kernelopts') else 'n/a'"},

    "on" : {"func" : "'*' if vm.is_running() else ''"}

}



def main():
    usage = "usage: %prog [options] <vm-name>"
    parser = OptionParser (usage)

    parser.add_option ("-n", "--network", dest="network",
                       action="store_true", default=False,
                       help="Show network addresses assigned to VMs")

    parser.add_option ("-c", "--cpu", dest="cpu",
                       action="store_true", default=False,
                       help="Show CPU load")

    parser.add_option ("-m", "--mem", dest="mem",
                       action="store_true", default=False,
                       help="Show memory usage")

    parser.add_option ("-d", "--disk", dest="disk",
                       action="store_true", default=False,
                       help="Show VM disk utilization statistics")

    parser.add_option ("-k", "--kernel", dest="kernel",
                       action="store_true", default=False,
                       help="Show VM kernel options")

    parser.add_option ("-i", "--ids", dest="ids",
                       action="store_true", default=False,
                       help="Show Qubes and Xen id#s")


    (options, args) = parser.parse_args ()

    qvm_collection = QubesVmCollection()
    qvm_collection.lock_db_for_reading()
    qvm_collection.load()
    qvm_collection.unlock_db()

    fields_to_display = ["name", "on", "state", "updbl", "type", "template", "netvm", "label" ]

    cpu_usages = None

    if (options.ids):
        fields_to_display += ["qid", "xid"]

    if (options.cpu):
        qhost = QubesHost()
        (measure_time, cpu_usages) = qhost.measure_cpu_usage()
        fields_to_display += ["cpu"]

    if (options.mem):
        fields_to_display += ["mem"]

    if (options.network):
        if 'template' in fields_to_display:
            fields_to_display.remove ("template")
        fields_to_display += ["ip", "ip back", "gateway/DNS"]

    if (options.disk):
        if 'template' in fields_to_display:
            fields_to_display.remove ("template")
        if 'netvm' in fields_to_display:
            fields_to_display.remove ("netvm")
        fields_to_display += ["priv-curr", "priv-max", "root-curr", "root-max", "disk" ]

    if (options.kernel):
        fields_to_display += ["kernel", "kernelopts" ]

    
    vms_list = [vm for vm in qvm_collection.values()]
    no_vms = len (vms_list)
    vms_to_display = []
    # Frist, the NetVMs...
    for netvm in vms_list:
        if netvm.is_netvm():
            vms_to_display.append (netvm)

    # Now, the AppVMs without template...
    for appvm in vms_list:
        if appvm.is_appvm() and appvm.template is None:
            vms_to_display.append (appvm)

    # Now, the template, and all its AppVMs...
    for tvm in vms_list:
        if tvm.is_template():
            vms_to_display.append (tvm)
            for vm in vms_list:
                if (vm.is_appvm() or vm.is_disposablevm()) and \
		    vm.template and vm.template.qid == tvm.qid:
                    vms_to_display.append(vm)

    assert len(vms_to_display) == no_vms

    # First calculate the maximum width of each field we want to display
    #  also collect data to display
    for f in fields_to_display:
        fields[f]["max_width"] = len(f)
    data_to_display = []
    for vm in vms_to_display:
        data_row = {}
        for f in fields_to_display:
            if vm.qid == 0 and (f.startswith('priv-') or f.startswith('root-') or f == 'disk'):
                data_row[f] = 'n/a'
            else:
                data_row[f] = str(eval(fields[f]["func"]))
            l = len(data_row[f])
            if l > fields[f]["max_width"]:
                fields[f]["max_width"] = l
        data_to_display.append(data_row)
        try:
            vm.verify_files()
        except QubesException as err:
            print >> sys.stderr, "WARNING: VM '{0}' has corrupted files!".format(vm.name)
                
    # XXX: For what?
    total_width = 0;
    for f in fields_to_display:
        total_width += fields[f]["max_width"]

    # Display the header
    s = ""
    for f in fields_to_display:
        fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1)
        s += fmt.format('-')
    print s
    s = ""
    for f in fields_to_display:
        fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
        s += fmt.format(f) 
    print s
    s = ""
    for f in fields_to_display:
        fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1)
        s += fmt.format('-')
    print s

    # ... and the actual data
    for row in data_to_display:
        s = ""
        for f in fields_to_display:
            fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
            s += fmt.format(row[f]) 
        print s

main()