Fix to admin
[invirt/packages/invirt-web.git] / templates / main.py
index c708752..915b5eb 100755 (executable)
@@ -15,7 +15,8 @@ import datetime
 import StringIO
 import getafsgroups
 
 import StringIO
 import getafsgroups
 
-sys.stderr = StringIO.StringIO()
+errio = StringIO.StringIO()
+sys.stderr = errio
 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
 
 from Cheetah.Template import Template
 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
 
 from Cheetah.Template import Template
@@ -83,42 +84,64 @@ MIN_DISK_SINGLE = 0.1
 MAX_VMS_TOTAL = 10
 MAX_VMS_ACTIVE = 4
 
 MAX_VMS_TOTAL = 10
 MAX_VMS_ACTIVE = 4
 
-def getMachinesByOwner(owner):
-    """Return the machines owned by a given owner."""
+def getMachinesByOwner(user, machine=None):
+    """Return the machines owned by the same as a machine.
+    
+    If the machine is None, return the machines owned by the same
+    user.
+    """
+    if machine:
+        owner = machine.owner
+    else:
+        owner = user.username
     return Machine.select_by(owner=owner)
 
     return Machine.select_by(owner=owner)
 
-def maxMemory(user, machine=None):
+def maxMemory(user, machine=None, on=True):
     """Return the maximum memory for a machine or a user.
 
     If machine is None, return the memory available for a new 
     machine.  Else, return the maximum that machine can have.
 
     """Return the maximum memory for a machine or a user.
 
     If machine is None, return the memory available for a new 
     machine.  Else, return the maximum that machine can have.
 
-    on is a dictionary from machines to booleans, whether a machine is
-    on.  If None, it is recomputed. XXX make this global?
+    on is whether the machine should be turned on.  If false, the max
+    memory for the machine to change to, if it is left off, is
+    returned.
     """
     """
-
-    machines = getMachinesByOwner(user.username)
+    if not on:
+        return MAX_MEMORY_SINGLE
+    machines = getMachinesByOwner(user, machine)
     active_machines = [x for x in machines if g.uptimes[x]]
     mem_usage = sum([x.memory for x in active_machines if x != machine])
     return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
 
 def maxDisk(user, machine=None):
     active_machines = [x for x in machines if g.uptimes[x]]
     mem_usage = sum([x.memory for x in active_machines if x != machine])
     return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
 
 def maxDisk(user, machine=None):
-    machines = getMachinesByOwner(user.username)
+    machines = getMachinesByOwner(user, machine)
     disk_usage = sum([sum([y.size for y in x.disks])
                       for x in machines if x != machine])
     return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
 
 def canAddVm(user):
     disk_usage = sum([sum([y.size for y in x.disks])
                       for x in machines if x != machine])
     return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
 
 def canAddVm(user):
-    machines = getMachinesByOwner(user.username)
+    machines = getMachinesByOwner(user)
     active_machines = [x for x in machines if g.uptimes[x]]
     return (len(machines) < MAX_VMS_TOTAL and
             len(active_machines) < MAX_VMS_ACTIVE)
 
 def haveAccess(user, machine):
     active_machines = [x for x in machines if g.uptimes[x]]
     return (len(machines) < MAX_VMS_TOTAL and
             len(active_machines) < MAX_VMS_ACTIVE)
 
 def haveAccess(user, machine):
-    """Return whether a user has access to a machine"""
+    """Return whether a user has adminstrative access to a machine"""
+    if user.username == 'moo':
+        return True
+    if user.username in (machine.administrator, machine.owner):
+        return True
+    if getafsgroups.checkAfsGroup(user.username, machine.administrator, 'athena.mit.edu'): #XXX Cell?
+        return True
+    if getafsgroups.checkLockerOwner(user.username, machine.owner):
+        return True
+    return owns(user, machine)
+
+def owns(user, machine):
+    """Return whether a user owns a machine"""
     if user.username == 'moo':
         return True
     if user.username == 'moo':
         return True
-    return getafsgroups.checkLockerOwner(user.username,machine.owner)
+    return getafsgroups.checkLockerOwner(user.username, machine.owner)
 
 def error(op, user, fields, err, emsg):
     """Print an error page when a CodeError occurs"""
 
 def error(op, user, fields, err, emsg):
     """Print an error page when a CodeError occurs"""
@@ -174,8 +197,9 @@ def remctl(*args, **kws):
         p.wait()
         return p.stdout.read(), p.stderr.read()
     if p.wait():
         p.wait()
         return p.stdout.read(), p.stderr.read()
     if p.wait():
-        raise CodeError('ERROR on remctl %s: %s' %
-                          (args, p.stderr.read()))
+        print >> sys.stderr, 'Error on remctl', args, ':'
+        print >> sys.stderr, p.stderr.read()
+        raise CodeError('ERROR on remctl')
     return p.stdout.read()
 
 def lvcreate(machine, disk):
     return p.stdout.read()
 
 def lvcreate(machine, disk):
@@ -195,10 +219,10 @@ def bootMachine(machine, cdtype):
     id of the CD (e.g. 'gutsy_i386')
     """
     if cdtype is not None:
     id of the CD (e.g. 'gutsy_i386')
     """
     if cdtype is not None:
-        remctl('web', 'vmboot', machine.name,
+        remctl('control', machine.name, 'create', 
                cdtype)
     else:
                cdtype)
     else:
-        remctl('web', 'vmboot', machine.name)
+        remctl('control', machine.name, 'create')
 
 def registerMachine(machine):
     """Register a machine to be controlled by the web interface"""
 
 def registerMachine(machine):
     """Register a machine to be controlled by the web interface"""
@@ -252,7 +276,7 @@ def statusInfo(machine):
 
     Gets and parses xm list --long
     """
 
     Gets and parses xm list --long
     """
-    value_string, err_string = remctl('list-long', machine.name, err=True)
+    value_string, err_string = remctl('control', machine.name, 'list-long', err=True)
     if 'Unknown command' in err_string:
         raise CodeError("ERROR in remctl list-long %s is not registered" % (machine.name,))
     elif 'does not exist' in err_string:
     if 'Unknown command' in err_string:
         raise CodeError("ERROR in remctl list-long %s is not registered" % (machine.name,))
     elif 'does not exist' in err_string:
@@ -292,6 +316,7 @@ def createVm(user, name, memory, disk, is_hvm, cdrom):
         machine.name = name
         machine.memory = memory
         machine.owner = user.username
         machine.name = name
         machine.memory = memory
         machine.owner = user.username
+        machine.administrator = user.username
         machine.contact = user.email
         machine.uuid = uuidToString(randomUUID())
         machine.boot_off_cd = True
         machine.contact = user.email
         machine.uuid = uuidToString(randomUUID())
         machine.boot_off_cd = True
@@ -319,8 +344,12 @@ def createVm(user, name, memory, disk, is_hvm, cdrom):
 
     return machine
 
 
     return machine
 
-def validMemory(user, memory, machine=None):
-    """Parse and validate limits for memory for a given user and machine."""
+def validMemory(user, memory, machine=None, on=True):
+    """Parse and validate limits for memory for a given user and machine.
+
+    on is whether the memory must be valid after the machine is
+    switched on.
+    """
     try:
         memory = int(memory)
         if memory < MIN_MEMORY_SINGLE:
     try:
         memory = int(memory)
         if memory < MIN_MEMORY_SINGLE:
@@ -328,7 +357,7 @@ def validMemory(user, memory, machine=None):
     except ValueError:
         raise InvalidInput('memory', memory, 
                            "Minimum %s MB" % MIN_MEMORY_SINGLE)
     except ValueError:
         raise InvalidInput('memory', memory, 
                            "Minimum %s MB" % MIN_MEMORY_SINGLE)
-    if memory > maxMemory(user, machine):
+    if memory > maxMemory(user, machine, on):
         raise InvalidInput('memory', memory,
                            'Maximum %s MB' % maxMemory(user, machine))
     return memory
         raise InvalidInput('memory', memory,
                            'Maximum %s MB' % maxMemory(user, machine))
     return memory
@@ -360,7 +389,7 @@ def create(user, fields):
                            "Already exists")
     
     memory = fields.getfirst('memory')
                            "Already exists")
     
     memory = fields.getfirst('memory')
-    memory = validMemory(user, memory)
+    memory = validMemory(user, memory, on=True)
     
     disk = fields.getfirst('disk')
     disk = validDisk(user, disk)
     
     disk = fields.getfirst('disk')
     disk = validDisk(user, disk)
@@ -511,10 +540,7 @@ def getDiskInfo(data_dict, machine):
 
 def deleteVM(machine):
     """Delete a VM."""
 
 def deleteVM(machine):
     """Delete a VM."""
-    try:
-        remctl('destroy', machine.name)
-    except:
-        pass
+    remctl('control', machine.name, 'destroy', err=True)
     transaction = ctx.current.create_transaction()
     delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
     try:
     transaction = ctx.current.create_transaction()
     delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
     try:
@@ -546,18 +572,18 @@ def command(user, fields):
         raise CodeError("Invalid action '%s'" % action)
     if action == 'Reboot':
         if cdrom is not None:
         raise CodeError("Invalid action '%s'" % action)
     if action == 'Reboot':
         if cdrom is not None:
-            remctl('reboot', machine.name, cdrom)
+            remctl('control', machine.name, 'reboot', cdrom)
         else:
         else:
-            remctl('reboot', machine.name)
+            remctl('control', machine.name, 'reboot')
     elif action == 'Power on':
         if maxMemory(user) < machine.memory:
             raise InvalidInput('action', 'Power on',
                                "You don't have enough free RAM quota to turn on this machine")
         bootMachine(machine, cdrom)
     elif action == 'Power off':
     elif action == 'Power on':
         if maxMemory(user) < machine.memory:
             raise InvalidInput('action', 'Power on',
                                "You don't have enough free RAM quota to turn on this machine")
         bootMachine(machine, cdrom)
     elif action == 'Power off':
-        remctl('destroy', machine.name)
+        remctl('control', machine.name, 'destroy')
     elif action == 'Shutdown':
     elif action == 'Shutdown':
-        remctl('shutdown', machine.name)
+        remctl('control', machine.name, 'shutdown')
     elif action == 'Delete VM':
         deleteVM(machine)
     print >> sys.stderr, time.time()-start_time
     elif action == 'Delete VM':
         deleteVM(machine)
     print >> sys.stderr, time.time()-start_time
@@ -567,95 +593,114 @@ def command(user, fields):
              machine=machine)
     return Template(file="command.tmpl", searchList=[d, global_dict])
 
              machine=machine)
     return Template(file="command.tmpl", searchList=[d, global_dict])
 
-def testOwner(user, owner, machine=None):
-    if not getafsgroups.checkLockerOwner(user.username, owner):
-        raise InvalidInput('owner', owner,
-                           "Invalid")
-    return owner
+def testAdmin(user, admin, machine):
+    if admin in (None, machine.administrator):
+        return None
+    if admin == user.username:
+        return admin
+    if getafsgroups.checkAfsGroup(user.username, admin, 'athena.mit.edu'):
+        return admin
+    if getafsgroups.checkAfsGroup(user.username, 'system:'+admin, 'athena.mit.edu'):
+        return 'system:'+admin
+    raise InvalidInput('admin', admin, 
+                       'You must control the group you move it to')
+    
+def testOwner(user, owner, machine):
+    if owner in (None, machine.owner):
+        return None
+    #XXX should you be able to transfer ownership if you don't already own it?
+    #if not owns(user, machine):
+    #    raise InvalidInput('owner', owner, "You don't own this machine, so you can't  transfer ownership")
+    value = getafsgroups.checkLockerOwner(user.username, owner, verbose=True)
+    if value == True:
+        return owner
+    raise InvalidInput('owner', owner, value)
 
 def testContact(user, contact, machine=None):
 
 def testContact(user, contact, machine=None):
-    if contact != user.email:
-        raise InvalidInput('contact', contact,
-                           "Invalid")
+    if contact in (None, machine.contact):
+        return None
+    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
+        raise InvalidInput('contact', contact, "Not a valid email")
     return contact
 
 def testDisk(user, disksize, machine=None):
     return disksize
 
 def testName(user, name, machine=None):
     return contact
 
 def testDisk(user, disksize, machine=None):
     return disksize
 
 def testName(user, name, machine=None):
-    if Machine.select_by(name=name) == []:
-        return name
-    if name == machine.name:
+    if name in (None, machine.name):
+        return None
+    if not Machine.select_by(name=name):
         return name
         return name
-    raise InvalidInput('name', name,
-                       "Already taken")
+    raise InvalidInput('name', name, "Already taken")
 
 def testHostname(user, hostname, machine):
     for nic in machine.nics:
         if hostname == nic.hostname:
             return hostname
     # check if doesn't already exist
 
 def testHostname(user, hostname, machine):
     for nic in machine.nics:
         if hostname == nic.hostname:
             return hostname
     # check if doesn't already exist
-    if NIC.select_by(hostname=hostname) == []:
-        return hostname
-    raise InvalidInput('hostname', hostname,
-                       "Different from before")
-
+    if NIC.select_by(hostname=hostname):
+        raise InvalidInput('hostname', hostname,
+                           "Already exists")
+    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
+        raise InvalidInput('hostname', hostname, "Not a valid hostname; must only use number, letters, and dashes.")
+    return hostname
 
 def modify(user, fields):
     """Handler for modifying attributes of a machine."""
 
 def modify(user, fields):
     """Handler for modifying attributes of a machine."""
-    #XXX not written yet
 
 
+    olddisk = {}
     transaction = ctx.current.create_transaction()
     try:
         machine = testMachineId(user, fields.getfirst('machine_id'))
         owner = testOwner(user, fields.getfirst('owner'), machine)
     transaction = ctx.current.create_transaction()
     try:
         machine = testMachineId(user, fields.getfirst('machine_id'))
         owner = testOwner(user, fields.getfirst('owner'), machine)
-        contact = testContact(user, fields.getfirst('contact'))
-        hostname = testHostname(owner, fields.getfirst('hostname'),
-                            machine)
+        admin = testAdmin(user, fields.getfirst('administrator'), machine)
+        contact = testContact(user, fields.getfirst('contact'), machine)
+        hostname = testHostname(owner, fields.getfirst('hostname'), machine)
         name = testName(user, fields.getfirst('name'), machine)
         oldname = machine.name
         name = testName(user, fields.getfirst('name'), machine)
         oldname = machine.name
-        olddisk = {}
+        command="modify"
 
         memory = fields.getfirst('memory')
         if memory is not None:
 
         memory = fields.getfirst('memory')
         if memory is not None:
-            memory = validMemory(user, memory, machine)
-        else:
-            memory = machine.memory
-        if memory != machine.memory:
+            memory = validMemory(user, memory, machine, on=False)
             machine.memory = memory
             machine.memory = memory
-
         disksize = testDisk(user, fields.getfirst('disk'))
         if disksize is not None:
             disksize = validDisk(user, disksize, machine)
         disksize = testDisk(user, fields.getfirst('disk'))
         if disksize is not None:
             disksize = validDisk(user, disksize, machine)
-        for disk in machine.disks:
-            disk.size = disksize
-            olddisk[disk.guest_device_name] = disk.size
-            ctx.current.save(disk)
+            disk = machine.disks[0]
+            if disk.size != disksize:
+                olddisk[disk.guest_device_name] = disksize
+                disk.size = disksize
+                ctx.current.save(disk)
         
         
-        # XXX all NICs get same hostname on change?  Interface doesn't support more.
-        for nic in machine.nics:
+        # XXX first NIC gets hostname on change?  Interface doesn't support more.
+        for nic in machine.nics[:1]:
             nic.hostname = hostname
             ctx.current.save(nic)
 
             nic.hostname = hostname
             ctx.current.save(nic)
 
-        if owner != machine.owner:
+        if owner is not None:
             machine.owner = owner
             machine.owner = owner
-        if name != machine.name:
+        if name is not None:
             machine.name = name
             machine.name = name
+        if admin is not None:
+            machine.administrator = admin
+        if contact is not None:
+            machine.contact = contact
             
         ctx.current.save(machine)
         transaction.commit()
     except:
         transaction.rollback()
         raise
             
         ctx.current.save(machine)
         transaction.commit()
     except:
         transaction.rollback()
         raise
-    remctl("web", "moveregister", oldname, name)
-    for disk in machine.disks:
-        # XXX all disks get the same size on change?  Interface doesn't support more.
-        if disk.size != olddisk[disk.guest_device_name]:
-            remctl("web", "lvresize", oldname, disk.guest_device_name, str(disk.size))
-        if oldname != name:
+    for diskname in olddisk:
+        remctl("web", "lvresize", oldname, diskname, str(olddisk[diskname]))
+    if name is not None:
+        for disk in machine.disks:
             remctl("web", "lvrename", oldname, disk.guest_device_name, name)
             remctl("web", "lvrename", oldname, disk.guest_device_name, name)
+        remctl("web", "moveregister", oldname, name)
     d = dict(user=user,
     d = dict(user=user,
-             command="modify",
+             command=command,
              machine=machine)
     return Template(file="command.tmpl", searchList=[d, global_dict])    
 
              machine=machine)
     return Template(file="command.tmpl", searchList=[d, global_dict])    
 
@@ -673,8 +718,21 @@ hope that the sipb-xen maintainers add support for serial consoles.""",
 HVM machines use the virtualization features of the processor, while
 ParaVM machines use Xen's emulation of virtualization features.  You
 want an HVM virtualized machine.""",
 HVM machines use the virtualization features of the processor, while
 ParaVM machines use Xen's emulation of virtualization features.  You
 want an HVM virtualized machine.""",
-                   cpu_weight="""Don't ask us!  We're as mystified as you are.""")
+                   cpu_weight="""Don't ask us!  We're as mystified as you are.""",
+                   owner="""The owner field is used to determine <a href="help?subject=quotas">quotas</a>.  It must be the name
+of a locker that you are an AFS administrator of.  In particular, you
+or an AFS group you are a member of must have AFS rlidwka bits on the
+locker.  You can check see who administers the LOCKER locker using the
+command 'fs la /mit/LOCKER' on Athena.)  See also <a href="help?subject=administrator">administrator</a>.""",
+                   administrator="""The administrator field determines who can access the console and power on and off the machine.  This can be either a user or a moira group.""",
+                   quotas="""Quotas are determined on a per-locker basis.  Each 
+quota may have a maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4 active machines."""
+
+                   )
     
     
+    if not subjects:
+        subjects = sorted(mapping.keys())
+        
     d = dict(user=user,
              simple=simple,
              subjects=subjects,
     d = dict(user=user,
              simple=simple,
              subjects=subjects,
@@ -691,16 +749,19 @@ def info(user, fields):
     if status is None:
         main_status = dict(name=machine.name,
                            memory=str(machine.memory))
     if status is None:
         main_status = dict(name=machine.name,
                            memory=str(machine.memory))
+        uptime=None
+        cputime=None
     else:
         main_status = dict(status[1:])
     else:
         main_status = dict(status[1:])
-    start_time = float(main_status.get('start_time', 0))
-    uptime = datetime.timedelta(seconds=int(time.time()-start_time))
-    cpu_time_float = float(main_status.get('cpu_time', 0))
-    cputime = datetime.timedelta(seconds=int(cpu_time_float))
+        start_time = float(main_status.get('start_time', 0))
+        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
+        cpu_time_float = float(main_status.get('cpu_time', 0))
+        cputime = datetime.timedelta(seconds=int(cpu_time_float))
     display_fields = """name uptime memory state cpu_weight on_reboot 
      on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
     display_fields = [('name', 'Name'),
                       ('owner', 'Owner'),
     display_fields = """name uptime memory state cpu_weight on_reboot 
      on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
     display_fields = [('name', 'Name'),
                       ('owner', 'Owner'),
+                      ('administrator', 'Administrator'),
                       ('contact', 'Contact'),
                       ('type', 'Type'),
                       'NIC_INFO',
                       ('contact', 'Contact'),
                       ('type', 'Type'),
                       'NIC_INFO',
@@ -722,6 +783,7 @@ def info(user, fields):
     machine_info['name'] = machine.name
     machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
     machine_info['owner'] = machine.owner
     machine_info['name'] = machine.name
     machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
     machine_info['owner'] = machine.owner
+    machine_info['administrator'] = machine.administrator
     machine_info['contact'] = machine.contact
 
     nic_fields = getNicInfo(machine_info, machine)
     machine_info['contact'] = machine.contact
 
     nic_fields = getNicInfo(machine_info, machine)
@@ -734,7 +796,7 @@ def info(user, fields):
     
     main_status['memory'] += ' MB'
     for field, disp in display_fields:
     
     main_status['memory'] += ' MB'
     for field, disp in display_fields:
-        if field in ('uptime', 'cputime'):
+        if field in ('uptime', 'cputime') and locals()[field] is not None:
             fields.append((disp, locals()[field]))
         elif field in machine_info:
             fields.append((disp, machine_info[field]))
             fields.append((disp, locals()[field]))
         elif field in machine_info:
             fields.append((disp, machine_info[field]))
@@ -754,6 +816,7 @@ def info(user, fields):
              ram=machine.memory,
              max_mem=max_mem,
              max_disk=max_disk,
              ram=machine.memory,
              max_mem=max_mem,
              max_disk=max_disk,
+             owner_help=helppopup("owner"),
              fields = fields)
     return Template(file='info.tmpl',
                    searchList=[d, global_dict])
              fields = fields)
     return Template(file='info.tmpl',
                    searchList=[d, global_dict])
@@ -783,8 +846,6 @@ if __name__ == '__main__':
         u.email = 'nobody'
     connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
     operation = os.environ.get('PATH_INFO', '')
         u.email = 'nobody'
     connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
     operation = os.environ.get('PATH_INFO', '')
-#    print 'Content-Type: text/plain\n'
-#    print operation
     if not operation:
         print "Status: 301 Moved Permanently"
         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
     if not operation:
         print "Status: 301 Moved Permanently"
         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
@@ -804,29 +865,30 @@ if __name__ == '__main__':
     try:
         output = fun(u, fields)
         print 'Content-Type: text/html\n'
     try:
         output = fun(u, fields)
         print 'Content-Type: text/html\n'
-        sys.stderr.seek(0)
-        e = sys.stderr.read()
+        sys.stderr=sys.stdout
+        errio.seek(0)
+        e = errio.read()
         if e:
             output = str(output)
             output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
         print output
     except CodeError, err:
         print 'Content-Type: text/html\n'
         if e:
             output = str(output)
             output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
         print output
     except CodeError, err:
         print 'Content-Type: text/html\n'
-        sys.stderr.seek(0)
-        e = sys.stderr.read()
         sys.stderr=sys.stdout
         sys.stderr=sys.stdout
+        errio.seek(0)
+        e = errio.read()
         print error(operation, u, fields, err, e)
     except InvalidInput, err:
         print 'Content-Type: text/html\n'
         print error(operation, u, fields, err, e)
     except InvalidInput, err:
         print 'Content-Type: text/html\n'
-        sys.stderr.seek(0)
-        e = sys.stderr.read()
         sys.stderr=sys.stdout
         sys.stderr=sys.stdout
+        errio.seek(0)
+        e = errio.read()
         print invalidInput(operation, u, fields, err, e)
     except:
         print 'Content-Type: text/plain\n'
         print invalidInput(operation, u, fields, err, e)
     except:
         print 'Content-Type: text/plain\n'
-        sys.stderr.seek(0)
-        e = sys.stderr.read()
+        sys.stderr=sys.stdout
+        errio.seek(0)
+        e = errio.read()
         print e
         print '----'
         print e
         print '----'
-        sys.stderr = sys.stdout
         raise
         raise