/*
 * Copyright (C) 1992 by Gustaf Neumann, Stefan Nusser
 *
 *      Wirtschaftsuniversitaet Wien,
 *      Abteilung fuer Wirtschaftsinformatik
 *      Augasse 2-6,
 *      A-1090 Vienna, Austria
 *      neumann@wu-wien.ac.at, nusser@wu-wien.ac.at
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both that
 * copyright notice and this permission notice appear in all supporting
 * documentation.  This software is provided "as is" without expressed or
 * implied warranty.
 *
 * Date: Mon, Apr 13 1992
 * Author: Stefan Nusser, Gustaf Neumann
 * Version: 0.97
 */

/*
 *   WAFE.C 
 *
 *   This file contains the main function as well as some
 *   utility procedures for set up communication in frontend mode
 */

#define MAIN

#include <ctype.h>
#include "wafe.h"

#ifndef STREAMS
# define SOCKET_CONNECTION
# include <sys/socket.h>
# ifdef SOCKET_CONNECTION
#  include <netinet/in.h>
#  include <sys/un.h>
#  include <netdb.h>
# endif
#else
# include <sys/stream.h>
# include <stropts.h>
#endif

#ifdef RDD
# include <rdd.h>
#endif

#include "version.h"

#define wafePerror(msg) {perror(msg); wafeExit(-1);}
/*****************************************************************************
 *    FUNCTION: iSocketPair
 *            Use socketpair on BSD or ioctl on Sys 5 or pipes
 *                                                                          
 *    Arguments: array of file descriptors
 * 
 *               parentRd childWr childRd parentWr
 *
 *    Returns:  0 if success, otherwise -1                         
 *                                                                          
 *    Used by:  main
 *****************************************************************************/
#ifdef PIPES
static int
iSocketPair(fd, onlyOne)
int fd[4],onlyOne;
    {
    if (pipe(&fd[0])<0) return(-1);
    if (onlyOne) return(0);
    if (pipe(&fd[2])<0) return(-1);
    return(0);
    }
#else
#ifdef STREAMS
#define SPX_DEVICE "/dev/spx"
static int
iSocketPair(fd, onlyOne)
int      fd[4], onlyOne;
    {
    struct strfdinsert  ins;
    queue_t                  *pointer;

    if ((fd[0] = open(SPX_DEVICE, O_RDWR)) < 0)
	return(-1);

    if ((fd[1] = open(SPX_DEVICE, O_RDWR)) < 0)
	{
	close(fd[0]);
	return(-1);
	}

    ins.ctlbuf.buf = (char *) &pointer;
    ins.ctlbuf.maxlen = sizeof(queue_t *);		
    ins.ctlbuf.len = sizeof(queue_t *);		

    ins.databuf.buf = (char *) 0;
    ins.databuf.maxlen = 0;
    ins.databuf.len = -1;

    ins.fildes = fd[1];
    ins.flags = 0;
    ins.offset = 0;

    if (ioctl(fd[0], I_FDINSERT, (char *) &ins) < 0)
	{
	close(fd[0]);
	close(fd[1]);
	return(-1);
	}

    fd[2]=fd[1];
    fd[3]=fd[0];
    return(0);
    }

#else

static int
iSocketPair(fd, onlyOne)
int      fd[4], onlyOne;
    {
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1)
	return(-1);
    else
	{
	fd[2]=fd[1];
	fd[3]=fd[0];
	return(0);
	} 
    }
#endif
#endif

static char *
checkArg(argc,args, argv, c)
int argc;
int *args;
char ** argv;
char c;
    {
    if (argv[*args][3]) 
        return &argv[*args][3];

    (*args)++;
    if (argc == *args) 
	{
	char buffer[2];
	buffer[0] = c;
	buffer[1] = 0;
        wafeFatal("main", "Option --%s needs an additional argument", buffer);
        }

    return argv[*args];
    }

#ifdef SOCKET_CONNECTION
#define SADDR(x) (struct sockaddr *) &x
static int
unixStreamConnection(socketName)
char *socketName;
    {
    struct sockaddr_un s;
    int servlen, fd, nfd;
    struct sockaddr local;
    int len = sizeof(local);

    DBUG_PRINT("socket",("will use unix domain socket %s",socketName));
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) wafePerror("socket");
    bzero((char*)&s, sizeof(s));
    s.sun_family = AF_UNIX;
    strcpy(s.sun_path,socketName);
    servlen = strlen(s.sun_path) + 1 + sizeof(s.sun_family);
    if (bind(fd, SADDR(s), servlen) < 0) wafePerror("bind");
    if (listen(fd,1) != 0) 
	{
	unlink(socketName);  
	wafePerror("listen");  
	}
    if ((nfd = accept(fd, &local, &len)) < 0) 
	{
	unlink(socketName);  
	wafePerror("accept");
	}
    unlink(socketName);  /*the socket will exist until the program terminates*/
    close(fd);
    return nfd;
    }

static int
tcpPortConnection(port)
int port;
    {
    struct sockaddr_in s,client;
    int fd, nfd, len = sizeof(client);
    DBUG_PRINT("socket",("accepting connections on port %d",port));

    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) wafePerror("socket");
    bzero((char*)&s, sizeof(s));
    s.sin_family      = AF_INET;
    s.sin_addr.s_addr = htonl(INADDR_ANY);
    s.sin_port        = htons(port);
    if (bind(fd, SADDR(s), sizeof(s)) < 0) wafePerror("bind");
    if (listen(fd,1) != 0) wafePerror("listen");  
    if ((nfd = accept(fd, SADDR(client), &len)) < 0)  wafePerror("accept");
    close(fd);
    return nfd;
    }

static int
tcpHostPortConnection(host,port)
char *host;
int port;
    {
    struct sockaddr_in s,client;
    int fd0, fd, nfd, len = sizeof(client);
    struct hostent* h = gethostbyname(host);
    char portString[INT_AS_STRING];
    
    DBUG_PRINT("socket",("trying connection to host %s port %d",host,port));
    h = gethostbyname(host);
    if (h == NULL || h->h_addrtype != AF_INET) wafePerror("gethostbyname");
    
    /* connect to server on specified host and port */
    if ((fd0 = socket(AF_INET, SOCK_STREAM, 0)) < 0) wafePerror("socket");
    bzero((char*)&s, sizeof(s));
    s.sin_family = AF_INET;
    memcpy((char *)&s.sin_addr,(char *)h->h_addr,h->h_length);
    s.sin_port = htons(port);

    DBUG_PRINT("socket",("trying to connect to %s:%d",host,port));
    if (connect(fd0, SADDR(s), sizeof(s)) < 0) wafePerror("connect");

    /* create new temporary port and play server on this port */    
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) wafePerror("socket");
    bzero((char*)&s, sizeof(s));
    s.sin_family      = AF_INET;
    s.sin_addr.s_addr = htonl(INADDR_ANY);
    s.sin_port        = 0;
    if (bind(fd, SADDR(s), sizeof(s)) < 0) wafePerror("bind");
    if (getsockname(fd, SADDR(s), &len)<0) wafePerror("getsockname");
    DBUG_PRINT("socket",("listening on new port %d",ntohs(s.sin_port)));
    if (listen(fd,1) != 0) wafePerror("listen");  

    /* tell partner the new port in form of a string */    
    sprintf(portString, "%d", ntohs(s.sin_port));
    write(fd0, portString, strlen(portString)+1);
    close(fd0);
    DBUG_PRINT("socket",("new port address sent, waiting for connection..."));

    /* accept connections on the new port */    
    if ((nfd = accept(fd, SADDR(client), &len)) < 0) wafePerror("accept");
    close(fd);
    return nfd;
    }
#endif

/*****************************************************************************
 *    FUNCTION:  MAIN                                
 *               Command Line parsing, starts Xt Application, registers     
 *               addditional tcl-commands  and according                   
 *               to mode establishes the socket connection or reads in a   
 *               file or reads from stdin.                                 
 *                                                                         
 *    Arguments: argc and argv                                             
 *    Returns:   nothing                                                   
 *                                                                         
 *****************************************************************************/

void
main(argc, argv, env)
int    argc;
char **argv;
char **env;
    {
    int        i;
    Boolean    opt_p = False;
    Boolean    forkChild  = True;
    Boolean    promptMode = True;
    Boolean    inputMode  = False;
    Boolean    directMode = False;
    Boolean    explicitClassName = False;
    int        args = 0; 
    int        cargc = 0;
    char     **cargv;    
    char     **cargvCount;
    char      *charPtr;
    char      *execString = NULL;
    char      *applicationName = NULL;
#ifdef SOCKET_CONNECTION
    int        port = 0;
    char      *hostName = NULL;
    char      *socketName = NULL;
#endif
    int        sp[4];  
    int        ep[4];  
    int        cp[4];

    DBUG_ENTER ("main");
    DBUG_PROCESS (argv[0]);

#ifdef DEBUG_MALLOC
    mal_debug(1);
    mal_trace(0);
#endif

    cargvCount = cargv = (char **)XtMalloc((argc+1) * sizeof(char *));
    
    /* In frontend mode, the class will be derived from the application's 
     * name, in file mode from the filename and in direct  mode it is 
     * defined to be Wafe
     */
    wafeAppClass = XtNewString(argv[0]);

    /*
     * Argument parsing loop - build new argument list without wafe's 
     * own args
     */ 

    while (args < argc)
	{
	char *currentArg = argv[args];
	
	if ((currentArg[0] == '-') 
	    && (currentArg[1] == '-') 
	    && (strlen(currentArg) > 2))
	    {
	    char flagChar = currentArg[2];

	    
	    if (flagChar == 'D') /***  Debug - Mode  ***/
                {                             
                DBUG_PUSH (&(currentArg[3]));  
	        }                             
	    else
                             
	    if (flagChar == 'n')  /***  Prompt - Mode  ***/ 
                promptMode = False;               
	    else
                             
            if (flagChar == 'v')  /***  Version  ***/ 
	        {                              
		fprintf(stderr, "\nThis is Wafe, version %s\n\n", VERSION);
                fprintf(stderr, "Copyright (C) 1992 by Gustaf Neumann, Stefan Nusser\n");
                fprintf(stderr, "University of Economics and Business Administration\n"); 
                fprintf(stderr, "Vienna, Austria\n\n");
                exit(0);
                }
	    else
                             
	    if (flagChar == 'i')   /***  Input - Mode  ***/
                inputMode  = True;            
	    else
                             
	    if (flagChar == 'e')   /***  Execute - Mode  ***/
		execString = checkArg(argc,&args, argv, flagChar);
	    else
                             
            if (flagChar == 'd')   /***  Direct - Mode  ***/
	        {                           
                directMode = True;
		forkChild = False;
	        }
	    else 
                             
            if (flagChar == 'c')   /*** Change Prompt Character  ***/
	        {
		extern char wafePromptChar;
		wafePromptChar = currentArg[3];
	        }
	    else

	    if (flagChar == 'p')   /*** Program Name ***/
	        {
		opt_p = True;
		charPtr = checkArg(argc,&args, argv, flagChar);

		if (!explicitClassName) 
		    {
		    if (wafeAppClass) XtFree(wafeAppClass);
		    wafeAppClass = XtNewString(charPtr);
		    }
		if (applicationName) XtFree(applicationName);
                applicationName = XtNewString(charPtr);
	        }
            else

	    if (flagChar == 'T')   /*** sockets for tunnel ***/
	        {
		charPtr = checkArg(argc,&args, argv, flagChar);
		if (!(sscanf(charPtr,"%d/%d",&wafeExtraCom,&wafeExtraClient)))
		    wafeFatal("main", 
			      "Option --t needs fileno(wafeSock)/fileno(clientSock)", NULL);
                }
            else

	    if (flagChar == 'C')   /*** Class ***/
	        {
		charPtr = checkArg(argc,&args, argv, flagChar);
		if (wafeAppClass) XtFree(wafeAppClass);
                wafeAppClass = XtNewString(charPtr);
		explicitClassName = True;
                }
            else

#ifdef SOCKET_CONNECTION
	    if (flagChar == 'S')   /*** Socket connection ***/
	        {
		char *colon;
		charPtr = checkArg(argc,&args, argv, flagChar);
		if ((colon = strchr(charPtr,':'))) 
                    {
                    /* we have hostName:port */
                    *colon++ = '\0';
                    hostName = XtNewString(charPtr);
                    port = atoi(colon);
                    }
		else
		if ( *charPtr == '/' || *charPtr == '.')
                    /* it looks like a filename */
		    socketName = XtNewString(charPtr);
		else 
		    /* must be a number */
		    port = atoi(charPtr);
		}
            else
#endif
                             
            if (flagChar == 'f')    /***  File - Mode  ***/
                {
                if (currentArg[3] == 'i') /* For convenience: --fi == --f --i*/
		    inputMode = True;

		wafeScriptName = checkArg(argc,&args, argv, flagChar);
		forkChild = False;
		promptMode = False;

		if (!explicitClassName)
		    {
		    if (wafeAppClass) XtFree(wafeAppClass);
		    wafeAppClass = XtNewString(wafeScriptName);
		    }
                }
	    else
		fprintf(stderr, "Wafe(main): Unknown Argument, ignoring!\n");
	    }
	else
	    {
	    *cargvCount = XtNewString(currentArg);
	    cargc++;
	    cargvCount++;
	    }
	args++; 
	}
    *cargvCount = NULL;

#ifdef SOCKET_CONNECTION
    if (port || socketName) 
	{
	int fd = hostName ?
	    tcpHostPortConnection(hostName,port) :
		port ? 
		    tcpPortConnection(port) : 
		    unixStreamConnection(socketName);

	dup2(fd, fileno(stdin));
	dup2(fd, fileno(stdout));
	close(fd);
	}
#endif

    /* Check that everybody has got his args */

    DBUG_PRINT("start", ("Arguments at startup (argc = %d):", argc));
    for (i = 0; argv[i] != NULL; i++)
	DBUG_PRINT("start",("argv[%d] = %s", i, argv[i]));

    DBUG_PRINT("start", ("Xt's + app's args (cargc = %d):", cargc));
    for (i = 0; cargv[i] != NULL; i++)
	DBUG_PRINT("start",("cargv[%d] = %s", i, cargv[i]));

     /*
      * If the program's name contains a path specification cut it off...
      */
    if ((charPtr = strrchr(wafeAppClass, '/')))
	wafeAppClass = ++charPtr;

    /*
     * We will need this information when we execute the application process...
     */
    if (!applicationName) 
	applicationName = XtNewString(wafeAppClass); 
    
    /*
     * If the name contains a dot, which is not allowed in Xt's class names, 
     * substitute it with a dash (keep this in mind, it will possibly affect 
     * the name of the AD file!
     */
    while((charPtr = strrchr(wafeAppClass, '.')))
	*charPtr = '-';
         
    /* Just because argv[0] will appear in the titlebar of the 
     * top level window - if we were in file mode the title would
     * always be wafe (which is probably not desired).
     */
    cargv[0] = XtNewString(wafeAppClass);

    /* 
     *  Now create class: Capitalized X, second letter capitalized, 
     *  rest remains unchanged. Under this name, the app-defaults file
     *  will be searched! 
     */
    if ((*wafeAppClass == 'x') || (*wafeAppClass == 'X'))
	{
	*wafeAppClass = 'X';
	*(wafeAppClass+1) = toupper(*(wafeAppClass+1));
	}
    else
	*wafeAppClass = toupper(*wafeAppClass);




    /* Must initialize here, so that Xt can strip off its arguments 
     * and application gets reduced argument list (frontend mode)
     */
#ifndef PRER6
    wafeTopLevel = XtVaOpenApplication(&wafeAppContext, wafeAppClass, NULL, 0, 
				       &cargc, cargv, NULL, 
				       applicationShellWidgetClass,
				       NULL); 
#else
    wafeTopLevel = XtVaAppInitialize(&wafeAppContext, wafeAppClass, NULL, 0, 
				 &cargc, cargv, NULL, NULL); 
#endif

    MOTIF_EDITRES_HANDLER(wafeTopLevel);

    DBUG_PRINT("start", ("App's args (cargc = %d):", cargc)); 

    for(i = 0; cargv[i] != NULL; i++)
	DBUG_PRINT("start",("cargv[%d] = %s", i, cargv[i])); 

    if (forkChild && !opt_p)
	forkChild = (*applicationName == 'x') || 
	            (*applicationName == 'X');

    /* if "Nothing" was specified, default to interactive operation */
    if (!(forkChild || opt_p || fileMode || 
	  inputMode || directMode || execString))
	{
	directMode = True;
	promptMode = False;
	fprintf(stderr, 
		"\nThis is Wafe %s! (%s Version, Interactive Mode)\n\n", 
		VERSION,
#ifdef ATHENA
		"Athena"
#else
		"OSF/Motif"
#endif
		);
	}


    if (forkChild)     /* running as a frontend */
	{     

	 /*
          *  Main socketpair, will be connected to the client's stdin
          *  and stdout channels.
          */

	if (iSocketPair(sp,0) == -1)
	    wafeFatal("main", "Couldn't create socket for stdin", NULL);
/*
 printf("sp %d %d %d %d\n",sp[0],sp[1],sp[2],sp[3]);
 */

	/*
	 * This pair is used to facilitate an additional communication channel.
	 */

	if (iSocketPair(cp,1) == -1)
	    wafeFatal("main", "Couldn't create socket for tunnel", NULL);

/*
 printf("cp %d %d %d %d\n",cp[0],cp[1],cp[2],cp[3]);
 */

	  wafeExtraClient = cp[1];  /* Communication endpoint for client  */
	  wafeExtraCom    = cp[0];  /* Communication endpoint for wafe    */

/*          close(cp[2]);
          close(cp[3]);
*/

	 /*
          *  Used to catch client's error messages - wafe will never write on
          *  this channel!
          */

	if (iSocketPair(ep,1) == -1)
	    wafeFatal("main", "Couldn't create socket for stderr", NULL);

/*
   printf("ep %d %d %d %d\n",ep[0],ep[1],ep[2],ep[3]);
 */
	wafeClientPid = fork();
    
	if (!wafeClientPid)     /* Child-process  */
	    {

	    dup2(sp[1], fileno(stdout));
	    dup2(sp[2], fileno(stdin));
	    dup2(ep[1], fileno(stderr));
/*
	    fprintf(stderr,
		    "closing %d %d %d %d in CHILD\n", 
		    ep[0], ep[1], sp[1], sp[0]);

	    fprintf(stderr,"after dup %d = %d (stdin)\n", 
		    sp[1], fileno(stdin));
	    fprintf(stderr,"after dup %d = %d (stdout)\n", 
		    sp[2], fileno(stdout));
	    fprintf(stderr,"after dup %d = %d (stderr)\n", 
		    ep[1], fileno(stderr));
*/
	    close(ep[0]);              /* Those 2 are for ..          */
	    close(ep[1]);              /* getting stderr-messages.    */

	    close(sp[1]);              /* For regular communication ..*/
	    close(sp[0]);              /* via stdin and stdout!       */

            setpgrp();

	    cargv[0] = opt_p ? applicationName : applicationName+1;
	    execvp(cargv[0], cargv);   /* execute the client-program  */  
   
	    wafeFatal("main", "Cannot execute <%s>", cargv[0]);
	    }

	if (wafeClientPid == -1)
	    wafeFatal("main", "Couldn't fork", NULL);

/*
          close(sp[1]);
          close(ep[1]);
*/

          /* Those are the four main communication-channels:
           *
           * 1) wafeErrorClient   is the client's diagnostic output,
           * 2) wafeToClient      leads to client's stdin and
           * 3) wafeFromClient    is client's stdout 
           * 4) wafeExtraCom      additional communication channel
           */

	wafeErrorClient = ep[0];
	wafeToClient    = sp[3];
	wafeFromClient  = sp[0];
/*
	fprintf(stderr,
		"errorClient=%d, toClient=%d, fromClient=%d, comWafe=%d\n", 
		wafeErrorClient, wafeToClient, wafeFromClient,wafeExtraCom);
 */
	}


     /* Now check for invalid combination of modes */
    if ((directMode) && (fileMode))
	wafeFatal("main","Invalid combination direct-mode and file-mode",
		  NULL);

    /* Now set up interrup handler, initalize wafe packages, set
     * global tcl variables
     */

    wafeInit(cargc, cargv, inputMode, promptMode);

    if (execString)
	(void)wafeEval(wafeInterpreter, execString, "cmdline");

    if (fileMode)
	if (Tcl_EvalFile(wafeInterpreter, wafeScriptName) == TCL_ERROR)
	    wafeFatal("file evaluation", "%s", wafeInterpreter->result);

#ifdef RDD
    rddAppMainLoop(wafeAppContext);
#else
    XtAppMainLoop(wafeAppContext);
#endif

    DBUG_VOID_RETURN;
    }

