From bfa3107b71951961644d5ddc4f0ff3e54abd0700 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sat, 8 Aug 2009 18:25:33 +0100 Subject: [PATCH] Hostinfo day 4: Implement command processing code. --- README | 29 +-- configure.ac | 2 +- hostinfo-set/Makefile.am | 2 + hostinfo-status/Makefile.am | 2 + hostinfo-test/Makefile.am | 2 + hostinfod/Makefile.am | 5 +- hostinfod/commands.c | 477 +++++++++++++++++++++++++++++++++++++++- hostinfod/hostinfo-protocol.pod | 32 ++- hostinfod/hostinfo.pod | 3 + hostinfod/hostinfod.h | 61 ++++- hostinfod/main.c | 104 +++++++-- hostinfod/ping.c | 57 +++++ 12 files changed, 713 insertions(+), 63 deletions(-) create mode 100644 hostinfod/ping.c diff --git a/README b/README index 2b8a7a8..6a6f62b 100644 --- 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 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. diff --git a/configure.ac b/configure.ac index 125eb28..cc05e37 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/hostinfo-set/Makefile.am b/hostinfo-set/Makefile.am index e9fe279..f3602fd 100644 --- a/hostinfo-set/Makefile.am +++ b/hostinfo-set/Makefile.am @@ -17,6 +17,8 @@ EXTRA_DIST = hostinfo-set.pl +CLEANFILES = hostinfo-set.8 + bin_SCRIPTS = hostinfo-set man_MANS = hostinfo-set.8 diff --git a/hostinfo-status/Makefile.am b/hostinfo-status/Makefile.am index d2e34e3..1791bac 100644 --- a/hostinfo-status/Makefile.am +++ b/hostinfo-status/Makefile.am @@ -17,6 +17,8 @@ EXTRA_DIST = hostinfo-status.pl +CLEANFILES = hostinfo-status.8 + bin_SCRIPTS = hostinfo-status man_MANS = hostinfo-status.8 diff --git a/hostinfo-test/Makefile.am b/hostinfo-test/Makefile.am index 5430676..25f9c7e 100644 --- a/hostinfo-test/Makefile.am +++ b/hostinfo-test/Makefile.am @@ -17,6 +17,8 @@ EXTRA_DIST = hostinfo-test.pl +CLEANFILES = hostinfo-test.1 + bin_SCRIPTS = hostinfo-test man_MANS = hostinfo-test.1 diff --git a/hostinfod/Makefile.am b/hostinfod/Makefile.am index 16272e0..5fc2cb1 100644 --- a/hostinfod/Makefile.am +++ b/hostinfod/Makefile.am @@ -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 \ diff --git a/hostinfod/commands.c b/hostinfod/commands.c index 1bbddc0..b928c36 100644 --- a/hostinfod/commands.c +++ b/hostinfod/commands.c @@ -27,19 +27,494 @@ #include #include +#include +#include +#include #include #include +#include +#include +#include +#include + #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; } diff --git a/hostinfod/hostinfo-protocol.pod b/hostinfod/hostinfo-protocol.pod index df838c0..c50d6e4 100644 --- a/hostinfod/hostinfo-protocol.pod +++ b/hostinfod/hostinfo-protocol.pod @@ -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 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 or -I). +Unless specified otherwise, the charset for strings is I, 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 or I. =head2 PING @@ -184,6 +191,11 @@ alpha-numeric characters ([0-9a-zA-Z]{1,16}). Returns C 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 string. -This indicates that everything is working. +The command succeeds and echos back the same C string. This +indicates that the connection through to the host daemon is working. =item * diff --git a/hostinfod/hostinfo.pod b/hostinfod/hostinfo.pod index 3fe8bc0..9bebced 100644 --- a/hostinfod/hostinfo.pod +++ b/hostinfod/hostinfo.pod @@ -384,6 +384,9 @@ Use the L 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 diff --git a/hostinfod/hostinfod.h b/hostinfod/hostinfod.h index 103783e..98fcb63 100644 --- a/hostinfod/hostinfod.h +++ b/hostinfod/hostinfod.h @@ -26,6 +26,7 @@ #include #include #include +#include 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 */ diff --git a/hostinfod/main.c b/hostinfod/main.c index 17da054..e9ca8ae 100644 --- a/hostinfod/main.c +++ b/hostinfod/main.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -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 index 0000000..4a79f8c --- /dev/null +++ b/hostinfod/ping.c @@ -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 +#endif + +#include +#include +#include + +#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) -- 1.8.3.1