#!/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, exclude_progs, exclude_pids):
    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 (d['process'] not in exclude_progs
                and d['pid'] not in exclude_pids):
                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(f):
    f.write("""\
usage: isi_kill_busy [-X] [-x exclude1,exclude2,...]
    -x      ignores processes by any of the names in the supplied
            comma-delimited list
    -X      ignores this process and its parent
""")

def main(argv):
    exclude_progs = []
    exclude_pids = []

    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hXx:', [])
    except getopt.GetoptError, err:
        print str(err)
        usage(sys.stderr)
        sys.exit(2)

    for o, a in opts:
        if o == '-x':
            exclude_progs += a.split(',')
        elif o == '-X':
            exclude_pids += [os.getpid(), os.getppid()]
        elif o == '-h':
            usage(sys.stdout)
            sys.exit(0)
        else:
            sys.stderr.write('Invalid argument: %s\n' % (o,))
            usage(sys.stderr)
            sys.exit(2)

    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, exclude_progs, exclude_pids)

    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, exclude_progs, exclude_pids)
        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, exclude_progs, exclude_pids)
    kill_processes(busy_data, signal.SIGKILL)

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