/*
 * Copyright (c) 1998-2001
 * University of Southern California/Information Sciences Institute.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*
 *  $Id: main.c,v 1.19 2003/02/12 21:56:04 pavlin Exp $
 */
/*
 * Part of this program has been derived from mrouted.
 * The mrouted program is covered by the license in the accompanying file
 * named "LICENSE.mrouted".
 *
 * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
 * Leland Stanford Junior University.
 *
 */


#include "defs.h"
#include <err.h>
#include <getopt.h>
#include <sys/stat.h>

char versionstring[100];
int disable_all_by_default = 0;
int haveterminal = 1;
struct rp_hold *g_rp_hold = NULL;
int mrt_table_id = 0;

char *config_file = _PATH_PIMD_CONF;
int syslog_level = LOG_NOTICE;

extern char todaysversion[];

static int sighandled = 0;
#define GOT_SIGINT      0x01
#define GOT_SIGHUP      0x02
#define GOT_SIGUSR1     0x04
#define GOT_SIGUSR2     0x08
#define GOT_SIGALRM     0x10

#define NHANDLERS       3
static struct ihandler {
    int fd;			/* File descriptor               */
    ihfunc_t func;		/* Function to call with &fd_set */
} ihandlers[NHANDLERS];
static int nhandlers = 0;

static struct debugname {
    char	*name;
    uint32_t	 level;
    size_t	 nchars;
} debugnames[] = {
    {   "dvmrp_detail",	    DEBUG_DVMRP_DETAIL,   5	    },
    {   "dvmrp_prunes",	    DEBUG_DVMRP_PRUNE,    8	    },
    {   "dvmrp_pruning",    DEBUG_DVMRP_PRUNE,    8	    },
    {   "dvmrp_routes",	    DEBUG_DVMRP_ROUTE,    7	    },
    {   "dvmrp_routing",    DEBUG_DVMRP_ROUTE,    7	    },
    {	"dvmrp_mrt",	    DEBUG_DVMRP_ROUTE,	  7	    },
    {	"dvmrp_neighbors",  DEBUG_DVMRP_PEER,	  7	    },
    {	"dvmrp_peers",	    DEBUG_DVMRP_PEER,	  8	    },
    {	"dvmrp_hello",	    DEBUG_DVMRP_PEER,	  7	    },
    {	"dvmrp_timers",	    DEBUG_DVMRP_TIMER,	  7	    },
    {	"dvmrp",	    DEBUG_DVMRP,	  1	    },
    {	"igmp_proto",	    DEBUG_IGMP_PROTO,	  6	    },
    {	"igmp_timers",	    DEBUG_IGMP_TIMER,	  6	    },
    {	"igmp_members",	    DEBUG_IGMP_MEMBER,	  6	    },
    {	"groups",	    DEBUG_MEMBER,	  1	    },
    {	"membership",	    DEBUG_MEMBER,	  2	    },
    {	"igmp",		    DEBUG_IGMP,		  1	    },
    {	"trace",	    DEBUG_TRACE,	  2	    },
    {	"mtrace",	    DEBUG_TRACE,	  2	    },
    {	"traceroute",	    DEBUG_TRACE,	  2	    },
    {	"timeout",	    DEBUG_TIMEOUT,	  2	    },
    {	"callout",	    DEBUG_TIMEOUT,	  3	    },
    {	"packets",	    DEBUG_PKT,		  2	    },
    {	"pkt",		    DEBUG_PKT,		  2	    },
    {	"interfaces",	    DEBUG_IF,		  2	    },
    {	"vif",		    DEBUG_IF,		  1	    },
    {	"kernel",	    DEBUG_KERN,		  2	    },
    {	"cache",	    DEBUG_MFC,		  1	    },
    {	"mfc",		    DEBUG_MFC,		  2	    },
    {	"k_cache",	    DEBUG_MFC,		  2	    },
    {	"k_mfc",	    DEBUG_MFC,		  2	    },
    {	"rsrr",		    DEBUG_RSRR,		  2	    },
    {	"pim_detail",	    DEBUG_PIM_DETAIL,	  5	    },
    {	"pim_hello",	    DEBUG_PIM_HELLO,	  5	    },
    {	"pim_neighbors",    DEBUG_PIM_HELLO,	  5	    },
    {	"pim_peers",	    DEBUG_PIM_HELLO,	  5	    },
    {	"pim_register",	    DEBUG_PIM_REGISTER,	  5	    },
    {	"registers",	    DEBUG_PIM_REGISTER,	  2	    },
    {	"pim_join_prune",   DEBUG_PIM_JOIN_PRUNE, 5	    },
    {	"pim_j_p",	    DEBUG_PIM_JOIN_PRUNE, 5	    },
    {	"pim_jp",	    DEBUG_PIM_JOIN_PRUNE, 5	    },
    {	"pim_bootstrap",    DEBUG_PIM_BOOTSTRAP,  5	    },
    {	"pim_bsr",	    DEBUG_PIM_BOOTSTRAP,  5	    },
    {	"bsr",		    DEBUG_PIM_BOOTSTRAP,  1	    },
    {	"bootstrap",	    DEBUG_PIM_BOOTSTRAP,  1	    },
    {	"pim_asserts",	    DEBUG_PIM_ASSERT,	  5	    },
    {	"pim_cand_rp",	    DEBUG_PIM_CAND_RP,	  5	    },
    {	"pim_c_rp",	    DEBUG_PIM_CAND_RP,	  5	    },
    {	"pim_rp",	    DEBUG_PIM_CAND_RP,	  6	    },
    {	"rp",		    DEBUG_PIM_CAND_RP,	  2	    },
    {	"pim_routes",	    DEBUG_PIM_MRT,	  6	    },
    {	"pim_routing",	    DEBUG_PIM_MRT,	  6	    },
    {	"pim_mrt",	    DEBUG_PIM_MRT,	  5	    },
    {	"pim_timers",	    DEBUG_PIM_TIMER,	  5	    },
    {	"pim_rpf",	    DEBUG_PIM_RPF,	  6	    },
    {	"rpf",		    DEBUG_RPF,		  3	    },
    {	"pim",		    DEBUG_PIM,		  1	    },
    {	"routes",	    DEBUG_MRT,		  1	    },
    {	"routing",	    DEBUG_MRT,		  1	    },
    {	"mrt",		    DEBUG_MRT,		  1	    },
    {	"neighbors",	    DEBUG_NEIGHBORS,	  1	    },
    {	"routers",	    DEBUG_NEIGHBORS,	  6	    },
    {	"mrouters",	    DEBUG_NEIGHBORS,	  7	    },
    {	"peers",	    DEBUG_NEIGHBORS,	  1	    },
    {	"timers",	    DEBUG_TIMER,	  1	    },
    {	"asserts",	    DEBUG_ASSERT,	  1	    },
    {	"all",		    DEBUG_ALL,		  2	    },
    {	"3",		    0xffffffff,		  1	    }	 /* compat. */
};


/*
 * Forward declarations.
 */
static void handler      (int);
static void timer	 (void *);
static void cleanup      (void);
static void restart      (int);
static void resetlogging (void *);

int register_input_handler(int fd, ihfunc_t func)
{
    if (nhandlers >= NHANDLERS)
	return -1;

    ihandlers[nhandlers].fd = fd;
    ihandlers[nhandlers++].func = func;

    return 0;
}

static void do_randomize(void)
{
#define rol32(data,shift) ((data) >> (shift)) | ((data) << (32 - (shift)))
   int fd;
   unsigned int seed;

   /* Setup a fallback seed based on quasi random. */
#ifdef SYSV
   seed = time(NULL);
#else
   seed = time(NULL) ^ gethostid();
#endif
   seed = rol32(seed, seed);

   fd = open("/dev/urandom", O_RDONLY);
   if (fd >= 0) {
       if (-1 == read(fd, &seed, sizeof(seed)))
	   warn("Failed reading entropy from /dev/urandom");
       close(fd);
  }

#ifdef SYSV
   srand48(seed);
#else
   srandom(seed);
#endif
}

/* Figure out the PID of a running daemon. */
static pid_t daemon_pid(void)
{
    int result;
    char *path = NULL;
    FILE *fp;
    pid_t pid = -1;

    result = asprintf(&path, "%s%s.pid", _PATH_VARRUN, __progname);
    if (result == -1 || path == NULL)
	return -1;

    fp = fopen(path, "r");
    if (!fp) {
	free(path);
	return -1;
    }

    result = fscanf(fp, "%d", &pid);
    fclose(fp);
    free(path);

    return pid;
}

/* Send signal to running daemon and the show resulting file. */
static int killshow(int signo, char *file)
{
    pid_t pid = daemon_pid();
    char buf[100];

    if (pid > 0) {
	if (file && -1 == remove(file) && errno != ENOENT)
	    warn("Failed removing %s, may be showing stale information", file);

	kill(pid, signo);
	if (file) {
	    usleep(200);
	    snprintf(buf, sizeof(buf), "cat %s", file);
	    if (-1 == system(buf)) {
		warnx("Failed listing file %s\n", file);
	    }
	}
    }

    return 0;
}

static int usage(void)
{
    size_t i;
    char line[76] = "  ";
    struct debugname *d;

    fprintf(stderr, "Usage: %s [-fhlNqrv] [-c FILE] [-d [LEVEL][,LEVEL...]]\n\n", __progname);
    fprintf(stderr, "  -c, --config=FILE    Configuration file to use, default %s\n", _PATH_PIMD_CONF);
    fputs("  -d, --debug[=LEVEL]  Debug level, see below for valid levels\n", stderr);
    fputs("  -f, --foreground     Run in foreground, do not detach from calling terminal\n", stderr);
    fputs("  -h, --help           Show this help text\n", stderr);
    /* fputs("  -i, --show-cache      Show internal cache tables\n", stderr); */
    fputs("  -l, --reload-config  Tell a running pimd to reload its configuration\n", stderr);
    fputs("  -N, --disable-vifs   Disable all virtual interfaces (phyint) by default\n", stderr);
    /* fputs("  -p,--show-debug      Show debug dump, only if debug is enabled\n", stderr); */
    fputs("  -q, --quit-daemon    Send SIGTERM to a running pimd\n", stderr);
    fputs("  -r, --show-routes    Show state of VIFs and multicast routing tables\n", stderr);
    fputs("  -t, --table-id=ID    Set multicast routing table ID.  Allowed values are:\n"
	  "                       0 ... 999999999.  Default: 0 (use default table)\n", stderr);
    fputs("  -s, --loglevel=LEVEL Maximum syslog level to use, default NOTICE, see below\n", stderr);
    fprintf(stderr, "  -v, --version        Show %s version\n", __progname);
    fputs("\n", stderr);

    /* From pimd v2.3.0 we show *all* the debug levels again */
    fputs("Valid debug levels:\n", stderr);
    for (i = 0, d = debugnames; i < ARRAY_LEN(debugnames); i++, d++) {
	if (strlen(line) + strlen(d->name) + 3 >= sizeof(line)) {
	    /* Finish this line and send to console */
	    strlcat(line, "\n", sizeof(line));
	    fputs(line, stderr);

	    /* Prepare for next line */
	    strlcpy(line, "  ", sizeof(line));
	}

	strlcat(line, d->name, sizeof(line));

	if (i + 1 < ARRAY_LEN(debugnames))
	    strlcat(line, ", ", sizeof(line));
    }
    /* Flush remaining line. */
    strlcat(line, "\n", sizeof(line));
    fputs(line, stderr);


    fputs("\nValid system log levels:", stderr);
    fputs("\n  1) alert    Action must be taken immediately", stderr);
    fputs("\n  2) crit     Critical conditions", stderr);
    fputs("\n  3) err      Error conditions", stderr);
    fputs("\n  4) warning  Warning conditions", stderr);
    fputs("\n  5) notice   Normal but significant condition (DEFAULT)", stderr);
    fputs("\n  6) info     Informational", stderr);
    fputs("\n  7) debug    Debug-level messages", stderr);
    fputs("\n\n", stderr);

    return 1;
}

static int lvltonum(char *arg, const char **errstr)
{
    int i;
    char *level[] = { "ENERG", "ALERT", "CRIT", "ERR", "WARNING", "NOTICE", "INFO", "DEBUG", NULL };

    if (!strncmp(arg, "LOG_", 4))
	arg += 4;

    for (i = 0; level[i]; i++) {
	if (!strncasecmp(arg, level[i], strlen(level[i]))) {
	    *errstr = NULL;
	    return i;
	}
    }

    *errstr = "invalid";

    return 5;
}

int main(int argc, char *argv[])
{
    int dummysigalrm, foreground = 0;
    struct timeval tv, difftime, curtime, lasttime, *timeout;
    fd_set rfds, readers;
    int nfds, n, i, secs, ch;
    struct sigaction sa;
    time_t boottime;
    struct option long_options[] = {
	{"config", 1, 0, 'c'},
	{"debug", 2, 0, 'd'},
	{"foreground", 0, 0, 'f'},
	{"disable-vifs", 0, 0, 'N'},
	{"help", 0, 0, 'h'},
	{"version", 0, 0, 'v'},
	{"quit-daemon", 0, 0, 'q'},
	{"reload-config", 0, 0, 'l'},
	{"show-routes", 0, 0, 'r'},
	{"table-id", 1, 0, 't'},
	{"syslog-level", 1, 0, 's'},   /* Compat */
	{"loglevel", 1, 0, 's'},
	/* {"show-cache", 0, 0, 'i'}, */
	/* {"show-debug", 0, 0, 'p'}, */
	{0, 0, 0, 0}
    };

    snprintf(versionstring, sizeof (versionstring), "pimd version %s", todaysversion);

    while ((ch = getopt_long(argc, argv, "c:d::fhlNvqrt:s:", long_options, NULL)) != EOF) {
	const char *errstr;

	switch (ch) {
	    case 'c':
		if (optarg)
		    config_file = optarg;
		break;

	    case 'd':
		if (!optarg) {
		    debug = DEBUG_DEFAULT;
		} else {
		    char *p,*q;
		    size_t i, len;
		    struct debugname *d;

		    debug = 0;
		    p = optarg; q = NULL;
		    while (p) {
			q = strchr(p, ',');
			if (q)
			    *q++ = '\0';
			len = strlen(p);
			for (i = 0, d = debugnames; i < ARRAY_LEN(debugnames); i++, d++) {
			    if (len >= d->nchars && strncmp(d->name, p, len) == 0)
				break;
			}

			if (i == ARRAY_LEN(debugnames))
			    return usage();

			debug |= d->level;
			p = q;
		    }
		}
		break;

	    case 'f':
		foreground = 1;
		break;

	    case 'h':
		return usage();
	    case 'l':
		return killshow(SIGHUP, NULL);

	    case 'N':
		disable_all_by_default = 1;
		break;

	    case 'v':
		printf("%s\n", versionstring);
		return 0;

	    case 'q':
		return killshow(SIGTERM, NULL);

	    case 'r':
		return killshow(SIGUSR1, _PATH_PIMD_DUMP);

	    case 's':
		if (!optarg) {
			fprintf(stderr, "Missing syslog level argument!\n");
			return usage();
		}

		if (isdigit((int)optarg[0]))
		    syslog_level = strtonum(optarg, LOG_ALERT, LOG_DEBUG, &errstr);
		else
		    syslog_level = lvltonum(optarg, &errstr);

		if (errstr) {
		    fprintf(stderr, "Syslog level %s is %s!\n", optarg, errstr);
		    return usage();
		}
		break;

	    case 't':
		if (!optarg) {
			fprintf(stderr, "Missing Table ID argument!\n");
			return usage();
		}

		mrt_table_id = strtonum(optarg, 0, 999999999, &errstr);
		if (errstr) {
		    fprintf(stderr, "Table ID %s!\n", errstr);
		    return usage();
		}
		break;

#if 0 /* XXX: TODO */
	    case 'i':
		return killshow(SIGUSR2, _PATH_PIMD_CACHE);

	    case 'p':
		return killshow(SIGQUIT, NULL);
#endif
	    default:
		return usage();
	}
    }

    argc -= optind;
    if (argc > 0)
	return usage();

    if (geteuid() != 0)
	errx(1, "Need root privileges to start.");

    setlinebuf(stderr);

    if (debug != 0) {
	struct debugname *d;
	char c;
	int tmpd = debug;

	fprintf(stderr, "debug level 0x%lx ", debug);
	c = '(';
	for (d = debugnames; d < debugnames + ARRAY_LEN(debugnames); d++) {
	    if ((tmpd & d->level) == d->level) {
		tmpd &= ~d->level;
		fprintf(stderr, "%c%s", c, d->name);
		c = ',';
	    }
	}
	fprintf(stderr, ")\n");
    }

    /*
     * Create directory for runtime files
     */
    if (-1 == mkdir(_PATH_PIMD_RUNDIR, 0755) && errno != EEXIST)
	err(1, "Failed creating %s directory for runtime files", _PATH_PIMD_RUNDIR);

    /*
     * Setup logging
     */
#ifdef LOG_DAEMON
    openlog("pimd", LOG_PID, LOG_DAEMON);
    setlogmask(LOG_UPTO(syslog_level));
#else
    openlog("pimd", LOG_PID);
#endif /* LOG_DAEMON */

    logit(LOG_NOTICE, 0, "%s starting ...", versionstring);

    do_randomize();
    time(&boottime);

    callout_init();
    init_igmp();
    init_pim();
    init_routesock(); /* Both for Linux netlink and BSD routing socket */
    init_pim_mrt();
    init_timers();

    /* Start up the log rate-limiter */
    resetlogging(NULL);

    /* TODO: check the kernel DVMRP/MROUTED/PIM support version */

    init_vifs();
    init_rp_and_bsr();   /* Must be after init_vifs() */

#ifdef RSRR
    rsrr_init();
#endif /* RSRR */

    sa.sa_handler = handler;
    sa.sa_flags = 0;	/* Interrupt system calls */
    sigemptyset(&sa.sa_mask);
    sigaction(SIGALRM, &sa, NULL);
    sigaction(SIGHUP, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGUSR2, &sa, NULL);

    FD_ZERO(&readers);
    FD_SET(igmp_socket, &readers);
    nfds = igmp_socket + 1;
    for (i = 0; i < nhandlers; i++) {
	FD_SET(ihandlers[i].fd, &readers);
	if (ihandlers[i].fd >= nfds)
	    nfds = ihandlers[i].fd + 1;
    }

    IF_DEBUG(DEBUG_IF)
	dump_vifs(stderr);
    IF_DEBUG(DEBUG_PIM_MRT)
	dump_pim_mrt(stderr);

    /* schedule first timer interrupt */
    timer_setTimer(TIMER_INTERVAL, timer, NULL);

    if (!debug && !foreground) {
	/* Detach from the terminal */
	haveterminal = 0;
	if (fork())
	    exit(0);

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	n = open("/dev/null", O_RDWR, 0);
	if (n >= 0) {
	    dup2(n, STDIN_FILENO);
	    dup2(n, STDOUT_FILENO);
	    dup2(n, STDERR_FILENO);
	}
#ifdef SYSV
	setpgrp();
#else
#ifdef TIOCNOTTY
	n = open("/dev/tty", 2);
	if (n >= 0) {
	    (void)ioctl(n, TIOCNOTTY, (char *)0);
	    (void)close(n);
	}
#else
	if (setsid() < 0)
	    perror("setsid");
#endif /* TIOCNOTTY */
#endif /* SYSV */
    } /* End of child process code */

    if (pidfile(NULL))
	warn("Cannot create pidfile");

    /*
     * Main receive loop.
     */
    dummysigalrm = SIGALRM;
    difftime.tv_usec = 0;
    gettimeofday(&curtime, NULL);
    lasttime = curtime;
    while (1) {
	memcpy(&rfds, &readers, sizeof(rfds));
	secs = timer_nextTimer();
	if (secs == -1)
	    timeout = NULL;
	else {
	    timeout = &tv;
	    timeout->tv_sec = secs;
	    timeout->tv_usec = 0;
	}

	if (boottime) {
	    time_t n;

	    time(&n);
	    if (n > boottime + 15) {
		struct rp_hold *rph = g_rp_hold;

		while(rph) {
		    add_rp_grp_entry(&cand_rp_list, &grp_mask_list,
				     rph->address, 1, (uint16_t)0xffffff,
				     rph->group, rph->mask,
				     curr_bsr_hash_mask, curr_bsr_fragment_tag);
		    rph = rph->next;
		}
		boottime = 0;
	    }
	}

	if (sighandled) {
	    if (sighandled & GOT_SIGINT) {
		sighandled &= ~GOT_SIGINT;
		break;
	    }
	    if (sighandled & GOT_SIGHUP) {
		sighandled &= ~GOT_SIGHUP;
		restart(SIGHUP);

		/* reconstruct readers and nfds */
		FD_ZERO(&readers);
		FD_SET(igmp_socket, &readers);
		nfds = igmp_socket + 1;
		for (i = 0; i < nhandlers; i++) {
		    FD_SET(ihandlers[i].fd, &readers);
		    if (ihandlers[i].fd >= nfds)
			nfds = ihandlers[i].fd + 1;
		}
		memcpy(&rfds, &readers, sizeof(rfds));
	    }
	    if (sighandled & GOT_SIGUSR1) {
		sighandled &= ~GOT_SIGUSR1;
		fdump(SIGUSR1);
	    }
	    if (sighandled & GOT_SIGUSR2) {
		sighandled &= ~GOT_SIGUSR2;
		cdump(SIGUSR2);
	    }
	    if (sighandled & GOT_SIGALRM) {
		sighandled &= ~GOT_SIGALRM;
		timer(&dummysigalrm);
	    }
	}
	if ((n = select(nfds, &rfds, NULL, NULL, timeout)) < 0) {
	    if (errno != EINTR) /* SIGALRM is expected */
		logit(LOG_WARNING, errno, "select failed");
	    continue;
	}
	if (n > 0) {
	    /* TODO: shall check first igmp_socket for better performance? */
	    for (i = 0; i < nhandlers; i++) {
		if (FD_ISSET(ihandlers[i].fd, &rfds)) {
		    (*ihandlers[i].func)(ihandlers[i].fd, &rfds);
		}
	    }
	}

	/*
	 * Handle timeout queue.
	 *
	 * If select + packet processing took more than 1 second,
	 * or if there is a timeout pending, age the timeout queue.
	 *
	 * If not, collect usec in difftime to make sure that the
	 * time doesn't drift too badly.
	 *
	 * If the timeout handlers took more than 1 second,
	 * age the timeout queue again.  XXX This introduces the
	 * potential for infinite loops!
	 */
	do {
	    /*
	     * If the select timed out, then there's no other
	     * activity to account for and we don't need to
	     * call gettimeofday.
	     */
	    if (n == 0) {
		curtime.tv_sec = lasttime.tv_sec + secs;
		curtime.tv_usec = lasttime.tv_usec;
		n = -1;	/* don't do this next time through the loop */
	    } else
		gettimeofday(&curtime, NULL);
	    difftime.tv_sec = curtime.tv_sec - lasttime.tv_sec;
	    difftime.tv_usec += curtime.tv_usec - lasttime.tv_usec;
	    while (difftime.tv_usec >= 1000000) {
		difftime.tv_sec++;
		difftime.tv_usec -= 1000000;
	    }
	    if (difftime.tv_usec < 0) {
		difftime.tv_sec--;
		difftime.tv_usec += 1000000;
	    }
	    lasttime = curtime;
	    if (secs == 0 || difftime.tv_sec > 0)
		age_callout_queue(difftime.tv_sec);
	    secs = -1;
	} while (difftime.tv_sec > 0);
    } /* Main loop */

    logit(LOG_NOTICE, 0, "%s exiting.", versionstring);
    cleanup();
    exit(0);
}

/*
 * The 'virtual_time' variable is initialized to a value that will cause the
 * first invocation of timer() to send a probe or route report to all vifs
 * and send group membership queries to all subnets for which this router is
 * querier.  This first invocation occurs approximately TIMER_INTERVAL seconds
 * after the router starts up.   Note that probes for neighbors and queries
 * for group memberships are also sent at start-up time, as part of initial-
 * ization.  This repetition after a short interval is desirable for quickly
 * building up topology and membership information in the presence of possible
 * packet loss.
 *
 * 'virtual_time' advances at a rate that is only a crude approximation of
 * real time, because it does not take into account any time spent processing,
 * and because the timer intervals are sometimes shrunk by a random amount to
 * avoid unwanted synchronization with other routers.
 */

uint32_t virtual_time = 0;

/*
 * Timer routine. Performs all perodic functions:
 * aging interfaces, quering neighbors and members, etc... The granularity
 * is equal to TIMER_INTERVAL.
 */
static void timer(void *i __attribute__((unused)))
{
    age_vifs();		/* Timeout neighbors and groups         */
    age_routes();	/* Timeout routing entries              */
    age_misc();		/* Timeout the rest (Cand-RP list, etc) */

    virtual_time += TIMER_INTERVAL;
    timer_setTimer(TIMER_INTERVAL, timer, NULL);
}

/*
 * Performs all necessary functions to quit gracefully
 */
/* TODO: implement all necessary stuff */
static void cleanup(void)
{
    vifi_t vifi;
    struct uvif *v;

    /* inform all neighbors that I'm going to die */
    for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v) {
	if ((v->uv_flags &
	     (VIFF_DOWN | VIFF_DISABLED | VIFF_REGISTER | VIFF_TUNNEL)) == 0)
	    send_pim_hello(v, 0);
    }

#ifdef RSRR
    rsrr_clean();
#endif /* RSRR */

    /* TODO: XXX (not in the spec): if I am the BSR, somehow inform the
     * other routers I am going down and need to elect another BSR?
     * (probably by sending a the Cand-RP-set with my_priority=LOWEST?)
     */

    k_stop_pim(igmp_socket);
}


/*
 * Signal handler.  Take note of the fact that the signal arrived
 * so that the main loop can take care of it.
 */
static void handler(int sig)
{
    switch (sig) {
    case SIGALRM:
	sighandled |= GOT_SIGALRM;
	break;

    case SIGINT:
    case SIGTERM:
	sighandled |= GOT_SIGINT;
	break;

    case SIGHUP:
	sighandled |= GOT_SIGHUP;
	break;

    case SIGUSR1:
	sighandled |= GOT_SIGUSR1;
	break;

    case SIGUSR2:
	sighandled |= GOT_SIGUSR2;
	break;
    }
}


/* TODO: not verified */
/*
 * Restart the daemon
 */
static void restart(int i __attribute__((unused)))
{
    logit(LOG_NOTICE, 0, "%s restarting ...", versionstring);

    /*
     * reset all the entries
     */
    /* TODO: delete?
       free_all_routes();
    */
    free_all_callouts();
    stop_all_vifs();
    k_stop_pim(igmp_socket);
    nhandlers = 0;
    close(igmp_socket);
    close(pim_socket);

    /*
     * When IOCTL_OK_ON_RAW_SOCKET is defined, 'udp_socket' is equal
     * 'to igmp_socket'. Therefore, 'udp_socket' should be closed only
     * if they are different.
     */
#ifndef IOCTL_OK_ON_RAW_SOCKET
    close(udp_socket);
#endif

    /* Both for Linux netlink and BSD routing socket */
    close(routing_socket);

    /*
     * start processing again
     */

    init_igmp();
    init_pim();
    init_routesock(); /* Both for Linux netlink and BSD routing socket */
    init_pim_mrt();
    init_vifs();

    /* schedule timer interrupts */
    timer_setTimer(TIMER_INTERVAL, timer, NULL);
}


static void resetlogging(void *arg)
{
    int nxttime = 60;
    void *narg = NULL;

    if (arg == NULL && log_nmsgs >= LOG_MAX_MSGS) {
	nxttime = LOG_SHUT_UP;
	narg = (void *)&log_nmsgs;	/* just need some valid void * */
	syslog(LOG_WARNING, "logging too fast, shutting up for %d minutes",
	       LOG_SHUT_UP / 60);
    } else {
	if (arg != NULL) {
	    syslog(LOG_NOTICE, "logging enabled again after rate limiting");
	}
	log_nmsgs = 0;
    }

    timer_setTimer(nxttime, resetlogging, narg);
}

/**
 * Local Variables:
 *  version-control: t
 *  indent-tabs-mode: t
 *  c-file-style: "ellemtel"
 *  c-basic-offset: 4
 * End:
 */
