/*
 * redir-helper.c - connect sockets back to mpiexec for PBSPro environments
 * that do not pay attention to MPIEXEC_STD*.
 *
 * $Id$
 *
 * Copyright (C) 2006 Pete Wyckoff <pw@osc.edu>
 *
 * Distributed under the GNU Public License Version 2 or later (See LICENSE)
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <stdarg.h>

static const char *progname;

/*
 * Strip dir part.
 */
static const char *set_progname(const char *argv0)
{
    const char *cp, *pname;

    for (cp=pname=argv0; *cp; cp++)
	if (*cp == '/')
	    pname = cp+1;
    return pname;
}

/*
 * Error, fatal.
 */
static void error(const char *fmt, ...)
{
    va_list ap;

    fprintf(stderr, "%s: Error: ", progname);
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, ".\n");
    exit(1);
}

/*
 * Error, fatal, with the errno message.
 */
static void error_errno(const char *fmt, ...)
{
    va_list ap;

    fprintf(stderr, "%s: Error: ", progname);
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, ": %s.\n", strerror(errno));
    exit(1);
}

int main(int argc, char *argv[])
{
    const struct {
	int fd;
	const char *env;
    } fds[] = {
	{ 0, "MPIEXEC_STDIN_PORT" },
	{ 1, "MPIEXEC_STDOUT_PORT" },
	{ 2, "MPIEXEC_STDERR_PORT" },
    };
    const char *const host_env = "MPIEXEC_HOST";
    char **nargv;
    int i, j, s, port;
    const char *env, *host_name;
    char *cq;
    struct sockaddr_in sin;
    socklen_t len = sizeof(sin);
    struct hostent *hp = NULL;

    progname = set_progname(argv[0]);

    /*
     * Look up the host first to check for errant usage.
     */
    host_name = getenv(host_env);
    if (!host_name)
	error("no environment variable \"%s\".\n"
	      "This program is not usually run by hand", host_env);
    hp = gethostbyname(host_name);
    if (!hp)
	error("could not resolve \"%s\"", host_name);

    if (argc < 2)
	error("need at least one program argument");

    /*
     * If the env var is found, connect the appropriate fd to it.
     */
    for (i=0; i<3; i++) {
	env = getenv(fds[i].env);
	if (!env)
	    continue;
	port = strtoul(env, &cq, 10);
	if (cq == env || *cq != '\0')
	    error("non-numeric port in environment variable \"%s\"",
	          fds[i].env);
	s = socket(AF_INET, SOCK_STREAM, 0);
	if (!s)
	    error_errno("socket");
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = hp->h_addrtype;
	memcpy(&sin.sin_addr, hp->h_addr_list[0], hp->h_length);
	sin.sin_port = htons(port);
	for (j=0; j<3; j++) {
	    if (connect(s, (struct sockaddr *) &sin, len) == 0)
		break;
	    if (errno == EINTR || errno == EADDRINUSE || errno == ETIMEDOUT
	     || errno == ECONNREFUSED) {
		sleep(1);
		if (j < 2)
		    continue;
	    }
	    error("connect %s to %s port %d", fds[i].env, host_name, port);
	}
	(void) close(i);
	if (dup2(s, i) < 0)
	    error_errno("dup2");
    }

    /*
     * Exec the real code.  The first element better be a real path.  We
     * chop off the directory part to get argv[0] for the execed code.
     */
    nargv = malloc(argc * sizeof(*nargv));
    if (!nargv)
    	error("malloc");
    nargv[0] = strdup(set_progname(argv[1]));
    for (i=2; i<argc; i++)
	nargv[i-1] = argv[i];
    nargv[argc-1] = NULL;
    execvp(argv[1], nargv);
    error("execvp");
    return 1;
}



syntax highlighted by Code2HTML, v. 0.9.1