bootlog.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* bootlog() - error notification and progress reporting for
* WAN boot components
*/
#include <sys/varargs.h>
#include <sys/types.h>
#include <sys/strlog.h>
#include <sys/wanboot_impl.h>
#include <errno.h>
#include <time.h>
#include <boot_http.h>
#include <stdio.h>
#include <parseURL.h>
#include <bootlog.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <libintl.h>
#include <netboot_paths.h>
#include <wanboot_conf.h>
#include <bootinfo.h>
#ifdef _BOOT
#include <sys/bootdebug.h>
#endif
static struct code pri_names[] = {
"panic", BOOTLOG_EMERG,
"alert", BOOTLOG_ALERT,
"crit", BOOTLOG_CRIT,
"warn", BOOTLOG_WARNING,
"info", BOOTLOG_INFO,
"debug", BOOTLOG_DEBUG,
"verbose", BOOTLOG_VERBOSE,
"progress", BOOTLOG_PROGRESS,
"none", NOPRI,
NULL, -1
};
typedef enum {
BL_NO_TRANSPORT,
BL_LOCAL_FILE,
BL_CONSOLE,
BL_HTTP,
BL_HTTPS
} bl_transport_t;
typedef struct list_entry {
char message[BOOTLOG_QS_MAX];
struct list_entry *flink;
} list;
#define BOOTLOG_RING_NELEM 512
static struct ringbuffer_t {
int w_ptr;
int r_ptr;
list entries[BOOTLOG_RING_NELEM];
} ringbuffer;
static FILE *bl_filehandle = NULL;
static http_handle_t bl_httphandle = NULL;
static url_t bl_url;
static bl_transport_t bl_transport = BL_NO_TRANSPORT;
static bl_transport_t openbootlog(void);
static boolean_t setup_con(http_handle_t, boolean_t, boolean_t);
static char *url_encode(const char *);
static boolean_t sendmessage(bl_transport_t, char *, const char *,
bootlog_severity_t, int);
static int ptr_incr(int ptr);
static int ptr_decr(int ptr);
static void rb_init(struct ringbuffer_t *);
static void rb_write(struct ringbuffer_t *, const char *);
static int rb_read(struct ringbuffer_t *, char *);
/*
* Return a string representing the current time; not thread-safe.
*/
static const char *
gettime(void)
{
static char timebuf[sizeof ("Tue Jan 19 03:14:07 2038\n")];
time_t curtime;
if (time(&curtime) == 0)
return ("<time unavailable>");
(void) strlcpy(timebuf, ctime(&curtime), sizeof (timebuf));
timebuf[19] = '\0'; /* truncate before "2038" above */
return (timebuf);
}
/*
* bootlog_common() - Common routine used by bootlog() and
* bootlog_internal() to write a message comprising a message
* header and a message body to the appropriate transport.
* The message header comprises an ident string and a message
* severity.
*/
static void
bootlog_common(const char *ident, bootlog_severity_t severity, char *message)
{
bl_transport_t entry_transport;
static int blrecurs;
static int blretry;
/*
* This function may be called recursively because the HTTP code
* is a bootlog consumer. The blrecurs variable is used to determine
* whether or not the invocation is recursive.
*/
blrecurs++;
entry_transport = bl_transport;
/*
* If this is the first bootlog call then setup the transport.
* We only do this in a non-recursive invocation as openbootlog()
* results in a recursive call for a HTTP or HTTPS transport.
*/
if (bl_transport == BL_NO_TRANSPORT && blrecurs == 1) {
rb_init(&ringbuffer);
bl_transport = openbootlog();
}
/*
* If we're not there already, try to move up a level.
* This is necessary because our consumer may have begun
* logging before it had enough information to initialize
* its HTTP or HTTPS transport. We've arbitrarily decided
* that we'll only check to see if we should move up, on
* every third (blretry) non-recursive invocation.
*/
if (blrecurs == 1 &&
!(bl_transport == BL_HTTPS || bl_transport == BL_HTTP)) {
if (blretry > 3) {
bl_transport = openbootlog();
blretry = 0;
} else
blretry++;
}
if (entry_transport != bl_transport) {
switch (bl_transport) {
case BL_CONSOLE:
(void) printf(
"%s wanboot info: WAN boot messages->console\n",
gettime());
break;
case BL_HTTP:
case BL_HTTPS:
(void) printf(
"%s wanboot info: WAN boot messages->%s:%u\n",
gettime(), bl_url.hport.hostname,
bl_url.hport.port);
break;
default:
break;
}
}
/*
* Failed attempts and recursively generated log messages are
* sent to the fallback transport.
*/
if (blrecurs > 1 || !sendmessage(bl_transport, message, ident,
severity, 0)) {
/*
* Fallback to a log file if one exists, or the console
* as a last resort. Note that bl_filehandle will always
* be NULL in standalone.
*/
(void) sendmessage(bl_filehandle != NULL ? BL_LOCAL_FILE :
BL_CONSOLE, message, ident, severity, 1);
}
blrecurs--;
}
/*
* bootlog() - the exposed interface for logging boot messages.
*/
/* PRINTFLIKE3 */
void
bootlog(const char *ident, bootlog_severity_t severity, char *fmt, ...)
{
char message[BOOTLOG_MSG_MAX_LEN];
va_list adx;
va_start(adx, fmt);
(void) vsnprintf(message, BOOTLOG_MSG_MAX_LEN, fmt, adx);
va_end(adx);
bootlog_common(ident, severity, message);
}
/*
* libbootlog() - an internal interface for logging boot
* messages.
*/
/* PRINTFLIKE2 */
void
libbootlog(bootlog_severity_t severity, char *fmt, ...)
{
char message[BOOTLOG_MSG_MAX_LEN];
va_list adx;
va_start(adx, fmt);
(void) vsnprintf(message, BOOTLOG_MSG_MAX_LEN,
dgettext(TEXT_DOMAIN, fmt), adx);
va_end(adx);
bootlog_common("libwanboot", severity, message);
}
static boolean_t
send_http(void)
{
http_respinfo_t *resp = NULL;
char buffer[BOOTLOG_MAX_URL + (BOOTLOG_QS_MAX * 3)];
char ringmessage[BOOTLOG_QS_MAX];
char *lenstr;
size_t length;
int retries;
while ((rb_read(&ringbuffer, ringmessage) != -1)) {
(void) snprintf(buffer, sizeof (buffer), "%s?%s",
bl_url.abspath, url_encode(ringmessage));
for (retries = 0; retries < BOOTLOG_CONN_RETRIES; retries++) {
if (retries > 0) {
(void) http_srv_disconnect(bl_httphandle);
if (http_srv_connect(bl_httphandle) != 0)
continue;
}
if (http_get_request(bl_httphandle, buffer) != 0 ||
http_process_headers(bl_httphandle, &resp) != 0)
continue;
if (resp->code != 200) {
http_free_respinfo(resp);
continue;
}
http_free_respinfo(resp);
lenstr = http_get_header_value(bl_httphandle,
"Content-Length");
length = strtol(lenstr, NULL, 10);
if (http_read_body(bl_httphandle, buffer, length) > 0)
break;
}
/*
* The attempt to log the message failed. Back the
* read pointer up so that we'll try to log it again
* later.
*/
if (retries == BOOTLOG_CONN_RETRIES) {
ringbuffer.r_ptr = ptr_decr(ringbuffer.r_ptr);
return (B_FALSE);
}
}
return (B_TRUE);
}
static boolean_t
sendmessage(bl_transport_t transport, char *message, const char *ident,
bootlog_severity_t severity, int failure)
{
static char *progtype = NULL;
char ringmessage[BOOTLOG_QS_MAX];
char hostname[MAXHOSTNAMELEN];
uint32_t msgid;
boolean_t ret;
int i;
/*
* In standalone, only log VERBOSE and DEBUG messages if the
* corresponding flag (-V or -d) has been passed to boot.
*
* Note that some bootlog() consumers impose additional constraints on
* printing these messages -- for instance, http_set_verbose() must be
* used before the HTTP code will call bootlog() with BOOTLOG_VERBOSE
* messages.
*/
#ifdef _BOOT
if (severity == BOOTLOG_DEBUG && !(boothowto & RB_DEBUG))
return (B_TRUE);
if (severity == BOOTLOG_VERBOSE && !verbosemode)
return (B_TRUE);
#endif
for (i = 0; pri_names[i].c_val != NOPRI; i++) {
if (severity == pri_names[i].c_val)
break;
}
/*
* VERBOSE and DEBUG messages always go to the console
*/
if (transport != BL_CONSOLE &&
(severity == BOOTLOG_DEBUG || severity == BOOTLOG_VERBOSE)) {
(void) printf("%s %s %s: %s\n", gettime(), ident,
pri_names[i].c_name, message);
}
STRLOG_MAKE_MSGID(message, msgid);
(void) gethostname(hostname, sizeof (hostname));
/*
* Note that in this case, "<time>" is a placeholder that will be used
* to fill in the actual time on the remote end.
*/
(void) snprintf(ringmessage, sizeof (ringmessage),
"<time> %s %s: [ID %u user.%s] %s", hostname, ident, msgid,
pri_names[i].c_name, message);
/*
* Prevent duplicate messages from being inserted into
* the ring buffer.
*/
if (failure == 0) {
rb_write(&ringbuffer, ringmessage);
}
switch (transport) {
case BL_CONSOLE:
/*
* PROGRESS messages update in-place on the console, as long
* as they are of the same 'progress type' (see below) --
* if not, reset the progress information.
*/
if (progtype != NULL && (severity != BOOTLOG_PROGRESS ||
strncmp(progtype, message, strlen(progtype)) != 0)) {
(void) printf("\n");
free(progtype);
progtype = NULL;
}
(void) printf("%s %s %s: %s\r", gettime(), ident,
pri_names[i].c_name, message);
if (severity != BOOTLOG_PROGRESS) {
(void) printf("\n");
} else if (progtype == NULL) {
/*
* New progress message; save its "type" (the part
* of the message up to and including the first
* colon). This should be made less clumsy in the
* future.
*/
progtype = strdup(message);
if (progtype != NULL) {
for (i = 0; progtype[i] != '\0'; i++) {
if (progtype[i] == ':') {
progtype[++i] = '\0';
break;
}
}
}
}
ret = B_TRUE;
break;
case BL_LOCAL_FILE:
if (bl_filehandle == NULL)
return (B_FALSE);
(void) fprintf(bl_filehandle, "%s %s %s: [ID %u user.%s] %s\n",
gettime(), hostname, ident, msgid, pri_names[i].c_name,
message);
ret = B_TRUE;
break;
case BL_HTTP:
case BL_HTTPS:
if (bl_httphandle == NULL)
return (B_FALSE);
ret = send_http();
break;
case BL_NO_TRANSPORT:
default:
ret = B_FALSE;
}
return (ret);
}
static bl_transport_t
openbootlog(void)
{
static boolean_t got_boot_logger = B_FALSE;
static boolean_t bl_url_valid = B_FALSE;
static boolean_t clientauth = B_FALSE;
static bc_handle_t bootconf_handle;
bl_transport_t transport;
/*
* We try to use a logfile in userland since our consumer (install)
* needs complete control over the terminal.
*/
#ifndef _BOOT
if (bl_filehandle == NULL)
bl_filehandle = fopen("/var/log/bootlog", "a");
#endif
transport = (bl_filehandle != NULL) ? BL_LOCAL_FILE : BL_CONSOLE;
/*
* If we haven't already been able to access wanboot.conf for a
* boot_logger URL, see if we can now.
*/
if (!got_boot_logger &&
bootconf_init(&bootconf_handle, NULL) == BC_SUCCESS) {
char *urlstr;
char *cas;
/*
* If there is a boot_logger, ensure that it's is a legal URL.
*/
if ((urlstr = bootconf_get(&bootconf_handle,
BC_BOOT_LOGGER)) != NULL &&
url_parse(urlstr, &bl_url) == URL_PARSE_SUCCESS) {
bl_url_valid = B_TRUE;
}
/*
* If the boot_logger URL uses an HTTPS scheme, see if
* client authentication is specified.
*/
if (bl_url.https) {
cas = bootconf_get(&bootconf_handle,
BC_CLIENT_AUTHENTICATION);
if (cas != NULL) {
clientauth = (strcmp(cas, BC_YES) == 0);
}
}
bootconf_end(&bootconf_handle);
/*
* Having now accessed wanboot.conf, remember not to come
* this way again; the value of boot_logger cannot change.
*/
got_boot_logger = B_TRUE;
}
/*
* If there is no legal boot_logger URL available, then we're done.
*/
if (!bl_url_valid) {
return (transport);
}
/*
* If we don't already have a bl_httphandle, try to get one.
* If we fail, then we're done.
*/
if (bl_httphandle == NULL) {
bl_httphandle = http_srv_init(&bl_url);
if (bl_httphandle == NULL) {
return (transport);
}
}
/*
* If we succeed in setting up the connection,
* then we use the connection as our transport.
* Otherwise, we use the transport we've already
* determined above.
*/
if (setup_con(bl_httphandle, bl_url.https, clientauth)) {
transport = bl_url.https ? BL_HTTPS : BL_HTTP;
}
return (transport);
}
static boolean_t
setup_con(http_handle_t handle, boolean_t https, boolean_t client_auth)
{
static boolean_t got_proxy = B_FALSE;
static boolean_t proxy_valid = B_FALSE;
static url_hport_t proxy;
int i;
/*
* If an HTTPS scheme is specified, then check that time
* has been initialized.
* If time() returns a non-zero value, then we know
* that the boot file system has been mounted and that
* we have a trusted time.
*/
if (https && time(0) == 0)
return (B_FALSE);
if (!got_proxy && bootinfo_init()) {
char hpstr[URL_MAX_STRLEN];
size_t vallen = sizeof (hpstr);
/*
* If there is a http-proxy, ensure that it's a legal host:port.
*/
if (bootinfo_get(BI_HTTP_PROXY, hpstr, &vallen, NULL) ==
BI_E_SUCCESS && vallen > 0) {
hpstr[vallen] = '\0';
if (url_parse_hostport(hpstr, &proxy,
URL_DFLT_PROXY_PORT) == URL_PARSE_SUCCESS) {
proxy_valid = B_TRUE;
}
}
got_proxy = B_TRUE;
}
if (proxy_valid && http_set_proxy(handle, &proxy) != 0)
return (B_FALSE);
(void) http_set_keepalive(handle, 1);
(void) http_set_socket_read_timeout(handle, BOOTLOG_HTTP_TIMEOUT);
/*
* If an HTTPS scheme is specified, then setup the necessary
* SSL context for the connection
*/
if (https) {
if (http_set_random_file(handle, "/dev/urandom") == -1)
return (B_FALSE);
if (http_set_certificate_authority_file(NB_CA_CERT_PATH) < 0)
return (B_FALSE);
/*
* The client certificate and key will not exist unless
* client authentication has been configured. If it is
* configured then the webserver will have added these
* files to the wanboot file system and the HTTP library
* needs to be made aware of their existence.
*/
if (client_auth) {
if (http_set_client_certificate_file(handle,
NB_CLIENT_CERT_PATH) < 0) {
return (B_FALSE);
}
if (http_set_private_key_file(handle,
NB_CLIENT_KEY_PATH) < 0) {
return (B_FALSE);
}
}
if (http_set_password(handle, WANBOOT_PASSPHRASE) < 0)
return (B_FALSE);
}
for (i = 0; i < BOOTLOG_CONN_RETRIES; i++) {
if (http_srv_connect(handle) == 0)
return (B_TRUE);
(void) http_srv_disconnect(handle);
}
return (B_FALSE);
}
static char *
url_encode(const char *ibufp)
{
int i;
char c;
unsigned char nibble;
static char obuff[BOOTLOG_QS_MAX * 3];
char *obufp = obuff;
/*
* Encode special characters as outlined in RFC2396.
*
* Special characters are encoded as a triplets beginning
* with '%' followed by the two hexidecimal digits representing
* the octet code. The space character is special. It can be encoded
* simply as a '+'.
*/
while ((c = *ibufp++) != '\0') {
/*
* Is the character one of the special characters
* that require encoding? If so append '%' to the output
* buffer follow that by the hexascii value.
*/
if (strchr("/?{}|^~[]`<>#%=\"\t", c) != NULL) {
*obufp++ = '%';
/*
* Compute the character's hex value and
* convert it to ASCII. That is two nibbles
* per character.
*/
for (i = 1; i >= 0; i--) {
nibble = ((uchar_t)c >> (4 * i)) & 0x0f;
/*
* If the hex digit is 0xa - 0xf, then
* compute its ASCII value by adding 0x37
* else 0x0 - 0x9 just add 0x30.
*/
if (nibble > 0x9)
nibble += 0x37;
else
nibble += 0x30;
*obufp++ = nibble;
}
/*
* The space character gets a special mapping.
*/
} else if (c == ' ') {
*obufp++ = '+';
/*
* Append the rest (sans any CR character)
*/
} else if (c != '\n') {
*obufp++ = c;
}
}
*obufp = '\0';
return (obuff);
}
static void
rb_init(struct ringbuffer_t *buffer)
{
int i;
buffer->w_ptr = 0;
buffer->r_ptr = 0;
for (i = 0; i < BOOTLOG_RING_NELEM; i++)
buffer->entries[i].message[0] = '\0';
}
static int
ptr_incr(int ptr)
{
if (++ptr < BOOTLOG_RING_NELEM)
return (ptr);
else
return (0);
}
static int
ptr_decr(int ptr)
{
if (ptr == 0)
return (BOOTLOG_RING_NELEM - 1);
else
return (--ptr);
}
static void
rb_write(struct ringbuffer_t *buffer, const char *buff)
{
(void) strlcpy(buffer->entries[buffer->w_ptr].message, buff,
BOOTLOG_QS_MAX);
buffer->w_ptr = ptr_incr(buffer->w_ptr);
if (buffer->r_ptr == buffer->w_ptr)
buffer->r_ptr = ptr_incr(buffer->r_ptr);
}
static int
rb_read(struct ringbuffer_t *buffer, char *buff)
{
if (buffer->r_ptr != buffer->w_ptr) {
(void) strlcpy(buff, buffer->entries[buffer->r_ptr].message,
BOOTLOG_QS_MAX);
buffer->r_ptr = ptr_incr(buffer->r_ptr);
return (0);
}
return (-1);
}