// Emacs style mode select -*- C++ -*-
//------------------------------------------------------------------------
//
// Copyright(c) 2001, 2002 Simon Howard
//
// 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
//
//--------------------------------------------------------------------------
//
// Multi process forking code for faster scanning
//
//--------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>

#include "samba.h"
#include "unix.h"
#include "parse.h"
#include "ui.h"

extern char **g_argv;

typedef struct {
        enum { 
		CONTROL_STATUS,
		CONTROL_ADDUSER,
		CONTROL_CLOSE,
        } control;
        
        int machine;

	union {
		char user[32];
		machinestatus_t status;
	} data;
} sendpacket_t;

//
// scan machines:
//
// We spawn NUM_PROCESSES subprocesses to do the scanning. They scan like
// this:
//
//   time->      
//
// process 0    0 4  8 12
// process 1    1 5  9 13
// process 2    2 6 10 14     .....
// process 3    3 7 11 15
//

#define NUM_PROCESSES 8

// sockets to the child processes (used by parent)

static int child_pids[NUM_PROCESSES];
static int child_sockets[NUM_PROCESSES];

//=========================================================================
//
// Child Process Stuff
//
// These are stuff used by the child process for scanning etc.
//
//=========================================================================

// socket to the parent process (used by children)

static int parent_socket;

// which child number we are

static int child_num;

//
// pass back a user to the parent process
// used by the scanning code to return new users
//

void scan_add_user(int machine, char *user)
{
        sendpacket_t packet;
        
        // build packet
        
        packet.control = CONTROL_ADDUSER;
        strncpy(packet.data.user, user, 31);
        packet.data.user[31] = '\0';
        packet.machine = machine;
        
        // send 
        
        write(parent_socket, &packet, sizeof(packet));
}

// mark a particular machine as down or not responding

void scan_set_status(int machine, machinestatus_t status)
{
        sendpacket_t packet;

	printf("%s->%i\n", machines[machine]->hostname, status);

        packet.control = CONTROL_STATUS;
	packet.data.status = status;
        packet.machine = machine;

	write(parent_socket, &packet, sizeof(packet));
}

// close a client connection: disconnect before terminating

static void scan_client_close()
{
        static int closed = 0;

        if(!closed) {
                sendpacket_t packet;

                packet.control = CONTROL_CLOSE;
	  
                write(parent_socket, &packet, sizeof(packet));

                close(parent_socket);
                
                closed = 1;
        }
}

// scan a particular machine

void scan_machine(int machine)
{
	// change the process name

	sprintf(g_argv[0], "massfinger: scanning %s", machines[machine]->hostname);

	//fprintf(stderr, "scanning: %s\n", machines[machine]->hostname);

        if(machines[machine]->type == MACHINE_WIN) {
                samba_finger_machine(machine);
        } else if(machines[machine]->type == MACHINE_UNIX 
                  || machines[machine]->type == MACHINE_LINUX) {
                unix_finger_machine(machine);
        } else if(machines[machine]->type == MACHINE_CHECKUP) {
                generic_finger_machine(machine);
        } else {
                printf("wtf\n");
        }

	//fprintf(stderr, "%s: done\n", machines[machine]->hostname);
}

//==========================================================================
//
// Parent process stuff
//
// Stuff done by the parent process: process packets from the children
// etc.
//
//==========================================================================

// kill all child processes on exit

static void kill_children() 
{
        int i;
        
        for(i=0; i<NUM_PROCESSES; i++) {
                kill(child_pids[i], SIGTERM);
        }
}

// signal handler for exit of child scanning processes

static void sigchld_handler(int i)
{
	wait(NULL);
}

// returns true if we are still waiting for some
// of the children to finish

int scan_waiting()
{
        int i;

        for(i=0; i<NUM_PROCESSES; i++) {
                if(child_sockets[i]) {
                        return 1;
                }
        }

        // all 0, finished
        
        return 0;
}

// read new packets from children

void scan_update()
{
        int i;
     
        for(i=0; i<NUM_PROCESSES; i++) {
                
                if(!child_sockets[i])
                        continue;

                while(1) {
                        sendpacket_t packet;
                        
                        if(read(child_sockets[i], &packet, sizeof(packet)) < 0) {
                                if(errno != EAGAIN) {
                                        perror("scan_update: ");
                                }
                                break;
                        }

                        // parse packet
                        
                        if (packet.control == CONTROL_CLOSE) {
                                child_sockets[i] = 0;
                                break;
                        } else if (packet.control == CONTROL_STATUS) {
				set_status(machines[packet.machine],
					   packet.data.status);
                        } else if (packet.control == CONTROL_ADDUSER) {
                                user_t *user = malloc(sizeof(*user));
                                machine_t *machine = machines[packet.machine];
                                
                                user->name = strdup(packet.data.user);
                                
                                add_user(machine, user);
                        }
                }
        }
}

//==========================================================================
//
// Scan
//
//==========================================================================

// do the scan

void scan_machines()
{
        int i;

        for(i=0; i<NUM_PROCESSES; i++) {
                int pid;

                // create a pipe for the parent and child to talk through

                int sockets[2];
                
                socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
                
                // fork
	  
                pid = fork();

                if(pid) {

                        // we are the parent
                        // save pipe to child

                        int flags;
                        
                        child_pids[i] = pid;
                        child_sockets[i] = sockets[0];
                        
                        // nonblocking 
                        
                        flags = fcntl(sockets[0], F_GETFL);
                        flags |= O_NONBLOCK;
                        fcntl(sockets[0], F_SETFL, flags);
                        
                } else {
                        
                        // child

                        child_num = i;
                        
                        // save socket to parent
                        
                        parent_socket = sockets[1];
                        
                        atexit(scan_client_close);
                        
                        // scan machines we have been given
                        
                        for(; i<num_machines; i+=NUM_PROCESSES) {
                                scan_machine(i);
                        }

                        // all done
                        
                        scan_client_close();
                        
			exit(0);
                }
        }

	// catch child process deaths

	signal(SIGCHLD, sigchld_handler);
        
	// if we quit, make sure we kill any child processes

        atexit(kill_children);
}
