#! /usr/bin/env python
#
# Copyright (C) 2005 BULL SA.
# Written by Guillaume Thouvenin <guillaume.thouvenin@bull.net>
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#


"""
This program allows the manipulation of group of processes called jobs. 
You can add a process in a given existing job or create a new job by 
giving 0 as job ID on the command line. You can remove a process from
a given job. You can also act as a server to recover existing information
about jobs. Finally, this command line program allows you to stop the 
job daemon.
"""

import getopt
import os
import socket
import sys

__revision__ = "$Revision: 1.6 $"

# Update variables below if jobd.h is modified  
SK_PATH_CLNT = "/tmp/sock_clnt"
SK_PATH_JOBD = "/tmp/sock_jobd"
SK_PATH_EXCH = "/tmp/sock_exch_jobd"

ADD_REQ      = '1'
REMOVE_REQ   = '2'
GETINFO_REQ  = '3'
QUIT_REQ     = '4'

def print_help(prog):
    """
    Displays help about 'prog'

    @prog: The name of the program
    """

    print """    Usage: 
        %s [action] [options]
        %s -h

    Actions: [-a | -r | -g | -s]
    Options: -[pje]

    Help (this screen):
        -h  Displays this help.
        
    Actions:
        -a  Adds a process to a given group of processes called 
            job. If the job ID is equal to 0 or not specified, a new 
	    job is created. The process PID and and job ID are set 
	    with options (see Options). 

        -r  Removes a process from a given job. The process PID and 
            and job ID are set with options (see Options).
    
        -d  Dump information about jobs on the standard ouput. 

        -s  Stops the job daemon.

    Options:
        -p  Sets the PID of a process

        -j  Sets the ID of a job. It can be equal to 0 when we want to 
            add a process in a new job.

        -e  Executes the command in a given job

    Examples:
        Adds a command in a job and executes it
        %s -a -e "ls -lR /"
 
        Adds a command in an existing job and executes it
        %s -a -e "gcc /tmp/file.c" -j 1

        Adds process 1234 in a new job
        %s -a -p 1234

        Removes process 1234 from job ID 2
        %s -r -p 1234 -j 2
    """ % (prog, prog, prog, prog, prog, prog)

    
def msg_payload(val):
    """
    Structure needed by the job daemon to get information is 
    {int, int, int}. As sendto() routine sends strings, we need to 
    convert the integer to four characters. The trick here is to 
    convert a string to a 32-bit packed binary format. We need to 
    reverse the results to obtain the right format.

    @val: value to convert
    """
   
    # Fisrt, we convert the command to the 32-bit binary format
    msg = socket.inet_aton(val)
    # create a list and reverse it
    tmp = list(msg)
    tmp.reverse()
    # rebuild the string from the list
    msg = ''
    msg = msg.join(tmp)
    return msg 

    
def read_jobd_info(sock, msg, path):
    """
    Reads information about jobs sent by the server. To achieve this 
    we act as a server. We must set the server before asking to the job
    daemon to start the sending. Information is displayed on the standard
    output.

    @sock: socket used to send request to job daemon
    @msg: request to send
    @path: path of the job daemon socket
    """
    
    # Open a socket for local communication
    local_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

    # Before binding the socket to the exchange path, we need to check 
    # if the path is not already used. If the answer is yes, remove
    # it. 
    try:
        os.unlink(SK_PATH_EXCH)
    except os.error: 
        # Just do nothing
        None

    try:
        local_sock.bind(SK_PATH_EXCH)
    except socket.error:
        print "Cannot bind socket to", SK_PATH_EXCH
        local_sock.close()
        return

    # enable the connection to become a server.
    local_sock.listen(5)

    # We are ready to receive information so we can ask to the job 
    # daemon to start sending
    try:
        sock.sendto(msg, path)
    except socket.error:
        print "Error when sending GET_REQ"
        print "Check if jobd is running"
        local_sock.close()
        os.unlink(SK_PATH_EXCH)
        return
   
    (jobd_conn, addr) = local_sock.accept()
    # addr, the adress of the client is not use

    while True:
        data = jobd_conn.recv(1024)
        if not data: 
            break
        else:
            os.write(1, data)

    os.write(1, '\n')
    
    jobd_conn.close()
    local_sock.close()
    os.unlink(SK_PATH_EXCH)


def parse_command_line(progname, command_line):
    """
    Parse the arguments given in command line. 

    @progname: The executable's name
    @command_line: list of all arguments (whitout the executable's name)
    """
    
    err = 0
    req = '0'
    pid = '0'
    jid = '0'
    cmd = ''

    # We parse start options
    try:
        opts, args = getopt.getopt(command_line, "hardsp:j:e:")
    except getopt.GetoptError:
        # print help and exit:
        print_help(progname)
        sys.exit(2)

    if not args == []:
        print "Warning: Argument(s)", args, "are not needed and useless"

    for option, argument in opts:
        if option == "-h":
            print_help(progname)
            sys.exit(2)
        elif option == "-a":
            req = ADD_REQ
            err = err + 1
        elif option == "-r":
            req = REMOVE_REQ
            err = err + 1
        elif option == "-d":
            req = GETINFO_REQ
            err = err + 1
        elif option == "-s":
            req = QUIT_REQ
            err = err + 1
        elif option == "-p":
            pid = argument
        elif option == "-j":
            jid = argument
        elif option == "-e":
            pid = str(os.getpid())
            cmd = argument

    # We cannot use add, remove or stop at the same time
    # it means that error must be equal to 1
    if err != 1:
        print progname, ": Wrong number of arguments"
        print "Try", progname, "-h for more information"
        sys.exit(2)

    return req, pid, jid, cmd
    
def main():
    """
    The main routine
    """

    # Initialize some variables
    progname = os.path.abspath(sys.argv[0]).split(os.sep)[-1]

    # Parse the command line that must have at least one argument  
    if len(sys.argv[1:]) == 0:
        print progname, ": Missing argument"
        print "Try", progname, "-h for more information"
        sys.exit(2)
    
    req, pid, jid, cmd = parse_command_line(progname, sys.argv[1:])

    # Open a socket for local communication
    try:
        local_sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    except socket.error:
        print "Cannot open the socket"
        print "Exit"
        sys.exit(2)
        

    # Before binding the socket to the local path, we need to check 
    # if the path is not already used. If the answer is yes, remove
    # it. 
    try:
        os.unlink(SK_PATH_CLNT)
    except os.error: 
        # Nothing to do
        None

    try:
        local_sock.bind(SK_PATH_CLNT)
    except socket.error:
        print "Cannot bind socket to", SK_PATH_CLNT
        print "Exit"
        local_sock.close()
        sys.exit(2)
    

    #  Now we can send the corresponding request
    msg = msg_payload(req) + msg_payload(pid) + msg_payload(jid)

    # if we want to get information about jobs we need to read 
    # information sent by the server
    if req == GETINFO_REQ:
        read_jobd_info(local_sock, msg, SK_PATH_JOBD)
    else:
        try:
            local_sock.sendto(msg, SK_PATH_JOBD)
        except socket.error:
            print "Error when sending the request"
            print "Check if jobd is running"

    # Execute a command
    if (req == ADD_REQ) and (cmd != ''):
        opts = cmd.split()
        cmd = opts[0]
        try: 
            os.execvp(cmd, opts)
        except os.error:
            print "Cannot run", cmd

    # And clear everything... 
    local_sock.close()
    os.unlink(SK_PATH_CLNT)

if __name__ == "__main__":
    main()
