/*-
 * cctrl.c --
 *	Program to perform various utility functions on a customs network
 *	or a single customs agent. The agent/network can be aborted,
 *	restarted, pinged, have its debugging parameters set or have
 *	an election forced on it.
 *
 * Copyright (c) 1988, 1989 by the Regents of the University of California
 * Copyright (c) 1988, 1989 by Adam de Boor
 * Copyright (c) 1989 by Berkeley Softworks
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any non-commercial purpose
 * and without fee is hereby granted, provided that the above copyright
 * notice appears in all copies.  The University of California,
 * Berkeley Softworks and Adam de Boor make no representations about
 * the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 */
#ifndef lint
static char *rcsid =
"$Id: cctrl.c,v 1.47 1995/12/31 04:29:26 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

#include    <stdio.h>
#include    <time.h>
#include    <sys/time.h>
#include    <netdb.h>
#include    <string.h>
#include    <ctype.h>
#include    <signal.h>
#include    <pwd.h>

#include    "customs.h"

int	nReplies;		/* number of replies to broadcast */

char *
getaddress(from)
    struct sockaddr_in	*from;
{
    struct hostent *he;
    char *cp;
    
    if (he = gethostbyaddr((char *)&(from->sin_addr),
				 sizeof(from->sin_addr),
				 AF_INET))
    {
	if (cp = strchr(he->h_name, '.')) {
	    *cp = '\0';
	}
	return (he->h_name);
    } else {
	return (InetNtoA(from->sin_addr));
    }
}

/*ARGSUSED*/
Boolean
DefaultResponse(from, len, data)
    struct sockaddr_in	*from;
    int	    	  	len;
    Rpc_Opaque	  	data;
{
    
    if (from) {
	char *address = getaddress(from);

	if (len != 0) {
	    fprintf (stderr, "non-zero length reply from %s (%d bytes)\n",
		     address, len);
	    fflush (stderr); 
	} else {
	    nReplies++;
	    printf ("%s: acknowledged\n", address);
	    fflush (stdout);
	}
    } else {
	printf ("%5d agent(s) responded\n", nReplies);
    }

    return (False);
}

/*ARGSUSED*/
Boolean
PingResponse(from, len, data)
    struct sockaddr_in	*from;
    int	    	  	len;
    Rpc_Opaque	  	data;
{
    if (from) {
	char *address = getaddress(from);

	if (len != 0) {
	    fprintf (stderr, "non-zero length reply from %s (%d bytes)\n",
		     address, len);
	    fflush (stderr); 
	} else {
	    nReplies++;
	    printf("customs on %s is alive\n", address);
	    fflush (stdout);
	}
    } else {
	printf ("%5d agent(s) responded\n", nReplies);
    }

    return (False);
}

/*ARGSUSED*/
Boolean
VersionResponse(from, len, data)
    struct sockaddr_in	*from;
    int	    	  	len;
    Version_Data  	*data;
{
    if (from) {
	char *address = getaddress(from);

	if (len != sizeof(*data)) {
	    fprintf (stderr, "wrong length length reply from %s (%d bytes)\n",
		     address, len);
	    fflush (stderr); 
	} else {
	    nReplies++;
	    printf("%s: Customs version %d.%d.%d #%d: built by %s on %s\n",
		   address,
		   (int)data->majorversion,
		   (int)data->minorversion,
		   (int)data->patchlevel,
		   (int)data->buildno,
		   data->builder,
		   data->builddate);
	    fflush (stdout);
	}
    } else {
	printf ("%5d agent(s) responded\n", nReplies);
    }

    return (False);
}

/*ARGSUSED*/
Boolean
JobsResponse(from, len, data)
    struct sockaddr_in	*from;
    int	    	  	len;
    Job_Info  		*data;
{
    static int nJobs, nPermits;

    if (from) {
	char *address = getaddress(from);

	if (len % sizeof(*data)) {
	    fprintf (stderr, "wrong length length reply from %s (%d extra bytes)\n",
		     address, len % sizeof(*data));
	    fflush (stderr); 
	} else {
	    static Boolean printHeader = True;
	    int numJobs, j;
	    char host[15];

	    if (nReplies == 0) {
		nJobs = nPermits = 0;
	    }
	    nReplies++;

	    strncpy(host, address, sizeof(host));

	    numJobs = len / sizeof(*data);
	    for (j = 0; j < numJobs; j++)
		if (data[j].pid == 0)
		    break;
	    numJobs = j;
	
	    if (numJobs > 0 && printHeader) {
		printf("HOST       USER            JOB   PID  PORT FROM       START    TIME COMMAND\n");
		printHeader = False;
	    }

	    for (j = 0; j < numJobs; j++) {
		struct passwd *pw;
		struct sockaddr_in from;
		time_t startTime = time((time_t *)0) - data[j].time;
		char *start = ctime(&startTime);
		char user[9];
		char runtime[10];

		start[10] = '\0';
		start[16] = '\0';

		pw = getpwuid(data[j].uid);

		if (pw) {
		    strncpy(user, pw->pw_name, sizeof(user));
		} else {
		    sprintf(user, "%d", data[j].uid);
		}

		if (data[j].time >= 100 * 60) {
		    sprintf(runtime, "%.1fH",
				(double)data[j].time/(60 * 60));
		} else {
		    sprintf(runtime, "%u:%02u",
				data[j].time/60, data[j].time%60);
		}

		from.sin_family = AF_INET;
		from.sin_port = data[j].from_port;
		from.sin_addr.s_addr = data[j].from_addr;

		if (data[j].pid == -1) {
		    nPermits++;
		    printf("%-10.10s %-8.8s %10d             %-10.10s  %s\n",
			    host, user,
			    data[j].id,
			    getaddress(&from),
			    data[j].command);
		} else {
		    nJobs++;
		    printf("%-10.10s %-8.8s %10d %5d %5u %-10.10s %-6s %6s %s\n",
			    host, user,
			    data[j].id,
			    data[j].pid,
			    ntohs(data[j].from_port),
			    getaddress(&from),
			    (data[j].time > 24 * 60 * 60) ?
				start + 4 : start + 11,
			    runtime,
			    data[j].command);
		}
	    }

	    fflush (stdout);
	}
    } else {
	printf ("%5d agent(s) responded, %d job(s) running, %d permit(s) issued\n",
		nReplies, nJobs, nPermits);
    }

    return (False);
}

/*ARGSUSED*/
Boolean
StatsResponse(from, len, data)
    struct sockaddr_in	*from;
    int	    	  	len;
    Job_Stats  		*data;
{
    static Job_Stats totals;

    if (from) {
	char *address = getaddress(from);

	if (len != sizeof(*data)) {
	    fprintf (stderr, "wrong length length reply from %s (%d bytes)\n",
		     address, len);
	    fflush (stderr); 
	} else {
	    static Boolean printHeader = True;
	    time_t now = time((time_t *)0),
		   since = data->start;
	    char *start = ctime(&since);

	    start[10] = '\0';
	    start[16] = '\0';

	    if (nReplies == 0) {
		memset(&totals, 0, sizeof(totals));
	    }
	    nReplies++;

	    if (printHeader) {
		printf("HOST       START   AVAIL   JOBS SIGNALS EVICTS       USER     SYSTEM       REAL\n");
		printHeader = False;
	    }
#define TIME_FORMAT(seconds) \
	    (seconds)/(60 * 60) , \
	    ((seconds)%(60 * 60))/60, \
	    ((seconds)%(60 * 60))%60

	    printf("%-10.10s %-6s %5.1lf%% %6d  %6d %6d %4d:%02d:%02d %4d:%02d:%02d %4d:%02d:%02d\n",
		   address,
		   (now - data->start > 24 * 60 * 60) ? start + 4 : start + 11,
		   data->availChecks ? data->avail * 100.0/data->availChecks : 0.0,
		   data->jobs,
		   data->signals,
		   data->evictions,
		   TIME_FORMAT(data->userTime),
		   TIME_FORMAT(data->sysTime),
		   TIME_FORMAT(data->realTime));
	    fflush (stdout);

	    /*
	     * Track totals
	     */
	    totals.avail += data->avail;
	    totals.availChecks += data->availChecks;
	    totals.jobs += data->jobs;
	    totals.signals += data->signals;
	    totals.evictions += data->evictions;
	    totals.userTime += data->userTime;
	    totals.sysTime += data->sysTime;
	    totals.realTime += data->realTime;
	}
    } else {
	printf("%5d agent(s)    %5.1lf%% %6d  %6d %6d %4d:%02d:%02d %4d:%02d:%02d %4d:%02d:%02d\n",
	       nReplies,
	       totals.availChecks ? totals.avail * 100.0/totals.availChecks : 0.0,
	       totals.jobs,
	       totals.signals,
	       totals.evictions,
	       TIME_FORMAT(totals.userTime),
	       TIME_FORMAT(totals.sysTime),
	       TIME_FORMAT(totals.realTime));
    }

    return (False);
}

char
getanswer()
{
    int answer, ch;

    answer = getchar();

    ch = answer;
    while (ch != EOF && ch != '\n') {
	ch = getchar ();
    }

    return answer;
}

void
Usage (name)
    char *name;
{
    fprintf (stderr, "Usage: %s [<action>] [-all | ALL | -net # | <host> | <network>] ...\n", name);
    fprintf (stderr, "\t-ping		Ping daemon [default]\n");
    fprintf (stderr, "\t-restart	Restart daemon\n");
    fprintf (stderr, "\t-abort		Terminate daemon\n");
    fprintf (stderr, "\t-elect		Initiate master election\n");
    fprintf (stderr, "\t-debug [r|c|n]  Set debugging [customs, RPC, none, all default]\n");
    fprintf (stderr, "\t-kill [#] 	Send signal to imported jobs [SIGTERM default]\n");
    fprintf (stderr, "\t-jobs 		Print list of imported jobs\n");
    fprintf (stderr, "\t-stats 		Print job statistics\n");
    fprintf (stderr, "\t-setstats 	Reset job statistics\n");
    fprintf (stderr, "\t-allow <addr> 	Add address to access list\n");
    fprintf (stderr, "\t-deny <addr> 	Remove address from access list\n");
    fprintf (stderr, "\t-v[ersion]	Print customs version\n");
    fprintf (stderr, "\t-h[elp]		Print this message\n");
    fprintf (stderr, "\t-y[es]		Skip confirmations\n");
    fprintf (stderr, "\t-q[uiet]	Suppress non-error messages\n");
    fprintf (stderr, "\t-port #		Set port number or service [%d/%s default]\n", DEF_CUSTOMS_UDP_PORT, CUSTOMS_SERVICE_NAME);
    fprintf (stderr, "\t-timeout <secs>	Set timeout [%d.%06d secs default]\n",
				CUSTOMS_RETRY, CUSTOMS_URETRY);
    fprintf (stderr, "\t-retry <times>	Set RPC retries [%d default]\n",
				CUSTOMS_NRETRY);
    fprintf (stderr, "\t-net #		Broadcast to customs network #\n");
    fprintf (stderr, "\t-all or ALL	Broadcast to all hosts\n");
    fprintf (stderr, "\t		default host is localhost\n");
}

main(argc, argv)
    int	    argc;
    char    **argv;
{
    int	    	  	sock;
    struct timeval	timeout;
    int			retry;
    struct sockaddr_in	sin;
    Rpc_Proc	  	proc;
    char    	  	*cp;
    int	    	  	debug;
    Kill_Data		killdata;
    struct servent  	*sep;
    Boolean		gotaddr = False;
    Boolean		force = False;
    Boolean		quiet = False;
    Boolean		timeoutChanged = True;
    Boolean		broadcast;
    char		*address;
    int			status = 1; /* exit with 1 if timeout, 0 otherwise */
    Rpc_Long		netToken = -1;

    /*
     * Try using a reserved socket to allow priviledged operations if
     * running as root.
     */
    sock = Rpc_UdpCreate(False, 0);
    if (sock < 0) {
	perror("could not create udp socket");
	exit(1);
    }
    (void)setuid(getuid());

    sep = getservbyname (CUSTOMS_SERVICE_NAME, "udp");
    if (sep == NULL) {
	fprintf(stderr, "%s/udp unknown\n", CUSTOMS_SERVICE_NAME);
	fprintf(stderr, "trying default port %d\n", DEF_CUSTOMS_UDP_PORT);
    }
    timeout.tv_sec = CUSTOMS_RETRY;
    timeout.tv_usec = CUSTOMS_URETRY;
    retry = CUSTOMS_NRETRY;
    timeoutChanged = True;
    sin.sin_family = AF_INET;
    sin.sin_port = sep ? sep->s_port : htons(DEF_CUSTOMS_UDP_PORT);
    
    cp = strrchr(argv[0], '/');
    if (cp == (char *)NULL) {
	cp = argv[0];
    } else {
	cp += 1;
    }
    if (strcmp (cp, "restart") == 0) {
	proc = (Rpc_Proc)CUSTOMS_RESTART;
    } else if (strcmp (cp, "abort") == 0) {
	proc = (Rpc_Proc)CUSTOMS_ABORT;
    } else {
	proc = (Rpc_Proc)CUSTOMS_PING;
    }

    while (--argc > 0) {
	int argLen, resultLen;
	Rpc_Opaque rpcArg, rpcResult;
	Boolean (*rpcResponse)();
	Boolean doit = False;
	Rpc_Stat rstat;
	Version_Data version;
	Job_Info jobinfo[MAX_INFO_SIZE/sizeof(Job_Info)];
	Job_Stats jobstats;
	Rpc_ULong addrArg;		/* -allow/-deny argument */

	argv++;
	if (strcmp(*argv, "-y") == 0 ||
	    strcmp(*argv, "-yes") == 0) {
	    force = True;
	} else if (strcmp(*argv, "-q") == 0 ||
	           strcmp(*argv, "-quiet") == 0) {
	    quiet = True;
	} else if (strcmp(*argv, "-port") == 0) {
	    if (argc == 1) {
		Usage(cp);
		exit(2);
	    } else {
		short port;
		argc--; argv++;
		port = htons(atoi(*argv));

		if (port == 0) {
		    sep = getservbyname (*argv, "udp");
		    if (sep == NULL) {
			fprintf(stderr, "%s: unknown service\n", *argv);
			exit(1);
		    }
		    port = sep->s_port;
		}
		sin.sin_port = port;
	    }
	} else if (strcmp(*argv, "-net") == 0) {
	    if (argc == 1) {
		Usage(cp);
		exit(2);
	    } else {
		static char netName[20];

		argc--; argv++;
		netToken = atoi(*argv);
		sprintf(netName, "net %d", netToken);

		sin.sin_addr.s_addr = htonl(INADDR_ANY);
		gotaddr = True;
		broadcast = True;
		address = netName;
		doit = True;
	    }
	} else if (strcmp(*argv, "-timeout") == 0) {
	    if (argc == 1 || !isdigit(**(argv + 1))) {
		Usage(cp);
		exit(2);
	    } else {
		double to;
		argc--; argv++;
		to = atof(*argv);
		timeout.tv_sec = to;
		timeout.tv_usec = (to - timeout.tv_sec) * 1000000;
		timeoutChanged = True;
	    }
	} else if (strcmp(*argv, "-retry") == 0) {
	    if (argc == 1 || !isdigit(**(argv + 1))) {
		Usage(cp);
		exit(2);
	    } else {
		argc--; argv++;
		retry = atoi(*argv);
		timeoutChanged = True;
	    }
	} else if (strcmp(*argv, "-restart") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_RESTART;
	} else if (strcmp (*argv, "-abort") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_ABORT;
	} else if (strcmp(*argv, "-ping") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_PING;
	} else if (strcmp(*argv, "-all") == 0 ||
	           strcmp(*argv, "ALL") == 0) {
	    netToken = -1;
	    sin.sin_addr.s_addr = htonl(INADDR_ANY);
	    gotaddr = True;
	    broadcast = True;
	    address = "all";
	    doit = True;
	} else if (strcmp(*argv, "-elect") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_ELECT;
	} else if (strcmp(*argv, "-debug") == 0) {
	    proc = CUSTOMS_DEBUG;
	    
	    if (argc == 1 || !strchr("rcn", **(argv + 1))) {
		debug = DEBUG_RPC | DEBUG_CUSTOMS;
	    } else {
		argc--, argv++;
		debug = 0;
		while (**argv != '\0') {
		    if (**argv == 'r') {
			debug |= DEBUG_RPC;
		    } else if (**argv == 'c') {
			debug |= DEBUG_CUSTOMS;
		    } else if (**argv == 'n') {
			debug = 0;
		    } else {
			Usage(cp);
			exit(2);
		    }
		    (*argv)++;
		}
	    }
	} else if (strcmp(*argv, "-allow") == 0 ||
		   strcmp(*argv, "-deny") == 0) {
	    proc = ((*argv)[1] == 'a') ? CUSTOMS_ALLOW : CUSTOMS_DENY;
	    
	    if (argc == 1) {
		Usage(cp);
		exit(2);
	    } else {
		argc--; argv++;

		addrArg = inet_addr(*argv);
		if ((int)addrArg == -1) {
		    Usage(cp);
		    exit(2);
		}
	    }
	} else if (strcmp(*argv, "-kill") == 0) {
	    proc = CUSTOMS_KILL;
	    
	    if (argc > 1) {
		killdata.signal = Signal_FromName(argv[1]);
		if (killdata.signal != SIGNAL_INVAL) {
		    argc--, argv++;
		} else if (isdigit(*argv[1])) {
		    argc--, argv++;
		    killdata.signal = Signal_FromHost(atoi(*argv));
		} else {
		    killdata.signal = SIGNAL_TERM;
		}
	    } else {
		killdata.signal = SIGNAL_TERM;
	    }
	} else if (strcmp(*argv, "-jobs") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_JOBS;
	} else if (strcmp(*argv, "-stats") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_STATS;
	} else if (strcmp(*argv, "-setstats") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_SETSTATS;
	} else if (strcmp(*argv, "-v") == 0 ||
	           strcmp(*argv, "-version") == 0) {
	    proc = (Rpc_Proc)CUSTOMS_VERSION;
	} else if (strcmp(*argv, "-h") == 0 ||
	           strcmp(*argv, "-help") == 0) {
	    Usage(cp);
	    gotaddr = True;
	    continue;
	} else if (**argv == '-') {
	    fprintf(stderr, "Unknown switch: %s\n", *argv);
	    Usage(cp);
	    exit(2);
	} else {
	    struct hostent *he;
	    struct netent *ne;

	    /*
	     * Make sense of the address supplied.
	     */
	    /*
	     * Sometimes network names are also host names.  Give preference
	     * to hostnames.
	     */
	    if (he = gethostbyname(*argv)) {
		/*
		 * A host domain address.
		 */
#ifdef DEBUG
		fprintf(stderr, "HOST name %s\n", *argv);
#endif
		memcpy (&sin.sin_addr, he->h_addr, he->h_length);
		doit = True;
		broadcast = False;
		address = *argv;
	    } else if (ne = getnetbyname(*argv)) {
	        /*
	         * A network name.
	         */
#ifdef DEBUG
		fprintf(stderr, "NETWORK name %s\n", *argv);
#endif
		sin.sin_addr.s_addr = ne->n_net;
		while (sin.sin_addr.s_addr &&
		       !(sin.sin_addr.s_addr & 0xff000000))
			sin.sin_addr.s_addr <<= 8;
		sin.sin_addr.s_addr = htonl(sin.sin_addr.s_addr);

		doit = True;
	        broadcast = True;
		address = *argv;
	    } else if ((sin.sin_addr.s_addr = inet_addr(*argv)) == -1) {
		/*
		 * Unknown, non-numerical address.
		 */
		fprintf(stderr, "%s: unknown host\n", *argv);
		doit = False;
	    } else if (he = gethostbyaddr((char *)&sin.sin_addr,
					 sizeof(sin.sin_addr),
					 AF_INET)) {
		/*
		 * A host IP address.
		 */
#ifdef DEBUG
		fprintf(stderr, "HOST address %s\n", *argv);
#endif
		doit = True;
		broadcast = False;
		address = he->h_name;
	    } else if ((sin.sin_addr.s_addr & 0x000000ff) == 0) {
		/*
		 * Network IP address.
		 */
#ifdef DEBUG
		fprintf(stderr, "NETWORK address %s\n", *argv);
#endif
		doit = True;
		broadcast = True;
		address = InetNtoA(sin.sin_addr);
	    } else {
		/*
		 * Host IP address.
		 */
#ifdef DEBUG
		fprintf(stderr, "IP address %s\n", *argv);
#endif
		doit = True;
		broadcast = False;
		address = InetNtoA(sin.sin_addr);
	    }
	    gotaddr = True;
	}
    
	if (!doit)
	    continue;	/* no host, continue option scanning */

    dorpc:
	/*
	 * Defaults for most of the calls.
	 */
	argLen = resultLen = 0;
	rpcArg = rpcResult = (Rpc_Opaque)0;
	rpcResponse = DefaultResponse;

	/*
	 * If a -net option was specified pass its value to the customs
	 * so it can decide whether to respond or not.
	 * XXX: Works only for RPCs that take no other args
	 */
	if (broadcast && netToken != -1) {
	    argLen = sizeof(netToken);
	    rpcArg = (Rpc_Opaque)&netToken;
	}

	if (!quiet && timeoutChanged) {
	    printf("timeout = %d.%06d secs, %d retries\n",
	           timeout.tv_sec, timeout.tv_usec, retry);
	    timeoutChanged = False;
	}

	/*
	 * A host or -all was specified: take action as specified by last
	 * option.
	 */
	switch ((Customs_Proc)proc) {
	    case CUSTOMS_PING:
		if (!quiet)
		    printf ("Pinging %s\n", address);
		rpcResponse = PingResponse;
		break;
	    case CUSTOMS_ELECT:
		if (!quiet)
		    printf ("Forcing election on %s\n", address);
		break;
	    case CUSTOMS_ABORT:
		if (force) {
		    if (!quiet)
			printf ("Aborting %s\n", address);
		    break;
		}
		printf ("Abort %s? [ny](n) ", address);
		if (getanswer() != 'y') {
		    continue;
		}
		break;
	    case CUSTOMS_RESTART:
		if (force) {
		    if (!quiet)
			printf ("Restarting %s\n", address);
		    break;
		}
		printf ("Restart %s? [yn](y) ", address);
		if (getanswer() == 'n') {
		    continue;
		}
		break;
	    case CUSTOMS_DEBUG:
		argLen = sizeof(debug);
		rpcArg = (Rpc_Opaque)&debug;
		if (!quiet) {
		    printf("Setting debug for %s to ", address);
		    if (debug == (DEBUG_CUSTOMS|DEBUG_RPC)) {
			printf("rpc & customs\n");
		    } else if (debug == DEBUG_CUSTOMS) {
			printf("customs only\n");
		    } else if (debug == DEBUG_RPC) {
			printf("rpc only\n");
		    } else {
			printf("nothing\n");
		    }
		}
		break;
	    case CUSTOMS_ALLOW:
	    case CUSTOMS_DENY:
		argLen = sizeof(addrArg);
		rpcArg = (Rpc_Opaque)&addrArg;
		if (!quiet) {
		    printf("Changing access list on %s\n", address);
		}
		break;
	    case CUSTOMS_KILL:
		argLen = sizeof(killdata);
		rpcArg = (Rpc_Opaque)&killdata;
	        killdata.id = 0;	/* all jobs */
		if (force) {
		    if (!quiet)
			printf("Sending %s signal to %s\n",
			       Signal_ToName(killdata.signal), address);
		    break;
		}
		printf ("Send %s signal to %s? [ny](n) ",
			       Signal_ToName(killdata.signal), address);
		if (getanswer() != 'y') {
		    continue;
		}
		break;
	    case CUSTOMS_VERSION:
		if (!quiet)
		    printf ("Getting version from %s\n", address);
		rpcResponse = VersionResponse;
		resultLen = sizeof(version);
		rpcResult = (Rpc_Opaque)&version;
		break;
	    case CUSTOMS_JOBS:
		if (!quiet)
		    printf ("Getting job list from %s\n", address);
		rpcResponse = JobsResponse;
		resultLen = sizeof(jobinfo);
		rpcResult = (Rpc_Opaque)jobinfo;
		break;
	    case CUSTOMS_STATS:
		if (!quiet)
		    printf ("Getting job statistics from %s\n", address);
		rpcResponse = StatsResponse;
		resultLen = sizeof(jobstats);
		rpcResult = (Rpc_Opaque)&jobstats;
		break;
	    case CUSTOMS_SETSTATS:
		if (!quiet)
		    printf ("Resetting job statistics on %s\n", address);
		break;
	}

	nReplies = 0;
	if (broadcast) {
#ifdef DEBUG
	    fprintf(stderr, "BROADCAST 0x%08lx\n", ntohl(sin.sin_addr.s_addr));
#endif
	    rstat = Rpc_Broadcast (sock, &sin, proc, argLen, rpcArg,
				      resultLen, rpcResult,
				      retry, &timeout,
				      rpcResponse, (Rpc_Opaque)0);
	} else {
#ifdef DEBUG
	    fprintf(stderr, "CALLING 0x%08lx\n", ntohl(sin.sin_addr.s_addr));
#endif
	    rstat = Rpc_Call (sock, &sin, proc, argLen, rpcArg,
				      resultLen, rpcResult,
				      retry, &timeout);
	    if (rstat == RPC_SUCCESS &&
		rpcResponse != DefaultResponse)
		(void)(*rpcResponse)(&sin, resultLen, rpcResult);
	}

	if (rstat != RPC_SUCCESS) {
	    fprintf(stderr, "%s: %s\n", address, Rpc_ErrorMessage(rstat));
	} else {
	    if (broadcast) {
		/*
		 * Call the reply handler one more time to print summary
		 */
		(void)(*rpcResponse)((struct sockkaddr_in *)0,
				     0, (Rpc_Opaque)0);
		fflush (stdout);
	    }
	    status = 0;
	}
    }

    if (!gotaddr) {
	/*
	 * No host specified.  Assume local host.
	 */
	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	gotaddr = True;
	broadcast = False;
	address = "localhost";
	goto dorpc;
    }

    /*
     * Return 0 if at least one host replied.
     */
    exit(status);
}
