#!/usr/bin/env python

import errno
import getopt
import os
import os.path
import re
import signal
import sys
import time

try:
    from isi.fs import lin
    from isi.misc import sysctlbyname, ifs_mounted
except ImportError, e:
    print "Could not import isi modules: %s" % e
    sys.exit(1)

def process_busy_vnode_data(data, excludes):
    re_busy_vnode = re.compile('^vnode 0x[\dA-Fa-f]+ ' \
        '\(lin (?P<lin>[^\)]+)\)\s*is\s*(?P<type>.*) ' \
        'of pid (?P<pid>\d+): (?P<process>.*)$')

    busy_info = []
    for line in data.splitlines():
        m = re_busy_vnode.match(line.strip())
        if m:
            d = m.groupdict()
            d['lin'] = long('0x%s' % d['lin'].replace(':', ''), 16)
            d['pid'] = int(d['pid'])
            try:
                d['path'] = lin.getPath((d['lin'],-1))
            except IOError, e:
                if e.errno == errno.ENOENT:
                    d['path'] = "(Not found)"
                else:
                    d['path'] = "(IOError from lin.getPath: %s)" % e
            if not d['process'] in excludes:
                busy_info.append(d)
        else:
            print "No match: %s" % line
            pass
    
    return busy_info

def check_for_working_dir(busy_data):
    had_working_dir=False
    for busy_vnode in busy_data:
        if busy_vnode['type'] == "working dir":
            print "%(process)s [PID: %(pid)i] has a current working " \
                    "dir of %(path)s" % busy_vnode
            had_working_dir=True

    return had_working_dir

def kill_processes(busy_data, signal):
    print "* Killing processes with signal %i" % signal
    # Remove duplicates.  Sysctl can return multiple entries of same pid
    busy_pids = list(set(x['pid'] for x in busy_data)) 
    vnode_dict = dict([(x['pid'] ,x) for x in busy_data])

    for pid in busy_pids:
        try:
            os.kill(pid, 0)
        except OSError, e:
            if e.errno == errno.ESRCH:
                continue
            else:
                raise OSError, e

        print "Killing %(process)s [PID: %(pid)i]" % vnode_dict[pid]

        try:
            os.kill(pid, signal)
        except OSError, e:
            if e.errno == errno.ESRCH:
                pass
            else:
                raise OSError, e

def usage():
    print """\
    usage: isi_kill_busy [-x exclude1,exclude2,...] 
    -x      ignores processes following the x in a comma separated list
    """
        
    
def main(argv):
    excludes = []
    
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'x:', [])
    except getopt.GetoptError, err:
        print str(err) 
        usage()
        sys.exit(1)

    for o, a in opts:
        if o == '-x':
            print 'received x'
            excludes = a.split(',')
        else:
            print 'Invalid argument'
            usage()
            sys.exit(1)

    if not ifs_mounted():
        print "/ifs does not appear to be mounted."
        sys.exit(0)
    
    print "Getting busy vnode status"
    data = sysctlbyname('efs.bam.busy_vnodes')
    busy_data = process_busy_vnode_data(data, excludes)

    try:
        arg=sys.argv[1]
    except IndexError:
        arg=None

    force=True

    if not force and check_for_working_dir(busy_data):
        print "Not killing processes."
        sys.exit(0)

    print "Killing isi_mcp"
    os.system("/usr/bin/killall isi_mcp 2>/dev/null")
    print "Killing lwsm services"
    os.system("/usr/likewise/bin/lwsm shutdown 2>&1 >/dev/null")
    print "Killing isi_hangdump"
    os.system("/usr/bin/killall isi_hangdump 2>/dev/null")
    kill_processes(busy_data, signal.SIGTERM)

    SLEEP_INTERVAL_SECS = 0.1
    MAX_SLEEP_SECS = 5
    t = 0

    while t < MAX_SLEEP_SECS:
        data = sysctlbyname('efs.bam.busy_vnodes')
        busy_data = process_busy_vnode_data(data, excludes)
        if not busy_data:
            sys.exit(0)
        time.sleep(SLEEP_INTERVAL_SECS)
        t += SLEEP_INTERVAL_SECS

    data = sysctlbyname('efs.bam.busy_vnodes')
    busy_data = process_busy_vnode_data(data, excludes)
    kill_processes(busy_data, signal.SIGKILL)

if __name__ == "__main__":
    main(sys.argv[:])
