/****************************************************************************
 *									    *
 *			  COPYRIGHT (c) 1990 - 2004			    *
 *			   This Software Provided			    *
 *				     By					    *
 *			  Robin's Nest Software Inc.			    *
 *									    *
 * Permission to use, copy, modify, distribute and sell this software and   *
 * its documentation for any purpose and without fee is hereby granted,	    *
 * provided that the above copyright notice appear in all copies and that   *
 * both that copyright notice and this permission notice appear in the	    *
 * supporting documentation, and that the name of the author not be used    *
 * in advertising or publicity pertaining to distribution of the software   *
 * without specific, written prior permission.				    *
 *									    *
 * THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 	    *
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN	    *
 * NO EVENT SHALL HE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL   *
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR    *
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS  *
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF   *
 * THIS SOFTWARE.							    *
 *									    *
 ****************************************************************************/
/*
 * Module:	dtprocs.c
 * Author:	Robin T. Miller
 * Date:	August 7, 1993
 *
 * Description:
 *	Functions to handle multiple processes for 'dt' program.
 *
 * Modification History:
 *
 * November 17th, 2003 by Robin Miller.
 *	Breakup output to stdout or stderr, rather than writing
 * all output to stderr.  If output file is stdout ('-') or a log
 * file is specified, then all output reverts to stderr.
 *
 * March 14th, 2003 by Robin Miller.
 *	Added support for testing an individual slice.
 *
 * May 31st, 2001 by Robin Miller.
 *	Update abort_procs() to use max_procs, valid for both multiple
 * processes and slices, and loop through entire process table since
 * some processes may have finished already (use to break out early).
 *	When starting slices with multiple processes, prime the data
 * pattern to ensure each process uses the same pattern in a slice.
 *
 * February 6th, 2001 by Robin Miller.
 *	If doing multiple slices in reverse direction, ensure the data
 * limit gets tot he slice length, or we'll overflow into previous slice.
 *
 * February 1st, 2001 by Robin Miller.
 *	Fix dumb problem starting multiple procs with multiple slices.
 *
 * January 28th, 2001 by Robin Miller.
 *	When aborting processes, send SIGINT instead of SIGTERM, so
 * statistics gets reported (important on long running commands :-).
 *	Added support for multiple slices option.  This sets up each
 * process exercising a different range of blocks (slice) on the disk.
 *
 * January 2nd, 2001 by Robin Miller.
 *      Make changes to build using MKS/NuTCracker product.
 *
 * March 28th, 2000 by Robin Miller.
 *	When creating multiple processes, do a better job determining
 * when a unique device name should be constructed.  Was broken for
 * named piped (FIFO's).
 *
 * May 3, 1999 by Robin Miller.
 *	Allocate more space for unique file names, since the size
 * of pid_t is now 31 bits in Steel.
 *
 * April 8, 1999 by Robin Miller.
 *	Merge in Jeff Detjen's changes for current process count.
 *
 * April 29, 1998 by Robin Miller.
 *	Add support for an alternate device directory.
 *
 * July 17, 1995 by Robin Miller.
 *	Apply a severity priority to child exit status.
 *	Added flag to ensure process abortion occurs only once.
 *
 * July 5, 1995 by Robin Miller.
 *	Properly check for child process exiting as result of a signal.
 *
 * March 28, 1995 by Robin Miller.
 *	Report specific error for "no processes started", and exit with
 *	error status if the system process limit has been exceeded.
 *
 * November 4, 1994 by Robin Miller.
 *	Don't set SIGCHLD signal to SIG_IGN (set to SIG_DFL) or else
 *	waitpid() won't detect any child processes (OSF R1.3 and QNX).
 *	[ Unfortunately, the POSIX standard states "The specification
 *	  of the effects of SIG_IGN on SIGCHLD as implementation defined
 *	  and permits, but does NOT require, the System V effect of
 *	  causing terminating children to be ignored by wait(). Yuck!!! ]
 */
#include "dt.h"
#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>

#define PROC_ALLOC (sizeof(pid_t) * 3)	/* Extra allocation for PID.	*/

/*
 * Structure to track multiple processes.
 */
struct dt_procs {
	pid_t	dt_pid;			/* The child process ID.	*/
	int	dt_status;		/* The child exit status.	*/
	bool	dt_active;		/* The process active flag.	*/
};

/*
 * Slice Range Definition:
 */
typedef struct slice_info {
	int	slice;			/* Slice number.		*/
	large_t	slice_position;		/* Starting slice position.	*/
	large_t	slice_length;		/* The slice data length.	*/
} slice_info_t;

/*
 * Forward References:
 */
static void init_slice_info(struct dinfo *dip, slice_info_t *sip, large_t *data_resid);
static void setup_slice(struct dinfo *dip, slice_info_t *sip);

struct dt_procs *ptable;		/* Multiple 'dt' procs table.	*/
int num_procs = 0;			/* Number of procs to create.	*/
int cur_proc = 0;			/* Current count of processes.	*/
int max_procs = 0;			/* Maximum processes started.	*/
int procs_active = 0;			/* Number of active processes.	*/
int num_slices = 0;			/* Number of slices to create.	*/
int slice_num = 0;			/* Slice number to operate on.	*/

/*
 * abort_procs - Abort processes started by the parent.
 */
void
abort_procs(void)
{
    static int aborted_processes = FALSE;
    struct dt_procs *dtp;
    int procs;
    pid_t pid;

    if ((ptable == NULL) || aborted_processes)  return;
    /*
     * Force all processes to terminate.
     */
    for (dtp = ptable, procs=0; procs < max_procs; procs++, dtp++) {
	if ((pid = dtp->dt_pid) == (pid_t) 0) continue;
	if (debug_flag) {
	    Printf("Aborting child process %d via a SIGINT (%d)...\n",
								pid, SIGINT);
	}
	if (dtp->dt_active) (void) kill (pid, SIGINT);
    }
    aborted_processes = TRUE;
}

void
await_procs(void)
{
    pid_t wpid;
    struct dt_procs *dtp;
    int procs, status;

    if (debug_flag) {
	Printf ("Waiting for %d child processes to complete...\n", procs_active);
    }
    while (1) {
	if ((wpid = waitpid ((pid_t) -1, &child_status, 0)) == FAILURE) {
	    if (errno == ECHILD) {
		if (procs_active) abort();	/* Programming error... */
		break;				/* No more children... */
	    } else if (errno == EINTR) {
		abort_procs();
		continue;
	    } else {
		report_error ("waitpid", FALSE);
		exit (FATAL_ERROR);
	    }
	}
	/*
	 * Examine the child process status.
	 */
	if ( WIFSTOPPED(child_status) ) {
	    Printf ("Child process %d, stopped by signal %d.\n",
					wpid, WSTOPSIG(child_status));
	    continue; /* Maybe attached from debugger... */
	} else if ( WIFSIGNALED(child_status) ) {
	    status = WTERMSIG(child_status);
	    Fprintf ("Child process %d, exiting because of signal %d\n",
							wpid, status);
	} else { /* Process must be exiting... */
	    status = WEXITSTATUS (child_status);
	    if (debug_flag) {
		Printf ("Child process %d, exiting with status %d\n",
							wpid, status);
	    }
	}
	if ( (exit_status == SUCCESS) && (status != SUCCESS) ) {
	    if ( (oncerr_action == ABORT) &&
		 (status != WARNING) && (status != END_OF_FILE) ) {
		abort_procs();		/* Abort procs on error. */
	    }
	    /*
	     * Save the most sever error for parent exit status.
	     *
	     * Severity Priorities:	WARNING		(lowest)
	     *				END_OF_FILE
	     *				Signal Number
	     *				FATAL_ERROR	(highest)
	     */
	    if ( ((exit_status == SUCCESS) || (status == FATAL_ERROR)) ||
		 ((exit_status == WARNING) && (status > WARNING))      ||
		 ((exit_status == END_OF_FILE) && (status > WARNING)) ) {
		exit_status = status;	/* Set error code for exit. */
	    }
	}
	/*
	 * House keeping... (mostly sanity check, not really necessary).
	 */
	for (dtp = ptable, procs = 0; procs < max_procs; procs++, dtp++) {
	    if (dtp->dt_pid == wpid) {
		dtp->dt_active = FALSE;
		dtp->dt_status = status;
		procs_active--;
	    }
	}
    } /* End of while(1)... */
}

pid_t
fork_process(void)
{
    pid_t pid;

    if ((pid = fork()) == (pid_t) -1) {
	if (errno == EAGAIN) {
	    if (procs_active == 0) {
		LogMsg (efp, logLevelCrit, 0,
	"ERROR: could NOT start any processes, please check your system...\n");
		exit (FATAL_ERROR);
	    } else {
		Printf (
	"WARNING: system imposed process limit reached, only %d procs started...\n",
								procs_active);
	    }
	} else {
	    report_error ("fork", FALSE);
	    abort_procs();
	}
    }
    return (pid);
}

pid_t
start_procs(void)
{
    struct dt_procs *dtp;
    size_t psize;
    int procs;

    max_procs = num_procs;
    psize = (max_procs * sizeof(*dtp));

    if ((ptable = (struct dt_procs *)malloc(psize)) == NULL) {
	report_error ("No memory for proc table", FALSE);
	exit (FATAL_ERROR);
    }
    bzero((char *)ptable, psize);
#if !defined(__MSDOS__) || defined(__NUTC__)
    (void) signal (SIGCHLD, SIG_DFL);
#endif
    (void) signal (SIGHUP, terminate);
    (void) signal (SIGINT, terminate);
    (void) signal (SIGTERM, terminate);

    cur_proc = 1;
    procs_active = 0;

    for (dtp = ptable, procs = 0; procs < max_procs; procs++, dtp++) {
	if ((child_pid = fork_process()) == (pid_t) -1) {
	    break;
	} else if (child_pid) {		/* Parent process gets the PID. */
	    cur_proc++;
	    dtp->dt_pid = child_pid;
	    dtp->dt_active = TRUE;
	    procs_active++;
	    if (debug_flag) {
		Printf ("Started Process %d...\n", child_pid);
	    }
	} else {			/* Child process... */
	    struct stat sb;
	    bool make_unique = FALSE;
	    int error;

	    if (!output_file) break;
	    error = stat (output_file, &sb);
	    if (!error) {
		if ( S_ISREG(sb.st_mode) ) {
		    make_unique = TRUE;
		}
	    /* Leave all other types alone! */
	    } else if ( (NEL (output_file, DEV_PREFIX, DEV_LEN)) &&
			(NEL (output_file, ADEV_PREFIX, ADEV_LEN)) ) {
		make_unique = TRUE;	/* Ok, not a device directory. */
	    }
	    /*
	     * Construct unique file name for file system I/O.
	     */
	    if (make_unique) {
		char *bp;
		bp = (char *)malloc(strlen(output_file) + PROC_ALLOC);
		(void)sprintf(bp, "%s-%d", output_file, getpid());
		output_dinfo->di_dname = output_file = bp;
	    }
	    break;			/* Child process, continue... */
	}
    }
    return (child_pid);
}

pid_t
start_slices(void)
{
    struct dinfo *dip = active_dinfo;
    struct dt_procs *dtp;
    size_t psize;
    struct slice_info slice_info;
    slice_info_t *sip = &slice_info;
    large_t data_resid;
    int procs;

    max_procs = num_slices;
    psize = (max_procs * sizeof(*dtp));

    if ((ptable = (struct dt_procs *)malloc(psize)) == NULL) {
	report_error ("No memory for proc table", FALSE);
	exit (FATAL_ERROR);
    }
    bzero((char *)ptable, psize);
#if !defined(__MSDOS__) || defined(__NUTC__)
    (void) signal (SIGCHLD, SIG_DFL);
#endif
    (void) signal (SIGHUP, terminate);
    (void) signal (SIGINT, terminate);
    (void) signal (SIGTERM, terminate);

    init_slice_info(dip, sip, &data_resid);

    cur_proc = 1;
    procs_active = 0;

    for (dtp = ptable, procs = 0; procs < max_procs; procs++, dtp++) {
	sip->slice++;
	if ((child_pid = fork_process()) == (pid_t) -1) {
	    break;
	} else if (child_pid) {		/* Parent process gets the PID. */
	    cur_proc++;
	    dtp->dt_pid = child_pid;
	    dtp->dt_active = TRUE;
	    procs_active++;
	    if (debug_flag) {
		Printf ("Started Slice %d, PID %d...\n", sip->slice, child_pid);
	    }
	    if (procs < max_procs) {
		sip->slice_position += sip->slice_length;
		if (procs == max_procs) {
		    sip->slice_length += data_resid;
		}
	    }
	} else {			/* Child process... */
	    /*
	     * Initialize the starting data pattern for each slice.
	     */
	    if (unique_pattern) {
		pattern = data_patterns[(cur_proc - 1) % npatterns];
	    }
	    setup_slice(dip, sip);
	    break;			/* Child process, continue... */
	}
    }
    return (child_pid);
}

void
init_slice(struct dinfo *dip, int slice)
{
    struct slice_info slice_info;
    slice_info_t *sip = &slice_info;
    large_t data_resid;

    init_slice_info(dip, sip, &data_resid);
    sip->slice_position += (sip->slice_length * (slice - 1));
    /*
     * Any residual goes to the last slice.
     */
    if (slice == num_slices) {
	sip->slice_length += data_resid;
    }
    sip->slice = slice;
    setup_slice(dip, sip);

    /*
     * Initialize the starting data pattern for each slice.
     */
    if (unique_pattern) {
	pattern = data_patterns[(slice - 1) % npatterns];
    }
    return;
}

static void
init_slice_info(struct dinfo *dip, slice_info_t *sip, large_t *data_resid)
{
    large_t slice_length;

    sip->slice = 0;
    sip->slice_position = file_position;
    slice_length = ((dip->di_data_limit - file_position) / num_slices);
    sip->slice_length = rounddown(slice_length, dip->di_dsize);
    if (sip->slice_length < dip->di_dsize) {
	LogMsg (efp, logLevelCrit, 0,
		"Slice length of " LUF " bytes is too small!\n",
						sip->slice_length);
	exit (FATAL_ERROR);
    }
    *data_resid = (dip->di_data_limit - (sip->slice_length * num_slices));
    *data_resid = rounddown(*data_resid, dip->di_dsize);
    return;
}

static void
setup_slice(struct dinfo *dip, slice_info_t *sip)
{
    file_position = sip->slice_position;
    if (dip->di_random_io) {
	rdata_limit = (file_position + sip->slice_length);
    }
    /*
     * Restrict data limit to slice length or user set limit.
     */
    data_limit = MIN(data_limit, sip->slice_length);
    if (debug_flag || Debug_flag) {
	large_t dlimit = (dip->di_random_io) ? rdata_limit : data_limit;
	Printf("Slice %d Information:\n"
		"\t\t Start: " FUF " offset (lba %u)\n"
		"\t\t   End: " FUF " offset (lba %u)\n"
		"\t\tLength: " FUF " bytes (%d blocks)\n"
		"\t\t Limit: " FUF " bytes (%d blocks)\n",
		sip->slice,
		file_position, (u_int32)(file_position / dip->di_dsize),
		(file_position + sip->slice_length),
		((file_position + sip->slice_length) / dip->di_dsize),
		sip->slice_length, (sip->slice_length / dip->di_dsize),
		dlimit, (dlimit / dip->di_dsize));
    }
    return;
}


syntax highlighted by Code2HTML, v. 0.9.1