From bf80616496ca9f6af9a61a05db214165fa83f632 Mon Sep 17 00:00:00 2001
From: "Leigh B. Stoller" <stoller@flux.utah.edu>
Date: Mon, 18 Mar 2002 18:55:22 +0000
Subject: [PATCH] First cut at dynamic events. Current status is this:

1. The tevc client can run on either a client node or on users. The
   command line syntax is like this:

	Usage: ./tevc [-s server] [-c] event
	       ./tevc [-s server] -e pid/eid time objname event [args ...]
	       time: 'now' or '+seconds' or [[[[yy]mm]dd]HH]MMss
	Examples:
	       ./tevc -e pid/eid now cbr0 set interval_=0.2
	       ./tevc -e pid/eid +10 cbr0 start
	       ./tevc -e pid/eid +20 cbr0 stop

   The -s arg defaults to BOSSNODE when not given. Only root can send
   TBCONTROL events (-c), but that part of tevc is probably going to be
   killed off when we convert to sending TBCONTROL via tmcc/tmcd (Rob
   is working on that). The -e option is required for dynamic events,
   and is not defaulted yet.

2. There is no real checking of arguments. The event is sent over to
   the scheduler, which tries to map it into an agent running on a
   node. If the map fails (say, no such trafgen, or no delay node on
   that link), or if the event makes no sense for that agent, the
   event fails and the user never hears about it. The solution for
   this is a back channel so that the scheduler can generate an error
   condition that the user sees.

3. To facilitate better checking, I think we need to change the DB
   tables so that there is a mapping between the type of agent
   (object) and the kinds of events it can take. This would replace
   the flat event_eventtypes table. The event_scheduler could load
   this table at init time so that it can do a quick check on dynamic
   events when they arrive and return an error when we support error
   values.

4. The scheduler initialization phase tries to determine what agents
   are valid for an experiment (thats where you can send events) so
   that when a user sends an event to cbr3, the event scheduler can
   determine what/where that is. My method for determining this is
   totally ad-hoc right now; read the virt_trafgens and delays table.
   These are the only two types of configurable agents we support.
   This is rather bogus! I think assign_wrapper or some other script
   in the front end needs to collect this data into a single table for
   the scheduler to read. Not sure yet; needs more thought. Anyway, I
   build a mapping table that can be searched when a dynamic event
   comes in; this avoids a DB table lookup in the critical path.
---
 event/lib/event.h         |  23 +++-
 event/sched/event-sched.c | 225 +++++++++++++++++++++++++--------
 event/tbgen/tevc.c        | 257 ++++++++++++++++++++++++++++++--------
 3 files changed, 397 insertions(+), 108 deletions(-)

diff --git a/event/lib/event.h b/event/lib/event.h
index 989d8183e4..3fcf08b24e 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 96c344bff9..396fa870e5 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 7d0ce3c3b6..7c0c8ce3d1 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);
-- 
GitLab