diff --git a/event/lib/event.h b/event/lib/event.h
index 989d8183e43315ea8f639620900d6b4129176f76..3fcf08b24e23eeb49d9bbef853285b1958f9d51e 100644
--- a/event/lib/event.h
+++ b/event/lib/event.h
@@ -5,7 +5,7 @@
  *
  * @COPYRIGHT@
  *
- * $Id: event.h,v 1.10 2002-03-05 16:33:41 stoller Exp $
+ * $Id: event.h,v 1.11 2002-03-18 18:55:21 stoller Exp $
  */
 
 #ifndef __EVENT_H__
@@ -85,6 +85,27 @@ int		address_tuple_free(address_tuple_t);
 #define event_notification_set_arguments(handle, note, buf) \
         event_notification_put_string(handle, note, "ARGS", buf)
 
+/*
+ * For dynamic events.
+ */
+#define event_notification_clear_host(handle, note) \
+	event_notification_remove(handle, note, "HOST")
+#define event_notification_set_host(handle, note, buf) \
+        event_notification_put_string(handle, note, "HOST", buf)
+#define event_notification_clear_objtype(handle, note) \
+	event_notification_remove(handle, note, "OBJTYPE")
+#define event_notification_set_objtype(handle, note, buf) \
+        event_notification_put_string(handle, note, "OBJTYPE", buf)
+
+/*
+ * Event library sets this field. Holds the sender of the event, as 
+ * determined by the library when it is initialized. 
+ */
+#define event_notification_get_sender(handle, note, buf, len) \
+        event_notification_get_string(handle, note, "___SENDER___", buf, len)
+#define event_notification_set_sender(handle, note, buf) \
+        event_notification_put_string(handle, note, "___SENDER___", buf)
+
 
 /* The "any host" string: */
 #define EVENT_HOST_ANY "*"
diff --git a/event/sched/event-sched.c b/event/sched/event-sched.c
index 96c344bff95d6e3718f50977cd601a2a5b525b04..396fa870e58ff83953921ae289fb0b29fd3446a5 100644
--- a/event/sched/event-sched.c
+++ b/event/sched/event-sched.c
@@ -35,6 +35,16 @@ static int	debug;
 static void	cleanup(void);
 static void	quit(int);
 
+#define MAXAGENTS	100
+static struct {
+	char    nodeid[TBDB_FLEN_NODEID];
+	char    vnode[TBDB_FLEN_VNAME];
+	char	objname[TBDB_FLEN_EVOBJNAME];
+	char	objtype[TBDB_FLEN_EVOBJTYPE];
+	char	ipaddr[32];
+} agents[MAXAGENTS];
+static int	numagents;
+
 void
 usage()
 {
@@ -51,7 +61,7 @@ main(int argc, char **argv)
 	char *server = NULL;
 	char *port = NULL;
 	char *log = NULL;
-	char buf[BUFSIZ];
+	char pideid[BUFSIZ], buf[BUFSIZ];
 	int c, count;
 
 	progname = argv[0];
@@ -85,6 +95,7 @@ main(int argc, char **argv)
 		usage();
 	pid = argv[0];
 	eid = argv[1];
+	sprintf(pideid, "%s/%s", pid, eid);
 
 	signal(SIGINT, quit);
 	signal(SIGTERM, quit);
@@ -138,6 +149,7 @@ main(int argc, char **argv)
 		fatal("could not allocate an address tuple");
 	}
 	tuple->scheduler = 1;
+	tuple->expt      = pideid;
 
 	if (event_subscribe(handle, enqueue, tuple, NULL) == NULL) {
 		fatal("could not subscribe to EVENT_SCHEDULE event");
@@ -183,50 +195,81 @@ main(int argc, char **argv)
 static void
 enqueue(event_handle_t handle, event_notification_t notification, void *data)
 {
-    sched_event_t event;
+    sched_event_t	event;
+    char		objname[TBDB_FLEN_EVOBJNAME];
+    int			x;
 
     /* Clone the event notification, since we want the notification to
        live beyond the callback function: */
     event.notification = elvin_notification_clone(notification,
                                                   handle->status);
     if (!event.notification) {
-        ERROR("elvin_notification_clone failed: ");
-        elvin_error_fprintf(stderr, handle->status);
-        return;
+	    error("elvin_notification_clone failed!\n");
+	    return;
     }
 
     /* Clear the scheduler flag */
     if (! event_notification_remove(handle, event.notification, "SCHEDULER") ||
 	! event_notification_put_int32(handle,
 				       event.notification, "SCHEDULER", 0)) {
-        ERROR("could not clear scheduler attribute of notification %p\n",
-              event.notification);
-        return;
+	    error("could not clear scheduler attribute of notification %p\n",
+		  event.notification);
+	    goto bad;
     }
 
     /* Get the event's firing time: */
-
-    if (event_notification_get_int32(handle, event.notification, "time_sec",
-                                     (int *) &event.time.tv_sec)
-        == 0)
-    {
-        ERROR("could not get time.tv_sec attribute from notification %p\n",
-              event.notification);
-        return;
+    if (! event_notification_get_int32(handle, event.notification, "time_usec",
+				       (int *) &event.time.tv_usec) ||
+	! event_notification_get_int32(handle, event.notification, "time_sec",
+				       (int *) &event.time.tv_sec)) {
+	    error("could not get time from notification %p\n",
+		  event.notification);
+	    goto bad;
     }
 
-    if (event_notification_get_int32(handle, event.notification, "time_usec",
-                                     (int *) &event.time.tv_usec)
-        == 0)
-    {
-        ERROR("could not get time.tv_usec attribute from notification %p\n",
-              event.notification);
-        return;
+    /*
+     * Must map the event to the proper agent running on a particular
+     * node. 
+     */
+    if (! event_notification_get_objname(handle, event.notification,
+					 objname, sizeof(objname))) {
+	    error("could not get object name from notification %p\n",
+		  event.notification);
+	    goto bad;
+    }
+    for (x = 0; x < numagents; x++) {
+	    if (!strcmp(agents[x].objname, objname))
+		    break;
+    }
+    if (x == numagents) {
+	    error("Could not map object to an agent: %s\n", objname);
+	    goto bad;
+    }
+    event_notification_clear_host(handle, event.notification);
+    event_notification_set_host(handle,
+				event.notification, agents[x].ipaddr);
+    event_notification_clear_objtype(handle, event.notification);
+    event_notification_set_objtype(handle,
+				   event.notification, agents[x].objtype);
+
+    if (debug > 1) {
+	    struct timeval now;
+	    
+	    gettimeofday(&now, NULL);
+	    
+	    info("Sched: note:%p at:%ld:%d now:%ld:%d agent:%d\n",
+                 event.notification,
+		 event.time.tv_sec, event.time.tv_usec,
+		 now.tv_sec, now.tv_usec,
+		 x);
     }
 
     /* Enqueue the event notification for resending at the indicated
        time: */
     sched_event_enqueue(event);
+    return;
+ bad:
+    event_notification_free(handle, event.notification);
 }
 
 /* Returns the amount of time until EVENT fires. */
@@ -284,12 +327,9 @@ dequeue(event_handle_t handle)
         }
 
 	if (debug > 1) {
-	    info("firing event (event=(notification=%p, "
-		 "time=(tv_sec=%ld, tv_usec=%ld)) "
-		 "at time (time=(tv_sec=%ld, tv_usec=%ld))\n",
-		 next_event.notification,
-		 next_event.time.tv_sec,
-		 next_event.time.tv_usec,
+	    info("Fire:  note:%p at:%ld:%d now:%ld:%d\n",
+                 next_event.notification,
+		 next_event.time.tv_sec, next_event.time.tv_usec,
 		 now.tv_sec,
 		 now.tv_usec);
 	}
@@ -311,28 +351,101 @@ get_static_events(event_handle_t handle)
 	address_tuple_t tuple;
 	char		pideid[BUFSIZ];
 	event_notification_t notification;
+	int		adx = 0;
+
+	/*
+	 * Build up a table of agents that can receive dynamic events.
+	 * Currently, these are trafgens and delay nodes. We want to
+	 * be able to quickly map from "cbr0" to the node on which it
+	 * lives (for dynamic events). 
+	 */
+	res = mydb_query("select vi.vname,vi.vnode,r.node_id "
+			 " from virt_trafgens as vi "
+			 "left join reserved as r on "
+			 " r.vname=vi.vnode and r.pid=vi.pid and r.eid=vi.eid "
+			 "where vi.role='source' and "
+			 " vi.pid='%s' and vi.eid='%s'",
+			 3, pid, eid);
+
+	if (!res) {
+		error("getting virt_trafgens list for %s/%s", pid, eid);
+		return 0;
+	}
+	nrows = mysql_num_rows(res);
+	while (nrows--) {
+		row = mysql_fetch_row(res);
+
+		if (!row[0] || !row[1] || !row[2])
+			continue;
+
+		strcpy(agents[numagents].objname, row[0]);
+		strcpy(agents[numagents].vnode,   row[1]);
+		strcpy(agents[numagents].nodeid,  row[2]);
+		strcpy(agents[numagents].objtype, TBDB_OBJECTTYPE_TRAFGEN);
+
+		if (! mydb_nodeidtoip(row[2], agents[numagents].ipaddr))
+			continue;
+		numagents++;
+	}
+	mysql_free_result(res);
+	
+	res = mydb_query("select d.vname,r.vname,d.node_id from delays as d "
+			 "left join reserved as r on r.node_id=d.node_id "
+			 "where d.pid='%s' and d.eid='%s'",
+			 3, pid, eid);
 
-	res = mydb_query("select ex.time,ex.vnode,ex.vname,ex.arguments,"
-			 " ot.type,et.type,i.IP from eventlist as ex "
+	if (!res) {
+		error("getting delays list for %s/%s", pid, eid);
+		return 0;
+	}
+	nrows = mysql_num_rows(res);
+	while (nrows--) {
+		row = mysql_fetch_row(res);
+
+		if (!row[0] || !row[1] || !row[2])
+			continue;
+
+		strcpy(agents[numagents].objname, row[0]);
+		strcpy(agents[numagents].vnode,   row[1]);
+		strcpy(agents[numagents].nodeid,  row[2]);
+		strcpy(agents[numagents].objtype, TBDB_OBJECTTYPE_LINK);
+
+		if (! mydb_nodeidtoip(row[2], agents[numagents].ipaddr))
+			continue;
+		numagents++;
+	}
+	mysql_free_result(res);
+
+	if (debug) {
+		for (adx = 0; adx < numagents; adx++) {
+			info("Agent %d: %10s %10s %10s %8s %16s\n", adx,
+			     agents[adx].objname,
+			     agents[adx].objtype,
+			     agents[adx].vnode,
+			     agents[adx].nodeid,
+			     agents[adx].ipaddr);
+		}
+	}
+
+	/*
+	 * Now get the eventlist. There should be entries in the
+	 * agents table for anything we find in the list.
+	 */
+	res = mydb_query("select ex.idx,ex.time,ex.vnode,ex.vname,"
+			 " ex.arguments,ot.type,et.type from eventlist as ex "
 			 "left join event_eventtypes as et on "
 			 " ex.eventtype=et.idx "
 			 "left join event_objecttypes as ot on "
 			 " ex.objecttype=ot.idx "
-			 "left join reserved as r on "
-			 " ex.vnode=r.vname and ex.pid=r.pid and ex.eid=r.eid "
-			 "left join nodes as n on r.node_id=n.node_id "
-			 "left join node_types as nt on nt.type=n.type "
-			 "left join interfaces as i on "
-			 " i.node_id=r.node_id and i.iface=nt.control_iface "
 			 "where ex.pid='%s' and ex.eid='%s'",
 			 7, pid, eid);
-#define EXTIME	row[0]
-#define EXVNODE	row[1]
-#define EXVNAME	row[2]
-#define EXARGS	row[3]
-#define OBJTYPE	row[4]
-#define EVTTYPE	row[5]
-#define IPADDR	row[6]
+#define EXIDX	 row[0]
+#define EXTIME	 row[1]
+#define EXVNODE	 row[2]
+#define OBJNAME  row[3]
+#define EXARGS	 row[4]
+#define OBJTYPE	 row[5]
+#define EVTTYPE	 row[6]
 
 	if (!res) {
 		error("getting static event list for %s/%s", pid, eid);
@@ -364,19 +477,27 @@ get_static_events(event_handle_t handle)
 		row = mysql_fetch_row(res);
 		firetime = atof(EXTIME);
 
-		if (debug) 
-			info("EV: %8s %10s %10s %10s %10s %10s %10s\n",
-			     row[0], row[1], row[2],
-			     row[3] ? row[3] : "",
-			     row[4], row[5],
-			     row[6] ? row[6] : "");
+		for (adx = 0; adx < numagents; adx++) {
+			if (!strcmp(agents[adx].objname, OBJNAME))
+				break;
+		}
+		if (adx == numagents) {
+			error("Could not map event index %s", EXIDX);
+			return 0;
+		}
 
 		tuple->expt      = pideid;
-		tuple->host      = IPADDR;
-		tuple->objname   = EXVNAME;
+		tuple->host      = agents[adx].ipaddr;
+		tuple->objname   = OBJNAME;
 		tuple->objtype   = OBJTYPE;
 		tuple->eventtype = EVTTYPE;
 
+		if (debug) 
+			info("%8s %10s %10s %10s %10s %10s %10s\n",
+			     EXTIME, EXVNODE, OBJNAME, OBJTYPE,
+			     EVTTYPE, agents[adx].ipaddr, 
+			     EXARGS ? EXARGS : "");
+
 		event.notification = event_notification_alloc(handle, tuple);
 		if (! event.notification) {
 			error("could not allocate notification");
diff --git a/event/tbgen/tevc.c b/event/tbgen/tevc.c
index 7d0ce3c3b6260d6c75981b9ad16904cee4c0881a..7c0c8ce3d1dd9fe608e5e7a5aa20ce226b52856b 100644
--- a/event/tbgen/tevc.c
+++ b/event/tbgen/tevc.c
@@ -2,30 +2,44 @@
  * This is used to send Testbed events (reboot, ready, etc.) to the
  * testbed event client. It is intended to be wrapped up by a perl
  * script that will use the tmcc to figure out where the event server
- * lives (host/port) to construct the server string. 
+ * lives (host/port) to construct the server string.
+ *
+ * Issues: key and pid/eid.
+ *         valid events, names, types.
+ *         valid arguments.
+ *         vname to ipaddr mapping.
  */
 
 #include <stdio.h>
 #include <ctype.h>
 #include <netdb.h>
 #include <unistd.h>
+#include <time.h>
+#include <math.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include "config.h"
 #include "log.h"
-
+#include "tbdefs.h"
 #include "event.h"
 
-static char	*progname;
+static int debug;
 
 void
-usage()
+usage(char *progname)
 {
-	fprintf(stderr,
-		"Usage: %s [-s server] [-p port] [-i ipaddr] <event>\n",
-		progname);
-	exit(-1);
+    fprintf(stderr,
+	"Usage: %s [-s server] [-c] event\n"
+	"       %s [-s server] -e pid/eid time objname event [args ...]\n"
+	"       time: 'now' or '+seconds' or [[[[yy]mm]dd]HH]MMss\n"
+	"Examples:\n"
+	"       %s -e pid/eid now cbr0 set interval_=0.2\n"
+	"       %s -e pid/eid +10 cbr0 start\n"
+	"       %s -e pid/eid +20 cbr0 stop\n",
+	progname, progname, progname, progname, progname);
+    exit(-1);
 }
 
 int
@@ -34,78 +48,83 @@ main(int argc, char **argv)
 	event_handle_t handle;
 	event_notification_t notification;
 	address_tuple_t	tuple;
+	char *progname;
 	char *server = NULL;
 	char *port = NULL;
-	char *ipaddr = NULL;
-	char buf[BUFSIZ], ipbuf[BUFSIZ], *bp;
+	int control = 0;
+	char *myeid = NULL;
+	char buf[BUFSIZ], *bp;
+	char *evtime = NULL, *objname = NULL, *event;
 	int c;
 
 	progname = argv[0];
 	
-	while ((c = getopt(argc, argv, "s:p:i:")) != -1) {
+	while ((c = getopt(argc, argv, "ds:p:ce:")) != -1) {
 		switch (c) {
+		case 'd':
+			debug++;
+			break;
 		case 's':
 			server = optarg;
 			break;
 		case 'p':
 			port = optarg;
 			break;
-		case 'i':
-			ipaddr = optarg;
+		case 'c':
+			control = 1;
+			break;
+		case 'e':
+			myeid = optarg;
 			break;
 		default:
-			usage();
+			usage(progname);
 		}
 	}
 	argc -= optind;
 	argv += optind;
 
-	if (argc != 1)
-		usage();
-	
-	loginit(0, 0);
+	if (control) {
+		if (geteuid())
+			fatal("Only root can send TBCONTROL events");
+		
+		if (argc != 1)
+			usage(progname);
+		
+		event = argv[0];
+		argc -= 1;
+		argv += 1;
+	}
+	else {
+		if (argc < 3)
+			usage(progname);
 
-	/*
-	 * Uppercase event tags for now. Should be wired in list instead.
-	 */
-	bp = argv[0];
-	while (*bp) {
-		*bp = toupper(*bp);
-		bp++;
+		evtime  = argv[0];
+		objname = argv[1];
+		event   = argv[2];
+
+		argc -= 3;
+		argv += 3;
 	}
+	loginit(0, 0);
 
 	/*
-	 * Get our IP address. Thats how we name ourselves to the
-	 * Testbed Event System. 
+	 * If server is not specified, then it defaults to BOSSNODE.
+	 * This allows the client to work on either users.emulab.net
+	 * or on a client node. 
 	 */
-	if (ipaddr == NULL) {
-	    struct hostent	*he;
-	    struct in_addr	myip;
-	    
-	    if (gethostname(buf, sizeof(buf)) < 0) {
-		fatal("could not get hostname");
-	    }
-
-	    if (! (he = gethostbyname(buf))) {
-		fatal("could not get IP address from hostname: %s", buf);
-	    }
-	    memcpy((char *)&myip, he->h_addr, he->h_length);
-	    strcpy(ipbuf, inet_ntoa(myip));
-	    ipaddr = ipbuf;
-	}
+	if (!server)
+		server = BOSSNODE;
 
 	/*
 	 * Convert server/port to elvin thing.
 	 *
 	 * XXX This elvin string stuff should be moved down a layer. 
 	 */
-	if (server) {
-		snprintf(buf, sizeof(buf), "elvin://%s%s%s",
-			 server,
-			 (port ? ":"  : ""),
-			 (port ? port : ""));
-		server = buf;
-	}
+	snprintf(buf, sizeof(buf), "elvin://%s%s%s",
+		 server,
+		 (port ? ":"  : ""),
+		 (port ? port : ""));
+	server = buf;
 
 	/*
 	 * Construct an address tuple for generating the event.
@@ -114,16 +133,54 @@ main(int argc, char **argv)
 	if (tuple == NULL) {
 		fatal("could not allocate an address tuple");
 	}
-	tuple->objtype  = OBJECTTYPE_TESTBED;
-	tuple->eventtype= argv[0];
-	tuple->host	= ipaddr;
-
+	
 	/* Register with the event system: */
 	handle = event_register(server, 0);
 	if (handle == NULL) {
 		fatal("could not register with event system");
 	}
 
+	if (control) {
+		/*
+		 * Send a control event.
+		 */
+		bp = event;
+		while (*bp) {
+			*bp = toupper(*bp);
+			bp++;
+		}
+		if (! tbdb_valideventtype(event))
+			fatal("Unknown %s event: %s",
+			      OBJECTTYPE_TESTBED, event);
+		
+		tuple->objtype  = OBJECTTYPE_TESTBED;
+		tuple->eventtype= event;
+	}
+	else {
+		/*
+		 * A dynamic event. 
+		 */
+		if (! myeid)
+			fatal("Must provide pid/eid");
+
+		bp = event;
+		while (*bp) {
+			*bp = toupper(*bp);
+			bp++;
+		}
+		if (!strcmp(event, "SET"))
+			event = TBDB_EVENTTYPE_MODIFY;
+		else if (! tbdb_valideventtype(event))
+			fatal("Unknown event: %s", event);
+		
+		tuple->objname   = objname;
+		tuple->eventtype = event;
+		tuple->expt      = myeid;
+		tuple->site      = ADDRESSTUPLE_ANY;
+		tuple->host      = ADDRESSTUPLE_ANY;
+		tuple->group     = ADDRESSTUPLE_ANY;
+	}
+
 	/* Generate the event */
 	notification = event_notification_alloc(handle, tuple);
 	
@@ -131,8 +188,98 @@ main(int argc, char **argv)
 		fatal("could not allocate notification");
 	}
 
-	if (event_notify(handle, notification) == 0) {
-		fatal("could not send test event notification");
+	/*
+	 * If there are any extra arguments, insert them into
+	 * the notification as an arg string.
+	 *
+	 * XXX For now, uppercase the strings, and remove trailing _.
+	 */
+	if (argc) {
+		buf[0] = NULL;
+		while (argc) {
+			if (strlen(*argv) + strlen(buf) >= sizeof(buf)-2)
+				fatal("Too many event argument strings!");
+
+			bp = *argv;
+			while (*bp && *bp != '=') {
+				*bp = toupper(*bp);
+				bp++;
+			}
+			if (*bp != '=')
+				fatal("Malformed argument: %s!", *argv);
+			if (*(bp-1) == '_')
+				*(bp-1) = NULL;
+			*bp++ = NULL;
+
+			sprintf(&buf[strlen(buf)], "%s=%s ", *argv, bp);
+			argc--;
+			argv++;
+		}
+		event_notification_set_arguments(handle, notification, buf);
+	}
+
+	if (control) {
+		if (event_notify(handle, notification) == 0) {
+			fatal("could not send test event notification");
+		}
+	}
+	else {
+		struct timeval when;
+		
+		/*
+		 * Parse the time. Now is special; set the time to 0.
+		 */
+		if (!strcmp(evtime, "now")) {
+			gettimeofday(&when, NULL);
+		}
+		else if (evtime[0] == '+') {
+			gettimeofday(&when, NULL);
+
+			if (strchr(evtime, '.')) {
+				double	val = atof(evtime);
+
+				when.tv_sec  += (int) rint(val);
+				when.tv_usec +=
+					(int) (1000000 * (val - rint(val)));
+
+				if (when.tv_usec > 1000000) {
+					when.tv_sec  += 1;
+					when.tv_usec -= 1000000;
+				}
+			}
+			else
+				when.tv_sec += atoi(evtime);
+		}
+		else {
+			char	 *format = "%y%m%d%H%M%S";
+			int	  len = strlen(evtime);
+			struct tm tm;
+			
+			if ((len & 1) || (len > strlen(format)) || (len < 4))
+				usage(progname);
+			format += strlen(format) - len;
+
+			gettimeofday(&when, NULL);
+			localtime_r(&when.tv_sec, &tm);
+			if (!strptime(evtime, format, &tm))
+				usage(progname);
+
+			when.tv_sec  = mktime(&tm);
+			when.tv_usec = 0;
+		}
+
+		if (debug) {
+			struct timeval now;
+			
+			gettimeofday(&now, NULL);
+			
+			info("Now:  %ld:%d\n", now.tv_sec, now.tv_usec);
+			info("When: %ld:%d\n", when.tv_sec, when.tv_usec);
+		}
+		
+		if (event_schedule(handle, notification, &when) == 0) {
+			fatal("could not send test event notification");
+		}
 	}
 
 	event_notification_free(handle, notification);