#! /usr/bin/python2.6
#
# 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
# 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 (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
"""
Various support code for the bootmgmt package
"""
import inspect
import libbe_py
import logging
import os
import sys
import platform
from bootmgmt import (BootmgmtNotSupportedError, BootmgmtArgumentError,
BootmgmtError)
from bootmgmt.pysol import (is_gpt_disk, find_gpt_partition,
libfdisk_find_part_by_id, zpool_open, zpool_close,
zpool_get_prop, ZPOOL_PROP_BOOTFS, zfs_open,
zfs_close, zfs_get_prop_string, ZFS_PROP_ENCRYPTION)
EFISYS_PARTID = 0xEF
EFISYS_GUID = 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B'
def get_current_arch_string():
"Returns the current architecture string"
proc = platform.processor()
if proc == 'i386':
return 'x86'
elif proc == 'sparc':
return 'sparc'
raise BootmgmtNotSupportedError('Unsupported platform: ' + proc)
def bootfs_split(bootfs):
"""Splits the bootfs passed in into rpool and bootfs values
"""
bootfs_spec = bootfs.split('/', 2)
if len(bootfs_spec) != 3 or bootfs_spec[1] != 'ROOT':
raise BootmgmtArgumentError('Invalid bootfs: %s' % bootfs)
return (bootfs_spec[0], bootfs_spec[2])
def bootfs_build(rpool, bename):
"""Construct a bootfs string from a root pool and BE name
"""
return '%s/ROOT/%s' % (rpool, bename)
def parse_bool(bool_string):
"""Returns True if the string is a representation of True.
"""
if str(bool_string).lower() in ['true', 'yes', '1']:
return True
return False
def find_efi_system_partition(devname, is_gpt=None):
"""Given a path to a wholedisk node /dev/[r]dsk/cXtYdZ, find
the path to the EFI system partition and return it. This only works
on x86 systems.
"""
esp_dev = None
lastslash = devname.rfind('/')
if lastslash == -1:
raise BootmgmtError('Invalid device path: %s' % devname)
firstpart = devname[:lastslash + 1] # include the slash
# We need the character device, below
firstpart = firstpart.replace('/dsk/', '/rdsk/')
lastpart = devname[lastslash + 1:]
last_ess = lastpart.rfind('s')
if last_ess != -1:
base_devname = firstpart + lastpart[:last_ess]
else:
last_pee = lastpart.rfind('p')
if last_pee != -1:
base_devname = firstpart + lastpart[:last_pee]
else:
base_devname = firstpart + lastpart
base_devname_block = base_devname.replace('/rdsk/', '/dsk/')
wholedisk = base_devname + 'p0'
# If the caller already computed is_gpt, use that to reduce duplicated
# work, otherwise, do it here:
if is_gpt is None:
try:
is_gpt = is_gpt_disk(wholedisk)
except OSError as oserr:
raise BootmgmtError('Error while accessing "%s": %s' %
(wholedisk, oserr.strerror))
if is_gpt:
esp_index = find_gpt_partition(wholedisk, EFISYS_GUID)
if esp_index >= 0:
if esp_index > 8:
raise BootmgmtError('The EFI System Partition (ESP) '
'for device %s is not accessible in Solaris because it is '
'located in partition number %d (Solaris can only access the '
'first 9 GPT partitions)' % (devname, esp_index + 1))
# else
esp_dev = base_devname_block + ('s%d' % esp_index)
else:
# Find the EFI System Partition on a DOS-partitioned drive
part_no = libfdisk_find_part_by_id(wholedisk, EFISYS_PARTID)
if part_no != -1:
esp_dev = base_devname_block + ('p%d' % part_no)
return esp_dev
def get_zpool_default_bootfs(lzfsh, rpool):
"""Returns the default bootfs property for the specified pool
"""
zph = None
try:
zph = zpool_open(lzfsh, rpool)
pool_default_bootfs = zpool_get_prop(lzfsh, zph, ZPOOL_PROP_BOOTFS)
zpool_close(zph)
except IOError:
if zph:
zpool_close(zph)
pool_default_bootfs = None
return pool_default_bootfs
def is_zfs_encrypted_dataset(lzfsh, dataset_name):
"""Returns True if the specified dataset has encryption enabled.
"""
zfsh = None
try:
zfsh = zfs_open(lzfsh, dataset_name)
onoff = zfs_get_prop_string(lzfsh, zfsh, ZFS_PROP_ENCRYPTION)
zfs_close(zfsh)
# pylint: disable=E1103
if onoff.lower() == 'on':
return True
# pylint: enable=E1103
except IOError:
if zfsh:
zfs_close(zfsh)
return False
def get_bootfs_list_from_libbe():
"""Returns a list of bootfs strings that identify the BEs on the system.
"""
ret, be_list = libbe_py.beList()
if ret != 0:
return []
bootfses = []
for bootenv in be_list:
if 'root_ds' in bootenv:
bootfses.append(bootenv['root_ds'])
return bootfses
class LoggerMixin(object):
"""Logging helper functions
"""
logger = logging.getLogger('bootmgmt')
debug_enabled = os.environ.get('BOOTMGMT_DEBUG', None) is not None
if debug_enabled:
logging.basicConfig(level=logging.DEBUG)
SPECIAL_OBJ = object()
@classmethod
def _debug(cls, log_msg, format_tuple=SPECIAL_OBJ):
"""Main debugging helper method
"""
if not cls.debug_enabled:
return
try:
func = inspect.getframeinfo(sys._getframe(1), context=0)[2]
except Exception as exc:
LoggerMixin.logger.debug(cls.__name__ +
': PROBLEM INSPECTING STACK: %s' % exc)
func = '?'
if format_tuple is not cls.SPECIAL_OBJ:
try:
log_str = log_msg % format_tuple
except ValueError:
log_str = log_msg + " [[Couldn't unpack format tuple]]"
else:
log_str = log_msg
LoggerMixin.logger.debug(cls.__name__ + '.' + func + ': ' + log_str)
def scrub_kargs(kargs, dbobj=LoggerMixin()):
"""Scrubs the $ZFS-BOOTFS kernel argument, while preserving others.
"""
# $ZFS-BOOTFS can be in the kernel argument list in a variety of
# ways, i.e.:
# -B [<otherargs>,]*$ZFS-BOOTFS[,<otherargs>]*
# Here, we compose a simple replace -- the order matters here. First
# we remove the string if it's in the middle of a comma-delimited
# arg list and then we remove instances bordered on either side.
# finally, we remove the entire -B argument but only if $ZFS-BOOTFS
# is the only -B argument (we're guaranteed that's the case by the
# time we get there because the previous cases would have eliminated
# instances where $ZFS-BOOTFS was the first in a -B arg list.
new_kargs = kargs.replace(',$ZFS-BOOTFS,', ',')
new_kargs = new_kargs.replace(',$ZFS-BOOTFS', '')
new_kargs = new_kargs.replace('$ZFS-BOOTFS,', '')
new_kargs = new_kargs.replace('-B $ZFS-BOOTFS', '')
dbobj._debug('Scrubbed kargs "%s" => "%s"', (kargs, new_kargs))
return new_kargs