4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #pragma ident "%Z%%M% %I% %E% SMI"
30 * bootlog() - error notification and progress reporting for
34 #include <sys/varargs.h>
35 #include <sys/types.h>
36 #include <sys/strlog.h>
37 #include <sys/wanboot_impl.h>
40 #include <boot_http.h>
49 #include <netboot_paths.h>
50 #include <wanboot_conf.h>
53 #include <sys/bootdebug.h>
56 static struct code pri_names
[] = {
57 "panic", BOOTLOG_EMERG
,
58 "alert", BOOTLOG_ALERT
,
60 "warn", BOOTLOG_WARNING
,
62 "debug", BOOTLOG_DEBUG
,
63 "verbose", BOOTLOG_VERBOSE
,
64 "progress", BOOTLOG_PROGRESS
,
77 typedef struct list_entry
{
78 char message
[BOOTLOG_QS_MAX
];
79 struct list_entry
*flink
;
82 #define BOOTLOG_RING_NELEM 512
84 static struct ringbuffer_t
{
87 list entries
[BOOTLOG_RING_NELEM
];
90 static FILE *bl_filehandle
= NULL
;
91 static http_handle_t bl_httphandle
= NULL
;
93 static bl_transport_t bl_transport
= BL_NO_TRANSPORT
;
95 static bl_transport_t
openbootlog(void);
96 static boolean_t
setup_con(http_handle_t
, boolean_t
, boolean_t
);
97 static char *url_encode(const char *);
98 static boolean_t
sendmessage(bl_transport_t
, char *, const char *,
99 bootlog_severity_t
, int);
100 static int ptr_incr(int ptr
);
101 static int ptr_decr(int ptr
);
102 static void rb_init(struct ringbuffer_t
*);
103 static void rb_write(struct ringbuffer_t
*, const char *);
104 static int rb_read(struct ringbuffer_t
*, char *);
107 * Return a string representing the current time; not thread-safe.
112 static char timebuf
[sizeof ("Tue Jan 19 03:14:07 2038\n")];
115 if (time(&curtime
) == 0)
116 return ("<time unavailable>");
118 (void) strlcpy(timebuf
, ctime(&curtime
), sizeof (timebuf
));
119 timebuf
[19] = '\0'; /* truncate before "2038" above */
124 * bootlog_common() - Common routine used by bootlog() and
125 * bootlog_internal() to write a message comprising a message
126 * header and a message body to the appropriate transport.
127 * The message header comprises an ident string and a message
131 bootlog_common(const char *ident
, bootlog_severity_t severity
, char *message
)
133 bl_transport_t entry_transport
;
138 * This function may be called recursively because the HTTP code
139 * is a bootlog consumer. The blrecurs variable is used to determine
140 * whether or not the invocation is recursive.
143 entry_transport
= bl_transport
;
146 * If this is the first bootlog call then setup the transport.
147 * We only do this in a non-recursive invocation as openbootlog()
148 * results in a recursive call for a HTTP or HTTPS transport.
150 if (bl_transport
== BL_NO_TRANSPORT
&& blrecurs
== 1) {
151 rb_init(&ringbuffer
);
152 bl_transport
= openbootlog();
156 * If we're not there already, try to move up a level.
157 * This is necessary because our consumer may have begun
158 * logging before it had enough information to initialize
159 * its HTTP or HTTPS transport. We've arbitrarily decided
160 * that we'll only check to see if we should move up, on
161 * every third (blretry) non-recursive invocation.
164 !(bl_transport
== BL_HTTPS
|| bl_transport
== BL_HTTP
)) {
166 bl_transport
= openbootlog();
172 if (entry_transport
!= bl_transport
) {
173 switch (bl_transport
) {
177 "%s wanboot info: WAN boot messages->console\n",
184 "%s wanboot info: WAN boot messages->%s:%u\n",
185 gettime(), bl_url
.hport
.hostname
,
195 * Failed attempts and recursively generated log messages are
196 * sent to the fallback transport.
198 if (blrecurs
> 1 || !sendmessage(bl_transport
, message
, ident
,
201 * Fallback to a log file if one exists, or the console
202 * as a last resort. Note that bl_filehandle will always
203 * be NULL in standalone.
205 (void) sendmessage(bl_filehandle
!= NULL
? BL_LOCAL_FILE
:
206 BL_CONSOLE
, message
, ident
, severity
, 1);
212 * bootlog() - the exposed interface for logging boot messages.
216 bootlog(const char *ident
, bootlog_severity_t severity
, char *fmt
, ...)
218 char message
[BOOTLOG_MSG_MAX_LEN
];
222 (void) vsnprintf(message
, BOOTLOG_MSG_MAX_LEN
, fmt
, adx
);
225 bootlog_common(ident
, severity
, message
);
229 * libbootlog() - an internal interface for logging boot
234 libbootlog(bootlog_severity_t severity
, char *fmt
, ...)
236 char message
[BOOTLOG_MSG_MAX_LEN
];
240 (void) vsnprintf(message
, BOOTLOG_MSG_MAX_LEN
,
241 dgettext(TEXT_DOMAIN
, fmt
), adx
);
244 bootlog_common("libwanboot", severity
, message
);
250 http_respinfo_t
*resp
= NULL
;
251 char buffer
[BOOTLOG_MAX_URL
+ (BOOTLOG_QS_MAX
* 3)];
252 char ringmessage
[BOOTLOG_QS_MAX
];
257 while ((rb_read(&ringbuffer
, ringmessage
) != -1)) {
258 (void) snprintf(buffer
, sizeof (buffer
), "%s?%s",
259 bl_url
.abspath
, url_encode(ringmessage
));
261 for (retries
= 0; retries
< BOOTLOG_CONN_RETRIES
; retries
++) {
263 (void) http_srv_disconnect(bl_httphandle
);
264 if (http_srv_connect(bl_httphandle
) != 0)
268 if (http_get_request(bl_httphandle
, buffer
) != 0 ||
269 http_process_headers(bl_httphandle
, &resp
) != 0)
272 if (resp
->code
!= 200) {
273 http_free_respinfo(resp
);
277 http_free_respinfo(resp
);
278 lenstr
= http_get_header_value(bl_httphandle
,
280 length
= strtol(lenstr
, NULL
, 10);
281 if (http_read_body(bl_httphandle
, buffer
, length
) > 0)
286 * The attempt to log the message failed. Back the
287 * read pointer up so that we'll try to log it again
290 if (retries
== BOOTLOG_CONN_RETRIES
) {
291 ringbuffer
.r_ptr
= ptr_decr(ringbuffer
.r_ptr
);
300 sendmessage(bl_transport_t transport
, char *message
, const char *ident
,
301 bootlog_severity_t severity
, int failure
)
303 static char *progtype
= NULL
;
304 char ringmessage
[BOOTLOG_QS_MAX
];
305 char hostname
[MAXHOSTNAMELEN
];
311 * In standalone, only log VERBOSE and DEBUG messages if the
312 * corresponding flag (-V or -d) has been passed to boot.
314 * Note that some bootlog() consumers impose additional constraints on
315 * printing these messages -- for instance, http_set_verbose() must be
316 * used before the HTTP code will call bootlog() with BOOTLOG_VERBOSE
320 if (severity
== BOOTLOG_DEBUG
&& !(boothowto
& RB_DEBUG
))
322 if (severity
== BOOTLOG_VERBOSE
&& !verbosemode
)
326 for (i
= 0; pri_names
[i
].c_val
!= NOPRI
; i
++) {
327 if (severity
== pri_names
[i
].c_val
)
332 * VERBOSE and DEBUG messages always go to the console
334 if (transport
!= BL_CONSOLE
&&
335 (severity
== BOOTLOG_DEBUG
|| severity
== BOOTLOG_VERBOSE
)) {
336 (void) printf("%s %s %s: %s\n", gettime(), ident
,
337 pri_names
[i
].c_name
, message
);
340 STRLOG_MAKE_MSGID(message
, msgid
);
341 (void) gethostname(hostname
, sizeof (hostname
));
344 * Note that in this case, "<time>" is a placeholder that will be used
345 * to fill in the actual time on the remote end.
347 (void) snprintf(ringmessage
, sizeof (ringmessage
),
348 "<time> %s %s: [ID %u user.%s] %s", hostname
, ident
, msgid
,
349 pri_names
[i
].c_name
, message
);
352 * Prevent duplicate messages from being inserted into
356 rb_write(&ringbuffer
, ringmessage
);
362 * PROGRESS messages update in-place on the console, as long
363 * as they are of the same 'progress type' (see below) --
364 * if not, reset the progress information.
366 if (progtype
!= NULL
&& (severity
!= BOOTLOG_PROGRESS
||
367 strncmp(progtype
, message
, strlen(progtype
)) != 0)) {
373 (void) printf("%s %s %s: %s\r", gettime(), ident
,
374 pri_names
[i
].c_name
, message
);
376 if (severity
!= BOOTLOG_PROGRESS
) {
378 } else if (progtype
== NULL
) {
380 * New progress message; save its "type" (the part
381 * of the message up to and including the first
382 * colon). This should be made less clumsy in the
385 progtype
= strdup(message
);
386 if (progtype
!= NULL
) {
387 for (i
= 0; progtype
[i
] != '\0'; i
++) {
388 if (progtype
[i
] == ':') {
389 progtype
[++i
] = '\0';
399 if (bl_filehandle
== NULL
)
402 (void) fprintf(bl_filehandle
, "%s %s %s: [ID %u user.%s] %s\n",
403 gettime(), hostname
, ident
, msgid
, pri_names
[i
].c_name
,
410 if (bl_httphandle
== NULL
)
415 case BL_NO_TRANSPORT
:
423 static bl_transport_t
426 static boolean_t got_boot_logger
= B_FALSE
;
427 static boolean_t bl_url_valid
= B_FALSE
;
428 static boolean_t clientauth
= B_FALSE
;
429 static bc_handle_t bootconf_handle
;
430 bl_transport_t transport
;
433 * We try to use a logfile in userland since our consumer (install)
434 * needs complete control over the terminal.
437 if (bl_filehandle
== NULL
)
438 bl_filehandle
= fopen("/var/log/bootlog", "a");
440 transport
= (bl_filehandle
!= NULL
) ? BL_LOCAL_FILE
: BL_CONSOLE
;
443 * If we haven't already been able to access wanboot.conf for a
444 * boot_logger URL, see if we can now.
446 if (!got_boot_logger
&&
447 bootconf_init(&bootconf_handle
, NULL
) == BC_SUCCESS
) {
452 * If there is a boot_logger, ensure that it's is a legal URL.
454 if ((urlstr
= bootconf_get(&bootconf_handle
,
455 BC_BOOT_LOGGER
)) != NULL
&&
456 url_parse(urlstr
, &bl_url
) == URL_PARSE_SUCCESS
) {
457 bl_url_valid
= B_TRUE
;
461 * If the boot_logger URL uses an HTTPS scheme, see if
462 * client authentication is specified.
465 cas
= bootconf_get(&bootconf_handle
,
466 BC_CLIENT_AUTHENTICATION
);
468 clientauth
= (strcmp(cas
, BC_YES
) == 0);
472 bootconf_end(&bootconf_handle
);
475 * Having now accessed wanboot.conf, remember not to come
476 * this way again; the value of boot_logger cannot change.
478 got_boot_logger
= B_TRUE
;
482 * If there is no legal boot_logger URL available, then we're done.
489 * If we don't already have a bl_httphandle, try to get one.
490 * If we fail, then we're done.
492 if (bl_httphandle
== NULL
) {
493 bl_httphandle
= http_srv_init(&bl_url
);
494 if (bl_httphandle
== NULL
) {
500 * If we succeed in setting up the connection,
501 * then we use the connection as our transport.
502 * Otherwise, we use the transport we've already
505 if (setup_con(bl_httphandle
, bl_url
.https
, clientauth
)) {
506 transport
= bl_url
.https
? BL_HTTPS
: BL_HTTP
;
513 setup_con(http_handle_t handle
, boolean_t https
, boolean_t client_auth
)
515 static boolean_t got_proxy
= B_FALSE
;
516 static boolean_t proxy_valid
= B_FALSE
;
517 static url_hport_t proxy
;
521 * If an HTTPS scheme is specified, then check that time
522 * has been initialized.
523 * If time() returns a non-zero value, then we know
524 * that the boot file system has been mounted and that
525 * we have a trusted time.
527 if (https
&& time(0) == 0)
530 if (!got_proxy
&& bootinfo_init()) {
531 char hpstr
[URL_MAX_STRLEN
];
532 size_t vallen
= sizeof (hpstr
);
535 * If there is a http-proxy, ensure that it's a legal host:port.
537 if (bootinfo_get(BI_HTTP_PROXY
, hpstr
, &vallen
, NULL
) ==
538 BI_E_SUCCESS
&& vallen
> 0) {
539 hpstr
[vallen
] = '\0';
540 if (url_parse_hostport(hpstr
, &proxy
,
541 URL_DFLT_PROXY_PORT
) == URL_PARSE_SUCCESS
) {
542 proxy_valid
= B_TRUE
;
548 if (proxy_valid
&& http_set_proxy(handle
, &proxy
) != 0)
551 (void) http_set_keepalive(handle
, 1);
552 (void) http_set_socket_read_timeout(handle
, BOOTLOG_HTTP_TIMEOUT
);
555 * If an HTTPS scheme is specified, then setup the necessary
556 * SSL context for the connection
559 if (http_set_random_file(handle
, "/dev/urandom") == -1)
562 if (http_set_certificate_authority_file(NB_CA_CERT_PATH
) < 0)
566 * The client certificate and key will not exist unless
567 * client authentication has been configured. If it is
568 * configured then the webserver will have added these
569 * files to the wanboot file system and the HTTP library
570 * needs to be made aware of their existence.
573 if (http_set_client_certificate_file(handle
,
574 NB_CLIENT_CERT_PATH
) < 0) {
578 if (http_set_private_key_file(handle
,
579 NB_CLIENT_KEY_PATH
) < 0) {
584 if (http_set_password(handle
, WANBOOT_PASSPHRASE
) < 0)
588 for (i
= 0; i
< BOOTLOG_CONN_RETRIES
; i
++) {
589 if (http_srv_connect(handle
) == 0)
592 (void) http_srv_disconnect(handle
);
599 url_encode(const char *ibufp
)
603 unsigned char nibble
;
604 static char obuff
[BOOTLOG_QS_MAX
* 3];
608 * Encode special characters as outlined in RFC2396.
610 * Special characters are encoded as a triplets beginning
611 * with '%' followed by the two hexidecimal digits representing
612 * the octet code. The space character is special. It can be encoded
615 while ((c
= *ibufp
++) != '\0') {
617 * Is the character one of the special characters
618 * that require encoding? If so append '%' to the output
619 * buffer follow that by the hexascii value.
621 if (strchr("/?{}|^~[]`<>#%=\"\t", c
) != NULL
) {
624 * Compute the character's hex value and
625 * convert it to ASCII. That is two nibbles
628 for (i
= 1; i
>= 0; i
--) {
629 nibble
= ((uchar_t
)c
>> (4 * i
)) & 0x0f;
631 * If the hex digit is 0xa - 0xf, then
632 * compute its ASCII value by adding 0x37
633 * else 0x0 - 0x9 just add 0x30.
642 * The space character gets a special mapping.
644 } else if (c
== ' ') {
648 * Append the rest (sans any CR character)
650 } else if (c
!= '\n') {
659 rb_init(struct ringbuffer_t
*buffer
)
666 for (i
= 0; i
< BOOTLOG_RING_NELEM
; i
++)
667 buffer
->entries
[i
].message
[0] = '\0';
673 if (++ptr
< BOOTLOG_RING_NELEM
)
683 return (BOOTLOG_RING_NELEM
- 1);
689 rb_write(struct ringbuffer_t
*buffer
, const char *buff
)
691 (void) strlcpy(buffer
->entries
[buffer
->w_ptr
].message
, buff
,
693 buffer
->w_ptr
= ptr_incr(buffer
->w_ptr
);
694 if (buffer
->r_ptr
== buffer
->w_ptr
)
695 buffer
->r_ptr
= ptr_incr(buffer
->r_ptr
);
699 rb_read(struct ringbuffer_t
*buffer
, char *buff
)
701 if (buffer
->r_ptr
!= buffer
->w_ptr
) {
702 (void) strlcpy(buff
, buffer
->entries
[buffer
->r_ptr
].message
,
704 buffer
->r_ptr
= ptr_incr(buffer
->r_ptr
);