#!/opt/freeware/bin/python2 -tt

# 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2009 Red Hat Inc
# written by Seth Vidal

# look through list of running apps
# report any app which was updated after it was started
# (and therefore needs to be restarted)


# for each /proc/number-dir 
# get stat of create time on that dir
# open up smaps and search for all lines with 'fd:' in them
# take filename
# search for the package owning that file
# make a list of installtimes of all pkgs of the files the program has open
# sort the list
# if the dir create time is < the largest time on the installtimes list
# then output the pid and process cmdline as needing to be restarted

#TODO:
# maybe note deleted files which are not owned by any pkg but which an app
# is still using
# output userids, too?


import sys
import os
import yum
import glob
import stat
from optparse import OptionParser
import subprocess
import re
import time

def pid_elapse(pid):
    etime_proc = subprocess.Popen("ps " + "-p " + pid + " -o etime=",
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   shell=True)
    (etime, error) = etime_proc.communicate()
    elapse_epoch = 0
    if etime_proc.returncode == 0:
        etime = etime.replace('-',':')
        etime_list = etime.split(':')
        etime_len = len(etime_list)

        (ds, hs, ms, ss, num_secs) = (0, 0, 0, 0, 0)

        if etime_len == 4:
            ds = int(etime_list[0]) * 86400
            hs = int(etime_list[1]) * 3600
            ms = int(etime_list[2]) * 60
            ss = int(etime_list[3])

        elif etime_len == 3:
            hs = int(etime_list[0]) * 3600
            ms = int(etime_list[1]) * 60
            ss = int(etime_list[2])

        elif etime_len == 2:
            ms = int(etime_list[0]) * 60
            ss = int(etime_list[1])

        elif etime_len == 1:
            if not etime_list[0].isspace():
                ss = int(etime_list[0])

        num_secs = ds + hs + ms +ss
        epoch_time = int(time.time())
        elapse_epoch = epoch_time - num_secs
        return elapse_epoch
    else:
        return -1

def parseargs(args):
    usage = """
    needs-restarting: Report a list of process ids of programs that started 
                    running before they or some component they use were updated.
    """
    parser = OptionParser(usage=usage)
    
    parser.add_option("-u", "--useronly", default=False, action="store_true",
      help='show processes for my userid only')
    
    (opts, args) = parser.parse_args(args)
    return (opts, args)


def return_running_pids(uid=None):
    mypid = os.getpid()
    pids = []
    for fn in glob.glob('/proc/[0123456789]*'):
        if mypid == os.path.basename(fn):
            continue

        if uid: # meaning we're not root and we've added -u
            if os.stat(fn)[stat.ST_UID] != uid:
                continue

        pids.append(os.path.basename(fn))
    return pids

def get_open_files(pid):
    # In AIX /proc/pid/map file contains the map of a running process,
    # but this file is a binary file and not a text as in case of linux.
    # So, we need to use the existing tools used to read the /proc files.
    files = []

    line_num = 0
    procmap_proc = subprocess.Popen("procmap " + pid,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     shell=True)
    for line in procmap_proc.stderr:
        if re.search("is a kernel process", line):
            return files
    grep_proc = subprocess.Popen("grep 'read/exec'",
                                  stdin=procmap_proc.stdout,
                                  stdout=subprocess.PIPE,
                                  shell=True)
    for line in grep_proc.stdout:
        line = line.strip().split()
        if line_num != 0:
            objects = line[:][3]
            # .a archive files. Entry returned is libtest.a[shr.o]
            # but it's better to have the entry as libtest.a as rpm -ql command shows
            # library as libtest.a.
            find_a = line[:][3].find('.a')
            if find_a != -1:
                a_line = objects[0:find_a+2]
                files.append(a_line)
            else:
                files.append(objects)
                  
        elif line_num == 0:
            ps_proc = subprocess.Popen("ps -o args -p" + pid + "| sed 1d",
                                        stdout=subprocess.PIPE,
                                        shell=True)
            (cmd, error) = ps_proc.communicate()
            space = cmd.find(' ')
            cmd = cmd[0:space]
            slash = cmd.find('/')
            if slash == -1:
                os.environ["PATH"]="/opt/freeware/bin:/opt/freeware/sbin:/usr/linux/sbin:/usr/linux/bin:/usr/bin:/etc:/usr/sbin:/usr/ucb:/usr/bin/X11:/sbin"
                which_proc = subprocess.Popen("which " + cmd,
                                                 stdout=subprocess.PIPE,
                                                 stderr=subprocess.PIPE,
                                                 shell=True)
                (which_op, error) = which_proc.communicate()
                if which_proc.returncode == 0:
                    files.append(which_op)
        line_num = line_num + 1
 
    return files

def main(args):
    (opts, args)  = parseargs(args)

    my = yum.YumBase()
    my.preconf.init_plugins=False
    if hasattr(my, 'setCacheDir'):
        my.setCacheDir()
    my.conf.cache = True
    
    myuid = None
    if opts.useronly:
        myuid = os.getuid()
    
    needing_restart = []

    for pid in return_running_pids(uid=myuid):
        try:
            # getting ctime doesn't seem to be a accurate thing here.
            # So, get the elpased time since the process start.
            pid_start = pid_elapse(pid)
        except OSError, e:
            continue
        found_match = False
        for fn in get_open_files(pid):
            if found_match:
                break
            
            for pkg in my.rpmdb.searchFiles(fn):
                if float(pkg.installtime) > float(pid_start):
                    needing_restart.append(pid)
                    found_match = True

    for pid in needing_restart:
        try:
            procmap_proc = subprocess.Popen("procmap " + pid, 
                                             stdout=subprocess.PIPE,
                                             shell=True)
            head_proc = subprocess.Popen("head -n1",
                                          stdin=procmap_proc.stdout,
                                          stdout=subprocess.PIPE,
                                          shell=True) 
            (output, error) = head_proc.communicate()
        except OSError, e:
            print "Couldn't access process information for %s: %s" % (pid, str(e))
            continue
        # proc cmdline is null-delimited so clean that up
        print output.strip('\n')
        
    return 0
    
if __name__ == "__main__":
    sys.exit(main(sys.argv))
