Hostinfo day 4: Implement command processing code.
authorRichard W.M. Jones <rjones@redhat.com>
Sat, 8 Aug 2009 17:25:33 +0000 (18:25 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Sun, 9 Aug 2009 21:27:55 +0000 (22:27 +0100)
12 files changed:
README
configure.ac
hostinfo-set/Makefile.am
hostinfo-status/Makefile.am
hostinfo-test/Makefile.am
hostinfod/Makefile.am
hostinfod/commands.c
hostinfod/hostinfo-protocol.pod
hostinfod/hostinfo.pod
hostinfod/hostinfod.h
hostinfod/main.c
hostinfod/ping.c [new file with mode: 0644]

diff --git a/README b/README
index 2b8a7a8..6a6f62b 100644 (file)
--- a/README
+++ b/README
@@ -55,31 +55,12 @@ Accessing host information from guests
 ----------------------------------------------------------------------
 
 No special software is required inside the guest.  Simply connect to
-the serial port[1] (eg. /dev/ttyS1 or COM2) using your favorite serial
-terminal software and send commands.
+the serial port (usually, but not always, /dev/ttyS1 or COM2) using
+your favorite serial terminal software and send commands.
 
 The protocol is a simple, line-based, text protocol.  No special
 libraries are needed to use it from programs or by hand.  The commands
-that you can send are documented in the hostinfod(8) manual page.
+that you can send are documented in the hostinfo-protocol(5) manual
+page.
 
-If you get no response on the serial port at all, check on the host
-side that the guest is allowed access (see /etc/hostinfod/).  Also
-check that the serial port has been set up in libvirt:
-
-  virsh dumpxml DOMAIN
-
-(should show a <serial/> clause)
-
-Also check that hostinfod (the daemon) is running on the host.
-
-Note that serial port settings like speed, parity etc. make no
-difference for these emulated serial ports, so that won't be a factor.
-
-If you get an answer from the hostinfo daemon, but it indicates a
-permissions error, check on the host side that the guest is allowed to
-access the particular resource (see /etc/hostinfod/).
-
-[1] _Which_ serial port can vary - you need to either try them one by
-one or ask the host administrator to tell you.  We try to stay on the
-second serial port (ttyS1 or COM2) but there are some configurations
-where we get bumped up or down to other serial ports.
+For troubleshooting, see hostinfo(8) manual page.
index 125eb28..cc05e37 100644 (file)
@@ -34,7 +34,7 @@ dnl Check support for 64 bit file offsets.
 AC_SYS_LARGEFILE
 
 dnl C functions which may be missing on older systems.
-AC_CHECK_FUNCS([inotify_init1])
+AC_CHECK_FUNCS([inotify_init1 clock_gettime])
 
 dnl Check for required packages using pkg-config.
 PKG_CHECK_MODULES([HOSTINFOD],[apr-1 >= 1.3])
index e9fe279..f3602fd 100644 (file)
@@ -17,6 +17,8 @@
 
 EXTRA_DIST = hostinfo-set.pl
 
+CLEANFILES = hostinfo-set.8
+
 bin_SCRIPTS = hostinfo-set
 
 man_MANS = hostinfo-set.8
index d2e34e3..1791bac 100644 (file)
@@ -17,6 +17,8 @@
 
 EXTRA_DIST = hostinfo-status.pl
 
+CLEANFILES = hostinfo-status.8
+
 bin_SCRIPTS = hostinfo-status
 
 man_MANS = hostinfo-status.8
index 5430676..25f9c7e 100644 (file)
@@ -17,6 +17,8 @@
 
 EXTRA_DIST = hostinfo-test.pl
 
+CLEANFILES = hostinfo-test.1
+
 bin_SCRIPTS = hostinfo-test
 
 man_MANS = hostinfo-test.1
index 16272e0..5fc2cb1 100644 (file)
@@ -17,6 +17,8 @@
 
 EXTRA_DIST = hostinfo.pod hostinfo-protocol.pod
 
+CLEANFILES = hostinfo.8 hostinfo-protocol.5
+
 sbin_PROGRAMS = hostinfod
 
 hostinfod_SOURCES = \
@@ -25,7 +27,8 @@ hostinfod_SOURCES = \
        error.c \
        hostinfod.h \
        main.c \
-       monitor_sockets.c
+       monitor_sockets.c \
+       ping.c
 
 hostinfod_CFLAGS = \
        -Wall \
index 1bbddc0..b928c36 100644 (file)
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <ctype.h>
 #include <unistd.h>
 #include <time.h>
 
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+
 #include "hostinfod.h"
 
+#define PROTOCOL_VERSION "1.0"
+#define CRLF "\r\n"
+
+apr_hash_t *commands = NULL;
+
+static const char *string_of_arg_type (enum arg_type);
+static int not_printable (const char *str);
+static int contains_crlf (const char *str);
+static char *parse_c_string (struct guest_description *hval, const char *str, size_t *posn);
+static int parse_long (struct guest_description *hval, const char *str, size_t *posn, long *ret);
+
 void
-execute_command (time_t now,
+execute_command (const struct timespec *now,
                 struct guest_description *hval, const char *command)
 {
+  size_t len, i, j;
+  int neg;
+  char *cmd;
+  apr_array_header_t *args;
+  struct arg arg;
+  command_fn fn;
+
   debug ("%s: %s", hval->name, command);
 
+  /* Create a new pool for allocation during the lifetime of the
+   * request/response.  **NB** the hval->reply field is allocated
+   * from this pool, which is why it gets nulled below.
+   */
+  if (hval->rpool)
+    apr_pool_destroy (hval->rpool);
+  hval->rpool = NULL;
+  hval->reply = NULL;
+  hval->reply_size = hval->reply_posn = 0;
+  apr_pool_create (&hval->rpool, hval->pool);
+
+  /* Split up the command.  Commands have a very narrowly-defined
+   * format, and we reject any malformed commands with a 400 error.
+   *
+   * NB. A lot of the code below assumes 7 bit, printable ASCII,
+   * and this is only safe because of the call to 'not_printable'
+   * here.
+   */
+  len = strlen (command);
+  if (len < 1 || len > 4094 || not_printable (command)) {
+    warning ("%s: command too short, too long or contained unprintable chars (len = %d)",
+            hval->name, len);
+    send_error (hval, 400);
+    return;
+  }
+
+  /* Command is alphanumeric string, non-zero length. */
+  i = strspn (command,
+             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+             "abcdefghijklmnopqrstuvwxyz"
+             "0123456789");
+  if (i < 1) {
+    warning ("%s: no command part in request", hval->name);
+    send_error (hval, 400);
+    return;
+  }
+
+  cmd = apr_pstrmemdup (hval->rpool, command, i);
+  for (j = 0; j < i; ++j)
+    cmd[j] = tolower (cmd[j]);
+
+  args = apr_array_make (hval->rpool, 0, sizeof (struct arg));
+
+  while (command[i]) { /* command[i] is the space before the next arg */
+    if (command[i] != ' ') {
+      warning ("%s: there must be a single space between command and each argument",
+              hval->name);
+      send_error (hval, 400);
+      return;
+    }
+
+    neg = 0;
+
+    i++;
+    switch (command[i]) {
+    case '\0':
+      warning ("%s: trailing space after command", hval->name);
+      send_error (hval, 400);
+      return;
+
+    case '"':                  /* string literal */
+      arg.type = arg_type_string;
+      arg.u.str = parse_c_string (hval, command, &i);
+      if (!arg.u.str) {
+       send_error (hval, 400);
+       return;
+      }
+      break;
+
+    case '-':                  /* integer literal */
+      neg = 1;
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      arg.type = arg_type_int;
+      if (parse_long (hval, command, &i, &arg.u.i) == -1) {
+       send_error (hval, 400);
+       return;
+      }
+      break;
+
+    case 't':                  /* boolean */
+    case 'f':
+    case 'T':
+    case 'F':
+      arg.type = arg_type_bool;
+      if (strncasecmp (&command[i], "true", 4) == 0) {
+       arg.u.i = 1;
+       i += 4;
+      } else if (strncasecmp (&command[i], "false", 5) == 0) {
+       arg.u.i = 0;
+       i += 5;
+      } else
+       goto unknown_arg;
+      break;
+
+    default:
+    unknown_arg:
+      warning ("%s: unknown or malformed argument starting at position %d ('%c')",
+              hval->name, i, command[i]);
+      send_error (hval, 400);
+      return;
+    } /* switch */
+
+    APR_ARRAY_PUSH (args, struct arg) = arg;
+  }
+
+  if (verbose) {
+    /* Debug output what the guest sent / what we decoded. */
+    debug ("%s: command '%s' with %d args", hval->name, cmd, args->nelts);
+    for (i = 0; i < args->nelts; ++i) {
+      struct arg arg = APR_ARRAY_IDX (args, i, struct arg);
+
+      switch (arg.type) {
+      case arg_type_bool:
+      case arg_type_int:
+       debug ("%s: arg %d : %s = %ld",
+              hval->name, i, string_of_arg_type (arg.type), arg.u.i);
+       break;
+      case arg_type_string:
+       debug ("%s: arg %d : %s = %s",
+              hval->name, i, string_of_arg_type (arg.type), arg.u.str);
+       break;
+      }
+    }
+  }
+
+  /* Know about this command? */
+  fn = apr_hash_get (commands, cmd, APR_HASH_KEY_STRING);
+  if (!fn) {
+    send_error (hval, 404);
+    return;
+  }
+
+  /* Before dispatching the command, check the command is enabled
+   * and guest is not calling it too frequently.
+   */
+  error ("XXXXXXX enabled check not implemented XXXXXXX");
+  error ("XXXXXXX frequency check not implemented XXXXXXX");
+
+  /* Dispatch the command. */
+  fn (hval, cmd, args);
+}
+
+/* All commands must consist only of printable 7 bit ASCII.
+ * NB. Don't use isprint(3).
+ */
+static int
+not_printable (const char *str)
+{
+  int c;
+
+  while ((c = *(unsigned char *)str)) {
+    if (c < 32 || c > 126)
+      return 1;
+    str++;
+  }
+  return 0;
+}
+
+static int
+contains_crlf (const char *str)
+{
+  int c;
+
+  while ((c = *(unsigned char *)str)) {
+    if (c == '\r' || c == '\n')
+      return 1;
+    str++;
+  }
+  return 0;
+}
+
+static int
+hexval (int c)
+{
+  switch (c) {
+  case '0': case '1': case '2': case '3': case '4':
+  case '5': case '6': case '7': case '8': case '9':
+    return c - '0';
+  case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+    return c - 'a' + 10;
+  case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+    return c - 'A' + 10;
+  default:
+    abort ();
+  }
+}
+
+static char *
+parse_c_string (struct guest_description *hval, const char *str, size_t *posn)
+{
+  apr_array_header_t *r;
+  int c;
+
+  r = apr_array_make (hval->rpool, 0, 1);
+#define APPEND(c) APR_ARRAY_PUSH (r,char) = (c);
+
+  assert (str[*posn] == '"');
+  (*posn)++;
+
+  while (str[*posn] != '"') {
+    if (str[*posn] == '\\') {  /* Start of \escape sequence. */
+      switch (str[*posn+1]) {
+      case '0': case '1': case '2': case '3':
+       if ((str[*posn+2] >= '0' && str[*posn+2] <= '7') &&
+           (str[*posn+3] >= '0' && str[*posn+3] <= '7')) {
+         c = (str[*posn+1] - '0') * 0100;
+         c += (str[*posn+2] - '0') * 010;
+         c += str[*posn+3] - '0';
+         if (c != 0) {
+           APPEND (c);
+           *posn += 4;
+         } else {
+           warning ("%s: \\0 cannot appear in string literal", hval->name);
+           return NULL;
+         }
+         break;
+       } else {
+         warning ("%s: invalid octal sequence in string literal", hval->name);
+         return NULL;
+       }
+      case 'x':
+       if (isxdigit (str[*posn+2]) && isxdigit (str[*posn+3])) {
+         c = hexval (str[*posn+2]) * 0x10;
+         c += hexval (str[*posn+3]);
+         if (c != 0) {
+           APPEND (c);
+           *posn += 4;
+         } else {
+           warning ("%s: \\0 cannot appear in string literal", hval->name);
+           return NULL;
+         }
+         break;
+       } else {
+         warning ("%s: invalid hex sequence in string literal", hval->name);
+         return NULL;
+       }
+      case 'a':
+       APPEND ('\a');
+       *posn += 2;
+       break;
+      case 'b':
+       APPEND ('\b');
+       *posn += 2;
+       break;
+      case 'f':
+       APPEND ('\f');
+       *posn += 2;
+       break;
+      case 'n':
+       APPEND ('\n');
+       *posn += 2;
+       break;
+      case 'r':
+       APPEND ('\r');
+       *posn += 2;
+       break;
+      case 't':
+       APPEND ('\t');
+       *posn += 2;
+       break;
+      case 'v':
+       APPEND ('\v');
+       *posn += 2;
+       break;
+      case '"':
+       APPEND ('"');
+       *posn += 2;
+       break;
+      case '\\':
+       APPEND ('\\');
+       *posn += 2;
+       break;
+
+      default:
+       warning ("%s: unterminated escape sequence in string literal",
+                hval->name);
+       return NULL;
+      }
+    } else if (str[*posn] == '\0') { /* Unterminated string literal. */
+      warning ("%s: unterminated string literal in request", hval->name);
+      return NULL;
+    } else {                   /* Ordinary character. */
+      APPEND (str[*posn]);
+      (*posn)++;
+    }
+  }
+
+  /* Finish off the string and return it. */
+  APPEND ('\0');
+  (*posn)++;                   /* Skips over the final quote. */
+
+  return r->elts;
+}
+
+static int
+parse_long (struct guest_description *hval,
+           const char *str, size_t *posn, long *ret)
+{
+  error ("XXXXXXX parse_long not implemented XXXXXXX");
+  return -1;
+}
+
+/* For single line replies. */
+void
+send_reply (struct guest_description *hval, int code, const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+
+  /* All success codes must be 2xx. */
+  assert (code >= 200 && code < 300);
+
+  va_start (args, fs);
+  msg = apr_pvsprintf (hval->rpool, fs, args);
+  va_end (args);
+
+  /* The result string must not contain any CR or LF characters.  If
+   * not it's an internal error in the command, or else the caller has
+   * (somehow) managed to pass a bad string through.
+   */
+  if (contains_crlf (msg)) {
+    error ("%s: send_reply: refusing the send a reply message containing CR/LF characters.  This is a serious internal error in the current command.",
+          hval->name);
+    send_error (hval, 500);
+    return;
+  }
+
+  /* Send reply. */
+  hval->reply =
+    apr_psprintf (hval->rpool, PROTOCOL_VERSION " %03d %s" CRLF,
+                 code, msg);
+  hval->reply_size = strlen (hval->reply);
+  hval->reply_posn = 0;
+  hval->state = guest_state_reply;
+}
+
+void
+send_error (struct guest_description *hval, int code)
+{
+  const char *msg;
+
+  /* All errors must be 4xx or 5xx. */
+  assert (code >= 400 && code < 600);
+
+  /* NB: If you add a code, update COMMON STATUS CODES section
+   * in hostinfo-protocol.pod.
+   */
+  switch (code) {
+  case 400: msg = "Bad request"; break;
+  case 401: msg = "Command disabled"; break;
+  case 404: msg = "Command not found"; break;
+  case 406: msg = "Too frequent"; break;
+  case 500: msg = "Internal server error"; break;
+  default:  msg = "Unknown error"; break;
+  }
+
+  /* Construct the reply. */
+  hval->reply =
+    apr_psprintf (hval->rpool, PROTOCOL_VERSION " %03d %s" CRLF,
+                 code, msg);
+  hval->reply_size = strlen (hval->reply);
+  hval->reply_posn = 0;
+  hval->state = guest_state_reply;
+
+  /* Penalty is always increased on errors, to ensure the guest
+   * cannot flood us with invalid commands.
+   */
+  hval->penalty++;
+}
+
+static const char *
+string_of_arg_type (enum arg_type t)
+{
+  switch (t) {
+  case arg_type_string: return "string";
+  case arg_type_int:    return "int";
+  case arg_type_bool:   return "bool";
+  }
+  abort ();
+}
+
+int
+get_args (apr_array_header_t *args, const char *argfs, ...)
+{
+  va_list vargs;
+  int i = 0, ret = 0;
+
+  va_start (vargs, argfs);
+
+  while (*argfs) {
+    struct arg arg;
+
+    if (i >= args->nelts) {
+      ret = -1;
+      goto end;
+    }
+
+    arg = APR_ARRAY_IDX (args, i, struct arg);
+
+    switch (*argfs) {
+    case 's':
+      if (arg.type == arg_type_string)
+       * va_arg (vargs, const char **) = arg.u.str;
+      else {
+       ret = -1;
+       goto end;
+      }
+      break;
+
+    case 'i':
+      if (arg.type == arg_type_int)
+       * va_arg (vargs, int *) = arg.u.i;
+      else {
+       ret = -1;
+       goto end;
+      }
+      break;
+
+    case 'b':
+      if (arg.type == arg_type_bool)
+       * va_arg (vargs, int *) = arg.u.i;
+      else {
+       ret = -1;
+       goto end;
+      }
+      break;
+
+    default:
+      error ("get_args: invalid character '%c'", *argfs);
+      ret = -1;
+      goto end;
+    }
 
+    argfs++;
+    i++;
+  }
 
+  if (i < args->nelts)
+    ret = -1;
 
+ end:
+  va_end (vargs);
 
+  return ret;
 }
index df838c0..c50d6e4 100644 (file)
@@ -155,20 +155,27 @@ are not case sensitive, and you can send them in lowercase or mixed
 case.
 
 The request is always a single line, always consists only of 7 bit
-printable ASCII (apart from the final CRLF), and must be less or equal
-to 4096 characters in length (that includes the final CRLF).
+printable ASCII bytes in the range 32-126 (apart from the final CRLF),
+and must be less or equal to 4096 bytes in length (that includes the
+final CRLF).
 
 Arguments that are strings I<must> be quoted (using double-quotes).
 Special characters inside the strings are escaped using backslashes.
-The rules are precisely the same as for C literal strings, so
-for example C<"\t"> is a string containing a single tab character.
+The rules are precisely the same as for C literal strings, so for
+example C<"\t"> is a string containing a single tab character.
 
-Arguments that are integers appear as integer literals.
+Strings may not contain C<\0> characters in the middle, nor can they
+be NULL.
 
-Other argument types that are allowed are: booleans (I<true> or
-I<false>).
+Unless specified otherwise, the charset for strings is I<UTF-8>, but
+any bytes outside the range 32-126 must be sent as escape sequences,
+eg. C<"\xC2\xA3"> would encode the pound (currency) sign.
 
-Note that integers, booleans must never be quoted.
+Arguments that are integers appear as integer literals, with optional
+minus sign (C<->) before.  As with C, use a I<0> to indicate octal and
+I<0x> to indicate hexadecimal literals.
+
+Boolean arguments should be sent as I<true> or I<false>.
 
 =head2 PING
 
@@ -184,6 +191,11 @@ alpha-numeric characters ([0-9a-zA-Z]{1,16}).
 
 Returns C<echodata> back to the caller.
 
+=head3 Example
+
+ >>> PING "hello"
+ <<< 1.0 200 hello
+
 =head3 Description
 
 This command is used to test the hostinfo connection.
@@ -194,8 +206,8 @@ The possible responses to this are:
 
 =item *
 
-The command succeeds and echos back the same C<echodata> string.
-This indicates that everything is working.
+The command succeeds and echos back the same C<echodata> string.  This
+indicates that the connection through to the host daemon is working.
 
 =item *
 
index 3fe8bc0..9bebced 100644 (file)
@@ -384,6 +384,9 @@ Use the L<hostinfo-test(8)> program in the guest to test this.
 
 =back
 
+Note that serial port settings like speed, parity etc. make no
+difference for these emulated serial ports, so that won't be a factor.
+
 =head1 FILES
 
 =over 4
index 103783e..98fcb63 100644 (file)
@@ -26,6 +26,7 @@
 #include <apr_pools.h>
 #include <apr_network_io.h>
 #include <apr_poll.h>
+#include <apr_hash.h>
 
 enum guest_state {
   guest_state_connecting,      /* Connecting to socket. */
@@ -35,27 +36,43 @@ enum guest_state {
 };
 
 struct guest_description {
-  int counter;
   apr_pool_t *pool;            /* Pool for lifetime of guest connection. */
+
+  apr_pool_t *rpool;           /* Pool for lifetime of single req/resp. */
+
   const char *name;            /* "driver-name" */
   const char *sock_path;       /* Full path to socket. */
   int sock;                    /* Real socket. */
   apr_socket_t *aprsock;       /* APR socket. */
   apr_pollfd_t pollfd;         /* APR poll descriptor. */
   enum guest_state state;      /* State of the connection. */
-
-  /* Increments every time guest does something bad, decremented once per min */
-  unsigned penalty;
-  time_t last_penalty_decr;
+  int counter;                 /* Used in reread_socket_dir. */
 
   unsigned request_max;                /* Max. length of request buffer. */
   unsigned request_posn;       /* Position in request buffer. */
   char *request;               /* Request buffer. */
 
-  unsigned reply_alloc;                /* Allocated for reply buffer. */
-  unsigned reply_size;         /* Size used in reply buffer. */
+  unsigned reply_size;         /* Size of reply buffer. */
   unsigned reply_posn;         /* Position in reply buffer. */
   char *reply;                 /* Reply buffer. */
+
+  /* Increments every time guest does something bad, decremented once per min */
+  unsigned penalty;
+  struct timespec last_penalty_decr;
+};
+
+enum arg_type {
+  arg_type_string,
+  arg_type_int,
+  arg_type_bool,
+};
+
+struct arg {
+  enum arg_type type;
+  union {
+    long i;                    /* For int, bool types. */
+    char *str;                 /* For string type. */
+  } u;
 };
 
 /* main.c */
@@ -69,6 +86,8 @@ extern int foreground_set_on_cmdline;
 extern int messages_to_stderr;
 extern apr_pool_t *pool;       /* pool for global memory allocation */
 
+extern void initialize (void);
+
 /* error.c */
 extern void init_syslog (void);
 extern void error (const char *fs, ...)
@@ -94,6 +113,32 @@ extern int sockets_inotify_fd;
 extern void monitor_socket_dir (void);
 
 /* commands.c */
-extern void execute_command (time_t now, struct guest_description *hval, const char *command);
+extern apr_hash_t *commands;
+extern void execute_command (const struct timespec *now, struct guest_description *hval, const char *command);
+extern int get_args (apr_array_header_t *args, const char *argfs, ...);
+extern void send_error (struct guest_description *hval, int code);
+extern void send_reply (struct guest_description *hval, int code, const char *fs, ...)
+  __attribute__((format (printf,3,4)));
+
+typedef void (*command_fn) (struct guest_description *hval, const char *cmd, apr_array_header_t *args);
+
+#define REGISTER_COMMAND(name)                                         \
+  static void register_##name (void) __attribute__((constructor));     \
+                                                                       \
+  static void                                                          \
+  register_##name (void)                                               \
+  {                                                                    \
+    if (pool == NULL)                                                  \
+      initialize ();                                                   \
+                                                                       \
+    if (commands == NULL)                                              \
+      commands = apr_hash_make (pool);                                 \
+                                                                       \
+    /* The assignment below just causes a warning                      \
+     * if the callback has the wrong type.                             \
+     */                                                                        \
+    command_fn cb = name;                                              \
+    apr_hash_set (commands, #name, APR_HASH_KEY_STRING, cb);           \
+  }
 
 #endif /* HOSTINFOD_H */
index 17da054..e9ca8ae 100644 (file)
@@ -27,6 +27,7 @@
 #include <dirent.h>
 #include <time.h>
 #include <sys/types.h>
+#include <sys/time.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/un.h>
@@ -50,6 +51,7 @@ static void do_reread_socket_dir (void);
 static struct guest_description *guest_added (const char *sock_path, const char *name);
 static void guest_removed (struct guest_description *);
 static void guest_event (const apr_pollfd_t *, void *);
+static void modify_pollfd_reqevents (struct guest_description *, apr_int16_t);
 
 const char *conf_file = DEFAULT_CONF_FILE;
 char *socket_dir = NULL;
@@ -95,6 +97,14 @@ usage (void)
          DEFAULT_CONF_FILE);
 }
 
+void
+initialize (void)
+{
+  apr_initialize ();
+  apr_pool_create (&pool, NULL);
+  init_syslog ();
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -110,13 +120,16 @@ main (int argc, char *argv[])
   int c;
   const char *optarg;
 
-  apr_initialize ();
-  apr_pool_create (&pool, NULL);
+  /* REGISTER_COMMAND macro should have caused this to be
+   * initialized.  If it's not, then something is badly wrong ...
+   */
+  if (!pool) {
+    error ("internal error: daemon not initialized - no commands registered");
+    exit (1);
+  }
 
   apr_getopt_init (&opt, pool, argc, argv);
 
-  init_syslog ();
-
   socket_dir = apr_pstrdup (pool, DEFAULT_SOCKET_DIR);
   guests_file = apr_pstrdup (pool, DEFAULT_GUESTS_FILE);
 
@@ -168,7 +181,7 @@ main (int argc, char *argv[])
     paprerror (r, "apr_pollset_create");
     exit (1);
   }
-  apr_socket_t *tsock;
+  apr_socket_t *tsock = NULL;
   r = apr_os_sock_put (&tsock, &sockets_inotify_fd, pool);
   if (r != APR_SUCCESS) {
     paprerror (r, "apr_os_sock_put");
@@ -228,6 +241,8 @@ main_loop (void)
     }
 
     /* Poll. */
+    numdescs = 0;
+    descs = NULL;
     r = apr_pollset_poll (set, -1, &numdescs, &descs);
     if (r != APR_SUCCESS) {
       paprerror (r, "apr_pollset_poll");
@@ -369,7 +384,7 @@ guest_added (const char *sock_path, const char *name)
   int r;
   unsigned retries = 0, tns;
   enum guest_state state;
-  apr_pool_t *guest_pool;
+  apr_pool_t *guest_pool = NULL;
   struct sockaddr_un addr;
   struct timespec ts;
 
@@ -490,7 +505,7 @@ guest_removed (struct guest_description *hval)
   /* Unregister the socket from the pollset. */
   r = apr_pollset_remove (set, &hval->pollfd);
   if (r != APR_SUCCESS)
-    paprerror (r, "apr_pollset_remove for %s", hval->name);
+    paprerror (r, "%s: apr_pollset_remove", hval->name);
 
   if (close (hval->sock) == -1)
     pwarningf ("close: %s", hval->sock_path);
@@ -514,6 +529,22 @@ guest_force_close (struct guest_description *hval)
   guest_removed (hval);
 }
 
+/* Difference between two timespec structures (r = a - b) */
+static struct timespec *
+diff_timespec (struct timespec *r,
+              const struct timespec *a, const struct timespec *b)
+{
+  if (a->tv_nsec - b->tv_nsec < 0) {
+    r->tv_sec = a->tv_sec - b->tv_sec - 1;
+    r->tv_nsec = 1000000000 + a->tv_nsec - b->tv_nsec;
+  } else {
+    r->tv_sec = a->tv_sec - b->tv_sec;
+    r->tv_nsec = a->tv_nsec - b->tv_nsec;
+  }
+
+  return r;
+}
+
 /* This is called when there is some event from the guest, eg.
  * connection finished, read, write or closed.
  */
@@ -524,9 +555,16 @@ guest_event (const apr_pollfd_t *pollfd, void *hvalv)
   int err, max, r, extra;
   socklen_t len;
   char *p;
-  time_t now;
-
-  time (&now);
+  struct timespec now;
+
+#ifdef HAVE_CLOCK_GETTIME
+  clock_gettime (CLOCK_MONOTONIC, &now);
+#else
+  struct timeval tv;
+  gettimeofday (&tv, NULL);
+  now.tv_sec = tv.tv_sec;
+  now.tv_nsec = tv.tv_usec * 1000;
+#endif
 
   /* If the guest keeps doing bad stuff, eventually lose patience with it. */
   if (hval->penalty >= 100) {
@@ -537,9 +575,15 @@ guest_event (const apr_pollfd_t *pollfd, void *hvalv)
   }
 
   /* Decrement the penalty once a minute, so the guest can recover. */
-  if (hval->penalty > 0 && now - hval->last_penalty_decr >= 60) {
-    hval->penalty--;
-    hval->last_penalty_decr = now;
+  if (hval->penalty > 0) {
+    struct timespec diff;
+
+    diff_timespec (&diff, &now, &hval->last_penalty_decr);
+
+    if (diff.tv_sec >= 60) {
+      hval->penalty--;
+      hval->last_penalty_decr = now;
+    }
   }
 
   switch (hval->state) {
@@ -621,7 +665,7 @@ guest_event (const apr_pollfd_t *pollfd, void *hvalv)
 
     *(p+1) = '\0';
 
-    execute_command (now, hval, hval->request);
+    execute_command (&now, hval, hval->request);
 
     hval->request_posn = 0;
     break;
@@ -662,16 +706,40 @@ guest_event (const apr_pollfd_t *pollfd, void *hvalv)
    */
   switch (hval->state) {
   case guest_state_connecting:
-    hval->pollfd.reqevents = APR_POLLOUT;
+    modify_pollfd_reqevents (hval, APR_POLLOUT);
     break;
   case guest_state_request:
-    hval->pollfd.reqevents = APR_POLLIN;
+    modify_pollfd_reqevents (hval, APR_POLLIN);
     break;
   case guest_state_reply:
-    hval->pollfd.reqevents = APR_POLLOUT;
+    modify_pollfd_reqevents (hval, APR_POLLOUT);
     break;
   case guest_state_dead:
-    hval->pollfd.reqevents = 0;
+    modify_pollfd_reqevents (hval, 0);
     break;
   }
 }
+
+/* It turns out you can't just update the pollfd->reqevents
+ * field.  Instead you have to remove the pollfd and reregister
+ * it in the pollset.
+ */
+static void
+modify_pollfd_reqevents (struct guest_description *hval,
+                        apr_int16_t new_reqevents)
+{
+  apr_status_t r;
+
+  if (hval->pollfd.reqevents != new_reqevents) {
+    r = apr_pollset_remove (set, &hval->pollfd);
+    if (r != APR_SUCCESS) {
+      paprerror (r, "%s: apr_pollset_remove", hval->name);
+      return;
+    }
+
+    hval->pollfd.reqevents = new_reqevents;
+    r = apr_pollset_add (set, &hval->pollfd);
+    if (r != APR_SUCCESS)
+      paprerror (r, "%s: apr_pollset_add", hval->name);
+  }
+}
diff --git a/hostinfod/ping.c b/hostinfod/ping.c
new file mode 100644 (file)
index 0000000..4a79f8c
--- /dev/null
@@ -0,0 +1,57 @@
+/* virt-hostinfo
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "hostinfod.h"
+
+static void
+ping (struct guest_description *hval,
+      const char *cmd, apr_array_header_t *args)
+{
+  const char *echodata;
+  size_t i, len;
+
+  if (get_args (args, "s", &echodata) == -1) {
+    warning ("%s: %s: wrong number of type of arguments", hval->name, "ping");
+    send_error (hval, 400);
+    return;
+  }
+  len = strlen (echodata);
+  if (len < 1 || len > 16) {
+    send_error (hval, 400);
+    return;
+  }
+
+  for (i = 0; i < len; ++i)
+    if (!((echodata[i] >= 'a' && echodata[i] <= 'z') ||
+         (echodata[i] >= 'A' && echodata[i] <= 'Z') ||
+         (echodata[i] >= '0' && echodata[i] <= '9'))) {
+      send_error (hval, 400);
+      return;
+    }
+
+  send_reply (hval, 200, "%s", echodata);
+}
+REGISTER_COMMAND (ping)