/*
 * Written by Jay Laefer (jl57+@andrew.cmu.edu)
 *     and Chuck Silvers (cs4n+@andrew.cmu.edu)
 * Last update: 01/02/96 by Derrick Brashear (shadow+@andrew.cmu.edu)
 *
 * Copyright 1990, 1991, BeakSoft Inc.
 * Copyright 1995 Derrick J Brashear
 * Hesiod support taken from zhm, copyright Massachusetts Institute of
 *  Technology.
 * All Rights Reserved
 * Permission is granted to copy, modify, and use this as long
 * as this notice remains intact.  CMU Sucks.
 */

#include "zstart.h"

void start_zhm();

#ifdef ZEPHYR_USES_HESIOD
char *
strsave (sp)
    const char *sp;
{
    char *ret;

    ret = (char *) malloc(strlen(sp) + 1);
    if (!ret) {
        perror("no mem strdup'ing");
        abort();
    }
    strcpy(ret, sp);
    return ret;
}
#endif

int need_zhm()
{
    int sock, retval = 1;
    struct sockaddr_in sin;
    struct servent *zhm;

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
	perror("socket");
	return 0; /* couldn't open a socket */
    }

    memset((char *)&sin, 0, sizeof (struct sockaddr_in));

    zhm = getservbyname(HM_SVCNAME, "udp");

    sin.sin_family = AF_INET;
    sin.sin_port = zhm ? zhm->s_port : htons(DEFAULT_ZHM_PORT);
    sin.sin_addr.s_addr = htonl(LOCALHOST);

    if (bind(sock, (struct sockaddr *)&sin,
		sizeof (struct sockaddr)) == -1) {
	/* zhm already running */
      if (errno == EADDRNOTAVAIL) {
	fprintf(stderr, "zstart: WARNING: you have no loopback interface\n");
	exit(-1);
      } else if (errno == EADDRINUSE) {
	retval = 0;
      }
    }

    close(sock);
    return retval;
}

main(argc, argv)
int argc;
char **argv;
{
#ifdef CMU_BACKWARD_COMPAT
    char *apath, pipe_path[MAXPATHLEN];
#endif
    int pid, ppgrp, zwgc_pid;
    int zhm_only = 0, make_pipe = 0, filedes[2];
    int local_path = 0;
    extern int optind, opterr;
    int c;

#ifdef CMU_BACKWARD_COMPAT
    while ((c = getopt(argc, argv, "hpl")) != EOF) {
#else
    while ((c = getopt(argc, argv, "hl")) != EOF) {
#endif
	switch (c) {
	case 'h':
	    zhm_only = 1;
	    break;
#ifdef CMU_BACKWARD_COMPAT
	case 'p':
	    make_pipe = 1;
	    break;
#endif
	case 'l':
	    local_path = 1;
	    break;
	default:
#ifdef CMU_BACKWARD_COMPAT
	    fputs("Usage: zstart [-h] [-p] [-l]\n", stderr);
#else
	    fputs("Usage: zstart [-h] [-l]\n", stderr);
#endif
	    exit(-1);
	}
    }

    if (optind != argc) {
#ifdef CMU_BACKWARD_COMPAT
	fputs("Usage: zstart [-h] [-p] [-l]\n", stderr);
#else
	fputs("Usage: zstart [-h] [-l]\n", stderr);
#endif
	exit(-1);
    }

    if (need_zhm()) {
	pid = fork();

	if (pid == 0) {
	    start_zhm(local_path);
	}
	else if (pid < 0) {
	  perror("zstart: cannot fork, aborting:");
	}
	else {
#ifdef _POSIX_VERSION
	  (void)waitpid(-1, (int *)0, 0);
#else
	  (void)wait3 ((union wait *)0, 0, (struct rusage*) 0);
#endif
	}
    }

    if (zhm_only) exit(0);

#ifdef CMU_BACKWARD_COMPAT
    if (!make_pipe) {
#endif
	if (getenv("DISPLAY")) {
		pid = fork();
		if (pid > 0) exit(0);
		if (pid < 0) {
		    perror("zstart: cannot fork, aborting:");
		    exit(-1);
                }
#if defined(_POSIX_VERSION) && !defined(ultrix)
		(void) setpgid(0, tcgetpgrp(1));
#else
		(void) setpgrp(0, getpgrp(getppid()));
#endif
		execl(PREFIX "/bin/zwgc", "zwgc", (char *)NULL);
	} else
	  execl(PREFIX "/bin/zwgc", "zwgc", "-disable", "X", "-ttymode", (char *)NULL);

	/* If the execl() failed */
	perror(PREFIX "/bin/zwgc");
	exit(-1);
#ifdef CMU_BACKWARD_COMPAT
    }

    /* We want a pipescript */
    pid = fork();
    if (pid < 0) {
	perror("fork");
	exit(-1);
    } else if (pid != 0) {
	/* let go */
	exit(0);
    }

    if (pipe(filedes) == -1) {
	perror("pipe");
	exit(-1);
    }

    zwgc_pid = pid = fork();
    if (pid < 0) {
	perror("fork");
	exit(-1);
    } else if (pid == 0) {
	if (dup2(filedes[1], 1) == -1) {
	    perror("dup2");
	    exit(-1);
	}
	(void)close(filedes[0]);
	(void)close(filedes[1]);
	execl(PREFIX "/bin/zwgc", "zwgc", "-disable", 
	      "X", "-ttymode", "-nofork", "-default", "plain", (char *)NULL);
	perror(PREFIX "/bin/zwgc");
	exit(-1);
    }

    /* parent */
    pid = fork();
    if (pid < 0) {
	perror("fork");
	exit(-1);
    } else if (pid > 0) {
	/* parent that waits for pipescript to die */
#ifdef _POSIX_VERSION
	  (void)waitpid(-1, (int *)0, 0);
#else
	  (void)wait3 ((union wait *)0, 0, (struct rusage*) 0);
#endif
	kill(zwgc_pid, SIGTERM);
	exit(0);
    }

    /* create pipescript */
    if (dup2(filedes[0], 0) == -1) {
	perror("dup2");
	exit(-1);
    }
    (void)close(filedes[0]);
    (void)close(filedes[1]);

    if ((apath = getenv("ANDREWDIR")) == NULL) {
	apath = DEFAULT_APATH;
    }

    (void)strcpy(pipe_path, apath);
    (void)strcat(pipe_path, "/bin/pipescript");
    execl(pipe_path, "pipescript", "-d", "-t", "Zephyr", (char *)NULL);

    perror(pipe_path);
    exit(-1);
#endif /* CMU_BACKWARD_COMPAT */
}

void start_zhm(local_path)
int local_path;
{
    int i, j, len, server_count;
    char *temp;
    FILE *server_file;
    char *zhm_args[MAX_ZEPHYR_SERVERS + 2]; /* one each for NULL, "zhm" */
    char *zcluster;
    char **clust_info;
    char prim_serv[MAXHOSTNAMELEN], cur_serv[MAXHOSTNAMELEN];
    char **serv_list = NULL;
    int numserv;
    char hostname[MAXHOSTNAMELEN];

    zhm_args[0] = "zhm";
    zhm_args[MAX_ZEPHYR_SERVERS+1] = NULL;
    
#ifdef ZEPHYR_USES_HESIOD
    numserv = 0;
    prim_serv[0] = '\0';
    
    if (gethostname(hostname, MAXHOSTNAMELEN) < 0) {
      printf("Can't find my hostname?!\n");
      exit(-1);
    }

    if ((clust_info = hes_resolve(hostname, "CLUSTER")) == NULL) {
      zcluster = NULL;
    } else
      for ( ; *clust_info; clust_info++) {
	/* Remove the following check once we have changed over to
	 * new Hesiod format (i.e. ZCLUSTER.sloc lookup, no primary
	 * server
	 */
	if (!strncasecmp("ZEPHYR", *clust_info, 6)) {
	  register char *c;
	  
	  if ((c = strchr(*clust_info, ' ')) == 0) {
	    printf("Hesiod error getting primary server info.\n");
	  } else
	    (void)strcpy(prim_serv, c+1);
	  break;
	}
	if (!strncasecmp("ZCLUSTER", *clust_info, 9)) {
	  register char *c;
	  
	  if ((c = strchr(*clust_info, ' ')) == 0) {
	    printf("Hesiod error getting zcluster info.\n");
	  } else {
	    if ((zcluster = malloc((unsigned)(strlen(c+1)+1)))
		!= NULL) {
	      (void)strcpy(zcluster, c+1);
		} else {
		  printf("Out of memory.\n");
		  exit(-5);
		}
	  }
	  break;
	}
      }
    
    if (zcluster == NULL) {
      if ((zcluster = malloc((unsigned)(strlen("zephyr")+1))) != NULL)
	(void)strcpy(zcluster, "zephyr");
    }
    while ((serv_list = hes_resolve(zcluster, "sloc")) == (char **)NULL) {
      syslog(LOG_ERR, "No servers or no hesiod");
      /* wait a bit, and try again */
      sleep(30);
    }
    clust_info = (char **)malloc(2*sizeof(char *));
    if (prim_serv[0])
      clust_info[numserv++] = prim_serv;
    for (i = 0; serv_list[i]; i++)
      /* copy in non-duplicates */
      /* assume the names returned in the sloc are full domain names */
      if (!prim_serv[0] || strcasecmp(prim_serv, serv_list[i])) {
	clust_info = (char **) realloc(clust_info,
				       (numserv+2) * sizeof(char *));
	clust_info[numserv++] = strsave(serv_list[i]);
      }
    clust_info[numserv] = NULL;
    if (numserv) {
      if (prim_serv && prim_serv[0]) {
	zhm_args[1] = prim_serv;
	for (i = 1; i < MAX_ZEPHYR_SERVERS; i++) {
	  zhm_args[i+1] = clust_info[i];
	}
      } else {
	for (i = 1; i <= MAX_ZEPHYR_SERVERS; i++) {
	  zhm_args[i] = clust_info[i];
	}
	srandom(time((time_t *)0));
	for (i = 1; i < server_count; i++) {
	  j = 1 + (random() % (server_count - i + 1));
	  temp = zhm_args[i];
	  zhm_args[i] = zhm_args[j];
	  zhm_args[j] = temp;
	}
      }    
    }
    if (!numserv) {
#endif

      server_file = fopen(local_path ? LOCAL_SERVER_LIST : SERVER_LIST, "r");
      if (!server_file) {
	fputs("Can't open ", stderr);
	fputs(local_path ? LOCAL_SERVER_LIST : SERVER_LIST, stderr);
	fputc('\n', stderr);
	exit(1);
      }

      for (i = 1; i <= MAX_ZEPHYR_SERVERS; i++) {
	zhm_args[i] = (char *)malloc(MAXHOSTNAMELEN + 1);
	if (!zhm_args[i]) {
	    fputs("Out of memory\n", stderr);
	    exit(1);
	}
	
	if (fgets(zhm_args[i], MAXHOSTNAMELEN, server_file) == NULL)
	{
	  zhm_args[i] = NULL;
	  break;
	}

	zhm_args[i][MAXHOSTNAMELEN] = '\0';		/* paranoia */
	len = strlen(zhm_args[i]);
	if (zhm_args[i][len - 1] == '\n')
	  zhm_args[i][len - 1] = '\0';
      }
      
      fclose(server_file);
      
      if (i == 1) {
	fputs("zstart: No zephyr servers found.\n", stderr);
	exit(1);
      }
      server_count = i - 1;

      /* randomize the order based on number of servers */
      srandom(time((time_t *)0));
      for (i = 1; i < server_count; i++) {
	j = 1 + (random() % (server_count - i + 1));
	temp = zhm_args[i];
	zhm_args[i] = zhm_args[j];
	zhm_args[j] = temp;
      }
#ifdef ZEPHYR_USES_HESIOD
    }
#endif
    
    execv(local_path ? LOCAL_ZHM : PREFIX "/etc/zhm", 
	  zhm_args);
    perror(local_path ? LOCAL_ZHM : PREFIX "/etc/zhm");
    exit(-1);
}
