----------------------------------------------------------------------
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.
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])
EXTRA_DIST = hostinfo-set.pl
+CLEANFILES = hostinfo-set.8
+
bin_SCRIPTS = hostinfo-set
man_MANS = hostinfo-set.8
EXTRA_DIST = hostinfo-status.pl
+CLEANFILES = hostinfo-status.8
+
bin_SCRIPTS = hostinfo-status
man_MANS = hostinfo-status.8
EXTRA_DIST = hostinfo-test.pl
+CLEANFILES = hostinfo-test.1
+
bin_SCRIPTS = hostinfo-test
man_MANS = hostinfo-test.1
EXTRA_DIST = hostinfo.pod hostinfo-protocol.pod
+CLEANFILES = hostinfo.8 hostinfo-protocol.5
+
sbin_PROGRAMS = hostinfod
hostinfod_SOURCES = \
error.c \
hostinfod.h \
main.c \
- monitor_sockets.c
+ monitor_sockets.c \
+ ping.c
hostinfod_CFLAGS = \
-Wall \
#include <stdio.h>
#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
#include <unistd.h>
#include <time.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 void init_commands_hash (void);
+static int not_printable (const char *str);
+
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",
+ hval->name);
+ 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;
+ XXX;
+ 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;
+ XXX ();
+ 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 += 4;
+ } 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 0;
+ str++;
+ }
+ return 1;
+}
+
+/* For single line replies. */
+void
+send_reply (struct guest_description *hval, int code, const char *fs, ...)
+{
+ const char *msg;
+
+ /* All success codes must be 2xx. */
+ assert (code >= 200 && code < 300);
+
+ XXX;
+}
+
+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++;
}
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
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.
=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 *
=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
};
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 */
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 void send_error (struct guest_description *hval, int code);
+
+typedef void (*command_fn) (struct guest_description *hval, const char *cmd, apr_array_header_t *args);
+
+#define REGISTER_COMMAND(name,callback) \
+ static void register_##name (void) __attribute__((constructor)); \
+ \
+ static void \
+ register_##name (void) \
+ { \
+ 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 = (callback); \
+ apr_hash_set (commands, (name), APR_HASH_KEY_STRING, cb); \
+ }
#endif /* HOSTINFOD_H */
#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>
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.
*/
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) {
}
/* 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) {
*(p+1) = '\0';
- execute_command (now, hval, hval->request);
+ execute_command (&now, hval, hval->request);
hval->request_posn = 0;
break;
--- /dev/null
+/* 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 ("s", &echodata)) {
+ 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 (
+
+}
+REGISTER_COMMAND ("ping", ping)