bootadm_hyper.c revision eac223cc141d147b18d5f11977ade425411c5116
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <alloca.h>
#include <ctype.h>
#include "message.h"
#include "bootadm.h"
#define HYPER_KERNEL_DIR "/platform/i86xpv/kernel"
#define METAL_KERNEL_DIR "/platform/i86pc/kernel"
#define BOOTRC_FILE "/boot/solaris/bootenv.rc"
#define ZFS_BOOTSTR "$ZFS-BOOTFS"
#define BFLAG "-B"
#define DEFAULT_SERIAL "9600,8,n,1"
#define WHITESPC(x) (x)
static char *console_dev = NULL;
static char *bootenv_rc_console = NULL;
static unsigned zfs_boot = 0;
/*
* Append the string pointed to by "str" to the string pointed to by "orig"
* adding the delimeter "delim" in between.
*
* Return a pointer to the new string or NULL, if we were passed a bad string.
*/
static char *
{
char *newstr;
int len;
return (NULL);
/*
* Return a pointer to a copy of the path so a caller can
* always rely upon being able to free() a returned pointer.
*/
}
bam_exit(1);
}
return (newstr);
}
/*
* Replace the substring "old_str" in a path with the substring "new_str"
*
* Return a pointer to the modified string.
*/
static char *
{
char *newpath;
char *pc;
int len;
/*
* Return a pointer to a copy of the path so a caller can always rely
* upon being able to free() a returned pointer.
*/
/*
* Allocate space for duplicate of path with name changes and
* NULL terminating byte
*/
bam_exit(1);
}
return (newpath);
}
/*
* Set "token" to be the the string starting from the pointer "str" delimited
* by any character in the string "delim" or the end of the string, but IGNORE
* any characters between single or double quotes.
*
* Return a pointer to the next non-whitespace character after the delimiter
* or NULL if we hit the end of the string. Also return NULL upon failure to
* find any characters from the delimeter string or upon failure to allocate
* memory for the new token string.
*/
static char *
{
char *dp;
unsigned len;
return (NULL);
do {
str++;
/* no matching quote found in string */
return (NULL);
}
/* look for a character from the delimiter string */
;
/* found a delimiter, so create a token string */
bam_exit(1);
}
;
return (str);
}
/* if we hit the end of the string, the token is the whole string */
return (NULL);
}
/*
* Convert a metal "console" device name to an equivalent one suitable for
* use with the hypervisor.
*
* Default to "vga" if we can't parse the console device.
*/
static void
console_metal_to_hyper(char *console)
{
console++;
console_dev = "console=com1";
console_dev = "console=com2";
else
console_dev = "console=vga";
}
static int
{
return (-1);
/*
* If rate is a NULL pointer, erase any existing serial configuration
* for this serial port.
*/
}
return (0);
}
return (0);
}
/*
* Convert "metal" serial port parameters to values compatible with the
* hypervisor.
*
* Return 0 on success, otherwise -1.
*/
static int
{
char com_rate[COM_RATE_LEN];
else
return (-1);
metal_serial++;
/*
* Check if it's specified as the default rate; if so it defaults to
* "auto" and we need not set it for they hypervisor.
*/
strlen(DEFAULT_SERIAL)) == 0) {
return (0);
}
&handshake) != 5)
return (-1);
/* validate serial port parameters */
return (-1);
/* validate baud rate */
switch (baud) {
case 150:
case 300:
case 600:
case 1200:
case 2400:
case 4800:
case 9600:
case 19200:
case 38400:
case 57600:
case 115200:
break;
default:
return (-1);
}
/*
* The hypervisor has no way to specify a handshake method, so it gets
* quietly dropped in the conversion.
*/
return (0);
}
/*
* Convert "name=value" metal options to values suitable for use with the
* hypervisor.
*
* Our main concerns are the console device and serial port settings.
*
* Return values:
*
* -1: Unparseable line
* 0: Success
* (n > 0): A property unimportant to us
*/
static int
cvt_metal_option(char *optstr)
{
char *value;
unsigned namlen;
zfs_boot = 1;
return (0);
}
return (-1);
return (1);
return (0);
}
}
return (1);
}
/*
* Convert "name=value" properties for use with a bare metal kernel
*
* Our main concerns are the console setting and serial port modes.
*
* Return values:
*
* -1: Unparseable line
* 0: Success
* (n > 0): A property unimportant to us
*/
static int
cvt_hyper_option(char *optstr)
{
char *value;
unsigned namlen;
unsigned baud;
zfs_boot = 1;
return (0);
}
/*
* If there's no "=" in the token, it's likely a standalone
* hypervisor token we don't care about (e.g. "noreboot" or
* "nosmp") so we ignore it.
*/
return (1);
return (1);
/*
* Note that we use strncmp against the values because the
* hypervisor allows setting console parameters for both the
* console and debugger via the format:
*
* console=cons_dev,debug_dev
*
* and we only care about "cons_dev."
*
* This also allows us to extract "comN" from hypervisor constructs
* like "com1H" or "com2L," concepts unsupported on bare metal kernels.
*
* Default the console device to "text" if it was "vga" or was
* unparseable.
*/
/* ignore the "console=hypervisor" option */
return (0);
console_dev = "ttya";
console_dev = "ttyb";
else
console_dev = "text";
}
/* serial port parameter conversion */
/*
* Check if it's "auto" - if so, use the default setting
* of "9600,8,n,1,-".
*
* We can't just assume the serial port will default to
* "9600,8,n,1" as there could be a directive in bootenv.rc
* that would set it to some other value and we want the serial
* parameters to be the same as that used by the hypervisor.
*/
} else {
/*
* Extract the "B,PS" setting from the com line; ignore
* other settings like io_base or IRQ.
*/
&stop) != 4)
return (-1);
/* validate serial port parameters */
(parity != 'o')))
return (-1);
/* validate baud rate */
switch (baud) {
case 150:
case 300:
case 600:
case 1200:
case 2400:
case 4800:
case 19200:
case 38400:
case 57600:
case 115200:
break;
default:
return (-1);
}
/*
* As the hypervisor has no way to denote handshaking
* in its serial port settings, emit a metal serial
* port configuration with none as well.
*/
}
return (-1);
return (0);
}
return (1);
}
/*
* Parse a hardware kernel's "kernel$" specifier into parameters we can then
* use to construct an appropriate "module$" line that can be used to specify
* how to boot the hypervisor's dom0.
*
* Return values:
*
* -1: error parsing kernel path
* 0: success
* 1: kernel already a hypervisor kernel
*/
static int
{
return (-1);
/*
* If the metal kernel specified contains the name of the hypervisor,
* we're probably trying to convert an entry already setup to run the
* hypervisor, so error out now.
*/
return (1);
}
/* if the path was the last item on the line, that's OK. */
return (0);
}
/* if the next token is "-B" process boot options */
return (0);
}
(void) cvt_metal_option(token);
}
(void) cvt_metal_option(token);
}
return (0);
}
/*
* Parse a hypervisor's "kernel$" line into parameters that can be used to
* help build an appropriate "kernel$" line for booting a bare metal kernel.
*
* Return 0 on success, non-zero on failure.
*/
static int
cvt_hyper_kernel(char *kernel)
{
return (-1);
/*
* If the hypervisor kernel specified lives in the metal kernel
* directory, we're probably trying to convert an entry already setup
* to run on bare metal, so error out now.
*/
return (-1);
}
/* check for kernel options */
(void) cvt_hyper_option(token);
}
(void) cvt_hyper_option(token);
}
return (0);
}
/*
* Parse a hypervisor's "module$" line into parameters that can be used to
* help build an appropriate "kernel$" line for booting a bare metal kernel.
*/
static void
{
/*
* If multiple pathnames exist on the module$ line, we just want
* the last one.
*/
if (*parsestr != '/')
break;
else
}
/* if the path was the last item on the line, that's OK. */
return;
}
return;
/* check for "-B" option */
return;
}
/* check for kernel options */
(void) cvt_hyper_option(token);
}
(void) cvt_hyper_option(token);
}
}
static void
parse_bootenvrc(char *osroot)
{
#define LINEBUF_SZ 1024
char *rcpath;
int len;
/* if we couldn't open the bootenv.rc file, ignore the issue. */
return;
}
int port = 0;
/* we're only interested in parsing "setprop" directives. */
continue;
/* eat initial "setprop" */
continue;
}
continue;
}
/* get property name */
continue;
}
/* get console property value */
continue;
if (bootenv_rc_console != NULL)
continue;
}
/* check if it's a serial port setting */
port = 0;
port = 1;
} else {
/* nope, so check the next line */
continue;
}
/* get serial port setting */
continue;
}
}
{
const char *fcn = "cvt_to_hyper()";
char *newstr;
char *osdev;
char *mod_kernel = NULL;
char *kern_bargs = NULL;
int kp_allocated = 0;
/*
* First just check to verify osroot is a sane directory.
*/
return (BAM_ERROR);
}
/*
* While the effect is purely cosmetic, if osroot is "/" don't
* bother prepending it to any paths as they are constructed to
* begin with "/" anyway.
*/
osroot = "";
/*
* Found the GRUB signature on the target partitions, so now get the
* default GRUB boot entry number from the menu.lst file
*/
/* look for the first line of the matching boot entry */
;
/* couldn't find it, so error out */
goto abort;
}
/*
* We found the proper menu entry, so first we need to process the
* bootenv.rc file to look for boot options the hypervisor might need
* passed as kernel start options such as the console device and serial
* port parameters.
*
* If there's no bootenv.rc, it's not an issue.
*/
if (bootenv_rc_console != NULL)
if (bootenv_rc_serial[0] != NULL)
/*
* Now process the entry itself.
*/
/*
* Process important lines from menu.lst boot entry.
*/
menu_cmds[MODULE_DOLLAR_CMD]) == 0) {
menu_cmds[KERNEL_DOLLAR_CMD]) == 0) &&
&kern_path)) != 0) {
if (ret < 0) {
} else
ret = BAM_NOCHANGE;
goto abort;
}
}
break;
}
/*
* If findroot, module or kern_path are NULL, the boot entry is
* malformed.
*/
goto abort;
}
goto abort;
}
goto abort;
}
/* assemble new kernel and module arguments from parsed values */
if (console_dev != NULL) {
if (serial_config[0] != NULL) {
kern_bargs = newstr;
}
kern_bargs = newstr;
}
}
kern_bargs = newstr;
}
if (kern_bargs != NULL) {
if (*kern_bargs != NULL)
} else {
}
/*
* Change the kernel directory from the metal version to that needed for
* the hypervisor. Convert either "direct boot" path to the default
* path.
*/
} else {
kp_allocated = 1;
}
/*
* We need to allocate space for the kernel path (twice) plus an
* intervening space, possibly the ZFS boot string, and NULL,
* of course.
*/
if (kp_allocated)
if (zfs_boot) {
}
/* shut off warning messages from the entry line parser */
return (newdef);
/*
* Now try to delete the current default entry from the menu and add
* the new hypervisor entry with the parameters we've setup.
*/
newdef--;
else
/*
* If we successfully created the new entry, set the default boot
* entry to that entry and let the caller know the new menu should
* be written out.
*/
if (ret != BAM_NOCHANGE)
return (ret);
}
/*ARGSUSED*/
{
const char *fcn = "cvt_to_metal()";
char *delim = ",";
char *newstr;
char *osdev;
char *barchive_path = DIRECT_BOOT_ARCHIVE;
int emit_bflag = 1;
/*
* First just check to verify osroot is a sane directory.
*/
return (BAM_ERROR);
}
/*
* Found the GRUB signature on the target partitions, so now get the
* default GRUB boot entry number from the menu.lst file
*/
/* look for the first line of the matching boot entry */
;
/* couldn't find it, so error out */
goto abort;
}
/*
* Now process the entry itself.
*/
/*
* Process important lines from menu.lst boot entry.
*/
menu_cmds[MODULE_DOLLAR_CMD]) == 0) {
} else {
}
menu_cmds[KERNEL_DOLLAR_CMD]) == 0) &&
ret = BAM_NOCHANGE;
goto abort;
}
}
break;
}
/*
* If findroot, module or kern_path are NULL, the boot entry is
* malformed.
*/
goto abort;
}
goto abort;
}
goto abort;
}
/*
* Assemble new kernel and module arguments from parsed values.
*
* First, change the kernel directory from the hypervisor version to
* that needed for a metal kernel.
*/
/* allocate initial space for the kernel path */
bam_exit(1);
}
if (zfs_boot) {
emit_bflag = 0;
}
/*
* Process the bootenv.rc file to look for boot options that would be
* the same as what the hypervisor had manually set, as we need not set
* those explicitly.
*
* If there's no bootenv.rc, it's not an issue.
*/
/*
* Don't emit a console setting if it's the same as what would be
* set by bootenv.rc.
*/
if (emit_bflag) {
emit_bflag = 0;
} else {
}
}
/*
* We have to do some strange processing here because the hypervisor's
* serial ports default to "9600,8,n,1,-" if "comX=auto" is specified,
* or to "auto" if nothing is specified.
*
* This could result in a serial mode setting string being added when
* it would otherwise not be needed, but it's better to play it safe.
*/
if (emit_bflag) {
delim = " ";
emit_bflag = 0;
}
/*
* Pass the serial configuration as the delimiter to
* append_str() as it will be inserted between the current
* string and the string we're appending, in this case the
* closing single quote.
*/
delim = ",";
}
/*
* Pass the serial configuration as the delimiter to
* append_str() as it will be inserted between the current
* string and the string we're appending, in this case the
* closing single quote.
*/
delim = ",";
}
/* shut off warning messages from the entry line parser */
return (newdef);
}
/*
* Now try to delete the current default entry from the menu and add
* the new hypervisor entry with the parameters we've setup.
*/
newdef--;
else
/*
* If we successfully created the new entry, set the default boot
* entry to that entry and let the caller know the new menu should
* be written out.
*/
if (ret != BAM_NOCHANGE)
return (ret);
}