diff options
Diffstat (limited to 'usr/src')
37 files changed, 18064 insertions, 1 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint index 7c74fb03f8..fb9724629a 100644 --- a/usr/src/Makefile.lint +++ b/usr/src/Makefile.lint @@ -330,6 +330,8 @@ COMMON_SUBDIRS = \ cmd/ztest \ lib/abi \ lib/auditd_plugins \ + lib/libbe \ + lib/pylibbe \ lib/brand/sn1 \ lib/brand/solaris10 \ lib/crypt_modules \ diff --git a/usr/src/Targetdirs b/usr/src/Targetdirs index d73e5fc95d..90f839d353 100644 --- a/usr/src/Targetdirs +++ b/usr/src/Targetdirs @@ -290,6 +290,7 @@ DIRS= \ /usr/lib/python2.6/vendor-packages \ /usr/lib/python2.6/vendor-packages/solaris \ /usr/lib/python2.6/vendor-packages/zfs \ + /usr/lib/python2.6/vendor-packages/beadm \ /usr/lib/rcap \ /usr/lib/rcap/$(MACH32) \ /usr/lib/sa \ diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile index 1526ae4520..3bc3713aa1 100644 --- a/usr/src/cmd/Makefile +++ b/usr/src/cmd/Makefile @@ -69,6 +69,7 @@ COMMON_SUBDIRS= \ basename \ bc \ bdiff \ + beadm \ bfs \ bnu \ boot \ @@ -528,6 +529,7 @@ MSGSUBDIRS= \ banner \ bart \ basename \ + beadm \ bnu \ bsmconv \ bsmunconv \ diff --git a/usr/src/cmd/beadm/BootEnvironment.py b/usr/src/cmd/beadm/BootEnvironment.py new file mode 100644 index 0000000000..b85373880e --- /dev/null +++ b/usr/src/cmd/beadm/BootEnvironment.py @@ -0,0 +1,444 @@ +# 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. +# + +"""Boot Environment classes used by beadm.""" + +import datetime + +class BootEnvironment: + """Boot Environment object that is used by beadm to manage command line + options, arguments and the log.""" + + def __init__(self): + self.trgt_rpool = None + self.trgt_be_name_or_snapshot = None + self.src_be_name_or_snapshot = None + self.properties = {} + self.log_id = None + self.log = None + self.msg_buf = {} + self.description = None + +class listBootEnvironment: + """Base class for beadm list + Determine the BE's to display. Prints command output according to option: + -d - dataset + -s - snapshot + -a - all (both dataset and snapshot) + <none> - only BE information + The -H option produces condensed, parseable output + The ';' delimits each field in the output. BEs with multiple + datasets will have multiple lines in the output. + """ + + def list(self, be_list, ddh, be_name): + """ print all output for beadm list command + be_list - list of all BEs + ddh - if True, Do not Display Headers - just parseable data + be_name - user-specified BE, if any + + returns 0 for success + side effect: beadm list output printed to stdout + """ + + #If we're listing Headers, initialize the array holding the + #column widths with the header widths themselves. Later on, + #the data in this array will get adjusted as we process actual + #row data and find that a piece of data is wider than its + #column header. + bemaxout = [0 for i in range(len(self.hdr[0]))] + if not ddh: + #iterate all header rows since their fields may not + #be of equal length. + for header in self.hdr: + icol = 0 + for hc in header: + if len(hc) + 1 > bemaxout[icol]: + bemaxout[icol] = len(hc) + 1 + icol += 1 + + #collect BEs + beout = {} #matrix of output text [row][attribute] + beoutname = {} #list of BE names [row] + be_space = {} #space used totals for BE [BE name]['space_used','ibei'] + ibe = 0 #BE index + spacecol = -1 #to contain column where space used is displayed + for be in be_list: + if 'orig_be_name' in be: + cur_be = be['orig_be_name'] + cur_be_obj = be + + #if BE name specified, collect matching BEs + if be_name is not None and not self.beMatch(be, be_name): + continue + attrs = () + #identify BE|dataset|snapshot attributes + att = '' + for att in ('orig_be_name', 'dataset', 'snap_name'): + if att in be and att in self.lattrs: + attrs = self.lattrs[att] + if att == 'orig_be_name': + be_space[cur_be] = {} + be_space[cur_be]['space_used'] = 0 + be_space[cur_be]['ibe'] = ibe + if not ddh and len(cur_be) + 1 > bemaxout[0]: + bemaxout[0] = len(cur_be) + 1 + break + beout[ibe] = {} + beoutname[ibe] = cur_be + + icol = 0 #first column + for at in attrs: + #for option -s, withhold subordinate datasets + if self.__class__.__name__ == 'SnapshotList' and \ + att == 'snap_name' and 'snap_name' in be and \ + '/' in be[att]: + break + #convert output to readable format and save + save = self.getAttr(at, be, ddh, cur_be_obj) + beout[ibe][at] = save + #maintain maximum column widths + if not ddh and len(save) + 1 > bemaxout[icol]: + bemaxout[icol] = len(save) + 1 + #sum all snapshots for BE + if at == 'space_used' and 'space_used' in be: + spacecol = icol + icol += 1 #next column + ibe += 1 + if 'space_used' in be: + #sum all snapshots and datasets for BE in 'beadm list' + if isinstance(self, BEList): + be_space[cur_be]['space_used'] += be.get('space_used') + elif cur_be in be_space and \ + ('space_used' not in be_space[cur_be] or + be_space[cur_be]['space_used'] == 0): + #list space used separately for other options + be_space[cur_be]['space_used'] = be.get('space_used') + + #output format total lengths for each BE with any snapshots + for cur_be in be_space: + save = self.getSpaceValue(be_space[cur_be]['space_used'], ddh) + ibe = be_space[cur_be]['ibe'] + beout[ibe]['space_used'] = save + #expand column if widest column entry + if (spacecol != -1) and \ + (not ddh and len(save) + 1 > bemaxout[spacecol]): + bemaxout[spacecol] = len(save) + 1 + + #print headers in columns + if not ddh: + for header in self.hdr: + outstr = '' + for icol in range(len(header)): + outstr += header[icol].ljust(bemaxout[icol]) + if outstr != '': + print outstr + + #print collected output in columns + outstr = '' + prev_be = None + cur_be = None + for ibe in beout: #index output matrix + if beoutname[ibe] != None: + cur_be = beoutname[ibe] + #find attributes for BE type + curtype = None + for att in ('orig_be_name', 'dataset', 'snap_name'): + if att in beout[ibe]: + attrs = self.lattrs[att] + curtype = att + break + + if curtype == None: #default to BE + curtype = 'orig_be_name' + if 'orig_be_name' in self.lattrs: + attrs = self.lattrs['orig_be_name'] + else: attrs = () + + if not ddh: + if prev_be != cur_be and cur_be != None: + #for -d,-s,-a, print BE alone on line + if self.__class__.__name__ != 'BEList': + print cur_be + prev_be = cur_be + + #print for one BE/snapshot/dataset + icol = 0 #first column + + #if this is a 'dataset' or 'snap_name', start line with BE + #name token + if ddh and curtype != 'orig_be_name': + outstr = cur_be + + for at in attrs: #for each attribute specified in table + if ddh: #add separators for parsing + if outstr != '': + outstr += ';' #attribute separator + if at in beout[ibe] and beout[ibe][at] != '-' and \ + beout[ibe][at] != '': + outstr += beout[ibe][at] + else: #append text justified in column + if at in beout[ibe]: + outstr += beout[ibe][at].ljust(bemaxout[icol]) + icol += 1 #next column + + if outstr != '': + print outstr + outstr = '' + + return 0 + + def beMatch(self, be, be_name): + """find match on user-specified BE.""" + + if 'orig_be_name' in be: + return be.get('orig_be_name') == be_name + if 'dataset' in be: + if be.get('dataset') == be_name: + return True + out = be.get('dataset').split("/") + return out[0] == be_name + if 'snap_name' in be: + if be.get('snap_name') == be_name: + return True + out = be.get('snap_name').split('@') + if out[0] == be_name: + return True + out = be.get('snap_name').split('/') + return out[0] == be_name + return False + + def getAttr(self, at, be, ddh, beobj): + """ + Extract information by attribute and format for printing + returns '?' if normally present attribute not found - error. + """ + if at == 'blank': + return ' ' + if at == 'dash': + return '-' + if at == 'orig_be_name': + if at not in be: + return '-' + ret = be[at] + if ddh or self.__class__.__name__ == 'BEList': + return ret + return ' ' + ret #indent + if at == 'snap_name': + if at not in be: + return '-' + if self.__class__.__name__ == 'CompleteList': + ret = self.prependRootDS(be[at], beobj) + else: + ret = be[at] + if ddh: + return ret + return ' ' + ret #indent + if at == 'dataset': + if at not in be: + return '-' + if self.__class__.__name__ == 'DatasetList' or \ + self.__class__.__name__ == 'CompleteList': + ret = self.prependRootDS(be[at], beobj) + else: + ret = be[at] + if ddh: + return ret + return ' ' + ret #indent + if at == 'active': + if at not in be: + return '-' + ret = '' + if 'active' in be and be['active']: + ret += 'N' + if 'active_boot' in be and be['active_boot']: + ret += 'R' + if ret == '': + return '-' + return ret + if at == 'mountpoint': + if at not in be: + return '-' + if 'mounted' not in be or not be['mounted']: + return '-' + return be[at] + if at == 'space_used': + if at not in be: + return '0' + return self.getSpaceValue(be[at], ddh) + if at == 'mounted': + if at not in be: + return '-' + return be[at] + if at == 'date': + if at not in be: + return '?' + if ddh: + return str(be[at]) #timestamp in seconds + sec = str(datetime.datetime.fromtimestamp(be[at])) + return sec[0:len(sec)-3] #trim seconds + if at == 'policy': + if at not in be: + return '?' + return be[at] + if at == 'root_ds': + if at not in be: + return '?' + if ddh or self.__class__.__name__ == 'BEList': + return be[at] + return ' ' + be[at] + if at == 'uuid_str': + if at not in be: + return '-' + return be[at] + #default case - no match on attribute + return be[at] + + def getSpaceValue(self, num, ddh): + """Readable formatting for disk space size.""" + + if ddh: + return str(num) #return size in bytes as string + + kilo = 1024.0 + mega = 1048576.0 + giga = 1073741824.0 + tera = 1099511627776.0 + + if num == None: + return '0' + if num < kilo: + return str(num) + 'B' + if num < mega: + return str('%.1f' % (num / kilo)) + 'K' + if num < giga: + return str('%.2f' % (num / mega)) + 'M' + if num < tera: + return str('%.2f' % (num / giga)) + 'G' + return str('%.2f' % (num / tera)) + 'T' + + def prependRootDS(self, val, beobj): + """Prepend root dataset name with BE name stripped.""" + + root_ds = beobj.get('root_ds') + return root_ds[0:root_ds.rfind('/')+1] + val + + +"""Top level "beadm list" derived classes defined here. + Only table definition is done here - all methods are in the base class. + Tables driving list: + hdr - list of text to output for each column + lattrs - dictionary of attributes + Each entry specifies either BE, dataset, snapshot with + an attribute key: + orig_be_name - for BEs + dataset - for datasets + snap_name - for snapshots + Each list item in entry indicates specific datum for + column + Number of hdr columns must equal number of lattrs entries + unless ddh (dontDisplayHeaders) is true. +""" +class BEList(listBootEnvironment): + """specify header and attribute information for BE-only output""" + + def __init__(self, ddh): + """Init function for the class.""" + self.hdr = \ + ('BE','Active','Mountpoint','Space','Policy','Created'), \ + ('--','------','----------','-----','------','-------') + if ddh: + self.lattrs = {'orig_be_name':('orig_be_name', 'uuid_str', + 'active', 'mountpoint', 'space_used', 'policy', + 'date')} + else: + self.lattrs = {'orig_be_name':('orig_be_name', 'active', + 'mountpoint', 'space_used', 'policy', 'date')} + +class DatasetList(listBootEnvironment): + """ + specify header and attribute information for dataset output, + -d option + """ + def __init__(self, ddh): + """Init function for the class.""" + + self.hdr = \ + ('BE/Dataset','Active','Mountpoint','Space','Policy','Created'), \ + ('----------','------','----------','-----','------','-------') + if ddh: + self.lattrs = { \ + 'orig_be_name':('orig_be_name', 'root_ds', 'active', + 'mountpoint', 'space_used', 'policy', 'date'), \ + 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', + 'policy', 'date')} + else: + self.lattrs = { \ + 'orig_be_name':('root_ds', 'active', 'mountpoint', + 'space_used', 'policy', 'date'), \ + 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', + 'policy', 'date')} + +class SnapshotList(listBootEnvironment): + """ + specify header and attribute information for snapshot output, + -s option + """ + def __init__(self, ddh): + """Init function for the class.""" + + self.hdr = \ + ('BE/Snapshot','Space','Policy','Created'), \ + ('-----------','-----','------','-------') + self.lattrs = {'snap_name':('snap_name', 'space_used', 'policy', + 'date')} + +class CompleteList(listBootEnvironment): + """ + specify header and attribute information for BE and/or dataset and/or + snapshot output, + -a or -ds options + """ + def __init__(self, ddh): + """Init function for the class.""" + + self.hdr = \ + ('BE/Dataset/Snapshot','Active','Mountpoint','Space','Policy','Created'), \ + ('-------------------','------','----------','-----','------','-------') + if ddh: + self.lattrs = { \ + 'orig_be_name':('orig_be_name', 'root_ds', 'active', + 'mountpoint', 'space_used', 'policy', 'date'), + 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', + 'policy', 'date'), + 'snap_name':('snap_name', 'dash', 'dash', 'space_used', + 'policy', 'date')} + else: + self.lattrs = { \ + 'orig_be_name':('root_ds', 'active', 'mountpoint', + 'space_used', 'policy', 'date'), \ + 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', + 'policy', 'date'), + 'snap_name':('snap_name', 'dash', 'dash', 'space_used', + 'policy', 'date')} diff --git a/usr/src/cmd/beadm/Makefile b/usr/src/cmd/beadm/Makefile new file mode 100644 index 0000000000..5586780eda --- /dev/null +++ b/usr/src/cmd/beadm/Makefile @@ -0,0 +1,77 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +include ../Makefile.cmd + +FILEMODE=0444 + +PYTHON= $(PYTHON_26) +ROOTPYDIR= $(ROOT)/usr/lib/python2.6/vendor-packages +ROOTMODDIR= $(ROOTPYDIR)/beadm + +PYCMD= beadm.py +PYCMDFILE= $(PYCMD:%.py=%) + +PYMODULES= __init__.py BootEnvironment.py messages.py +PYMODOBJS= $(PYMODULES:%.py=%.pyc) +PYMODFILES= $(PYMODULES) $(PYMODOBJS) + +ROOTCMDFILE= $(PYCMDFILE:%=$(ROOTSBIN)/%) +$(ROOTCMDFILE) := FILEMODE = 0555 +ROOTMODFILES= $(PYMODFILES:%=$(ROOTMODDIR)/%) +ROOTUSRSBINLINKS= $(PYCMDFILE:%=$(ROOTUSRSBIN)/%) + +POFILE = beadm.po +MSGFILES = beadm.py messages.py +XGETTEXT = $(GNUXGETTEXT) +XGETFLAGS = $(GNUXGETFLAGS) + +.KEEP_STATE: + +all: $(PYCMDFILE) $(PYMODOBJS) + +install: all $(ROOTCMDFILE) $(ROOTMODFILES) $(ROOTUSRSBINLINKS) + +clean: + $(RM) $(PYCMDFILE) $(PYMODOBJS) + +clobber: clean + +$(ROOTCMDDIR)/%: % + $(INS.pyfile) + +$(ROOTMODDIR)/%: % + $(INS.pyfile) + +$(ROOTUSRSBINLINKS): + -$(RM) $@; $(SYMLINK) ../../sbin/$(@F) $@ + +$(POFILE): $(MSGFILES) + $(BUILDPO.msgfiles) + +_msg: $(MSGDOMAINPOFILE) + +include $(SRC)/Makefile.msg.targ +include ../Makefile.targ diff --git a/usr/src/cmd/beadm/__init__.py b/usr/src/cmd/beadm/__init__.py new file mode 100644 index 0000000000..9972bd8edd --- /dev/null +++ b/usr/src/cmd/beadm/__init__.py @@ -0,0 +1,31 @@ +# 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. +# + +import gettext + +_ = gettext.translation("beadm", "/usr/lib/locale", + fallback=True).gettext + +__all__ = ["messages", "BootEnvironment"] + diff --git a/usr/src/cmd/beadm/beadm.py b/usr/src/cmd/beadm/beadm.py new file mode 100644 index 0000000000..cb6772ffd9 --- /dev/null +++ b/usr/src/cmd/beadm/beadm.py @@ -0,0 +1,1058 @@ +#!/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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. +# + +""" +beadm - The Boot Environment Administration tool. Use this CLI to +manage boot environments. +""" + +import getopt +import gettext +import os +import sys +import shutil +import traceback +import time +import subprocess + +from beadm import _ +from beadm.BootEnvironment import * +import beadm.messages as msg +import libbe_py as lb + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def usage(): + '''Defines parameters and options of the command beadm.''' + print >> sys.stderr, _(""" +Usage: + beadm subcommand cmd_options + + subcommands: + + beadm activate beName + beadm create [-a] [-d description] + [-e non-activeBeName | beName@snapshot] + [-o property=value] ... [-p zpool] beName + beadm create beName@snapshot + beadm destroy [-fF] beName | beName@snapshot + beadm list [[-a] | [-d] [-s]] [-H] [beName] + beadm mount beName mountpoint + beadm rename beName newBeName + beadm unmount [-f] beName""") + sys.exit(1) + + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Public Command Line functions described in beadm(1) +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def activate(opts): + """ + Function: activate + + Description: Activate a Boot Environment.The following is the + subcommand, options and args that make up the + opts object passed in: + + Parameters: + opts - A string containing the active subcommand + + Returns: + 0 - Success + 1 - Failure + """ + + if len(opts) != 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + be = BootEnvironment() + + if lb.beVerifyBEName(opts[0]) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + rc = lb.beActivate(opts[0]) + if rc == 0: + return 0 + + be.msg_buf["0"] = opts[0] + if rc == msg.Msgs.BE_ERR_BE_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, opts[0]) + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_ACTIVATE, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_ACTIVATE, be.msg_buf, -1) + return 1 + + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def create(opts): + """ + Function: create + + Description: Create a Boot Environment. The following is the + subcommand, options and args that make up the + opts object passed in: + + create [-a] [-d description] + [-e non-activeBeName | beName@Snapshot] + [-o property=value] ... [-p zpool] beName + + create beName@Snapshot + + Parameters: + opts - A object containing the create subcommand + and all the options and arguments passed in + on the command line mentioned above. + + Returns: + 0 - Success + 1 - Failure + """ + + be = BootEnvironment() + + activate = False + + try: + opts_args, be.trgt_be_name_or_snapshot = getopt.getopt(opts, + "ad:e:o:p:") + except getopt.GetoptError: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + # Counters for detecting multiple options. + # e.g. beadm create -p rpool -p rpool2 newbe + num_a_opts = 0 + num_e_opts = 0 + num_p_opts = 0 + num_d_opts = 0 + + for opt, arg in opts_args: + if opt == "-a": + activate = True + num_a_opts += 1 + elif opt == "-e": + be.src_be_name_or_snapshot = arg + num_e_opts += 1 + elif opt == "-o": + key, value = arg.split("=") + be.properties[key] = value + elif opt == "-p": + be.trgt_rpool = arg + num_p_opts += 1 + elif opt == "-d": + be.description = arg + num_d_opts += 1 + + if num_a_opts > 1 or num_e_opts > 1 or num_p_opts > 1 or num_d_opts > 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + # Check that all info provided from the user is legitimate. + if (verifyCreateOptionsArgs(be) != 0): + usage() + + if initBELog("create", be) != 0: + return 1 + + msg.printMsg(msg.Msgs.BEADM_MSG_BE_CREATE_START, + be.trgt_be_name_or_snapshot[0], be.log_id) + + if '@' in be.trgt_be_name_or_snapshot[0]: + # Create a snapshot + rc = createSnapshot(be) + else: + if lb.beVerifyBEName(be.trgt_be_name_or_snapshot[0]) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + # Create a BE based on a snapshot + if be.src_be_name_or_snapshot is not None and \ + '@' in be.src_be_name_or_snapshot: + # Create a BE from a snapshot + rc = createBEFromSnapshot(be) + else: + rc = createBE(be) + + # Activate the BE if the user chose to. + if activate and rc == 0: + rc = activateBE(be) + cleanupBELog(be) + + return rc + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def destroy(opts): + """ + Function: destroy + + Description: Destroy a Boot Environment. The following is the + subcommand, options and args that make up the + opts object passed in: + + destroy [-fF] beName | beName@snapshot + + Parameters: + opts - A object containing the destroy subcommand + and all the options and arguments passed in + on the command line mentioned above. + + Returns: + 0 - Success + 1 - Failure + """ + + force_unmount = 0 + suppress_prompt = False + be_active_on_boot = None + be = BootEnvironment() + + try: + opts_args, be.trgt_be_name_or_snapshot = getopt.getopt(opts, "fF") + except getopt.GetoptError: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + # Counters for detecting multiple options. + # e.g. beadm destroy -f -f newbe + num_f_opts = 0 + num_sf_opts = 0 + + for opt, arg in opts_args: + if opt == "-f": + force_unmount = 1 + num_sf_opts += 1 + elif opt == "-F": + suppress_prompt = True + num_f_opts += 1 + + if num_sf_opts > 1 or num_f_opts > 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + if len(be.trgt_be_name_or_snapshot) != 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + is_snapshot = False + + if "@" in be.trgt_be_name_or_snapshot[0]: + is_snapshot = True + be_name, snap_name = be.trgt_be_name_or_snapshot[0].split("@") + if lb.beVerifyBEName(be_name) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + else: + if lb.beVerifyBEName(be.trgt_be_name_or_snapshot[0]) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + # Get the 'active' BE and the 'active on boot' BE. + be_active, be_active_on_boot = getActiveBEAndActiveOnBootBE() + + # If the user is trying to destroy the 'active' BE then quit now. + if not is_snapshot and be_active == be.trgt_be_name_or_snapshot[0]: + be.msg_buf["0"] = be.msg_buf["1"] = be_active + msg.printMsg(msg.Msgs.BEADM_ERR_DESTROY_ACTIVE, be.msg_buf, -1) + return 1 + + if not suppress_prompt: + + # Display a destruction question and wait for user response. + # Quit if negative user response. + + if not displayDestructionQuestion(be): + return 0 + + if is_snapshot: + + # Destroy a snapshot. + rc = lb.beDestroySnapshot(be_name, snap_name) + else: + + # Destroy a BE. Passing in 1 for the second arg destroys + # any snapshots the BE may have as well. + + rc = lb.beDestroy(be.trgt_be_name_or_snapshot[0], 1, force_unmount) + + # Check if the BE that was just destroyed was the + # 'active on boot' BE. If it was, display a message letting + # the user know that the 'active' BE is now also the + # 'active on boot' BE. + if be_active_on_boot == be.trgt_be_name_or_snapshot[0] and rc == 0: + msg.printMsg(msg.Msgs.BEADM_MSG_ACTIVE_ON_BOOT, + be_active, -1) + + if rc == 0: + try: + shutil.rmtree("/var/log/beadm/" + \ + be.trgt_be_name_or_snapshot[0], True) + except: + msg.printMsg(msg.Msgs.BEADM_ERR_LOG_RM, + "/var/log/beadm/" + be.trgt_be_name_or_snapshot[0], -1) + + return 0 + + be.msg_buf["0"] = be.trgt_be_name_or_snapshot[0] + if rc == msg.Msgs.BE_ERR_MOUNTED: + be.msg_buf["1"] = be.msg_buf["2"] = be.trgt_be_name_or_snapshot[0] + msg.printMsg(msg.Msgs.BEADM_ERR_MOUNTED, be.msg_buf, -1) + return 1 + elif rc == msg.Msgs.BE_ERR_DESTROY_CURR_BE: + msg.printMsg(msg.Msgs.BEADM_ERR_DESTROY_ACTIVE, \ + be.msg_buf["0"], -1) + return 1 + elif rc == msg.Msgs.BE_ERR_ZONES_UNMOUNT: + be.msg_buf["1"] = be.trgt_be_name_or_snapshot[0] + msg.printMsg(msg.Msgs.BE_ERR_ZONES_UNMOUNT, be.msg_buf, -1) + return 1 + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_DESTROY, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_DESTROY, be.msg_buf, -1) + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def list(opts): + """ + Description: List the attributes of a Boot Environment. + The following is the subcommand, options + and args that make up the opts object + passed in: + + list [[-a] | [-d] [-s]] [-H] [beName] + + -a displays all info + -d displays BE info plus dataset info + -s displays BE info plus snapshot info + -H displays info parsable by a computer + + Parameters: + opts - A object containing the list subcommand + and all the options and arguments passed in + on the command line mentioned above. + + Returns: + 0 - Success + 1 - Failure + """ + + be = BootEnvironment() + + list_all_attrs = "" + list_datasets = "" + list_snapshots = "" + dont_display_headers = False + be_name = None + be_list = None + + # Counters for detecting multiple options. + # e.g. beadm list -a -a newbe + num_a_opts = 0 + num_d_opts = 0 + num_s_opts = 0 + num_h_opts = 0 + + try: + opts_args, be.trgt_be_name_or_snapshot = getopt.getopt(opts, "adHs") + except getopt.GetoptError: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + for opt, arg in opts_args: + if opt == "-a": + list_all_attrs = opt + num_a_opts += 1 + elif opt == "-d": + list_datasets = opt + num_d_opts += 1 + elif opt == "-s": + list_snapshots = opt + num_s_opts += 1 + elif opt == "-H": + dont_display_headers = True + num_h_opts += 1 + + if num_a_opts > 1 or num_d_opts > 1 or num_s_opts > 1 or num_h_opts > 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + if len(be.trgt_be_name_or_snapshot) > 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + if len(be.trgt_be_name_or_snapshot) == 1: + be_name = be.trgt_be_name_or_snapshot[0] + if lb.beVerifyBEName(be_name) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + if (list_all_attrs == "-a" and (list_datasets == "-d" \ + or list_snapshots == "-s")): + msg.printMsg(msg.Msgs.BEADM_ERR_MUTUALLY_EXCL, + list_all_attrs + " " + list_datasets + " " + + list_snapshots, -1) + usage() + + list_options = "" + + # When zones are implemented add "listZones == "-z" below + + # Coelesce options to pass to displayBEs + + if (list_datasets == "-d" and list_snapshots == "-s" or \ + list_all_attrs == "-a"): + list_options = "-a" + elif list_datasets != "" or list_snapshots != "" or list_all_attrs != "": + list_options = list_datasets + " " + list_snapshots + + rc, be_list = lb.beList() + if rc != 0: + if rc == msg.Msgs.BE_ERR_BE_NOENT: + if be_name == None: + msg.printMsg(msg.Msgs.BEADM_ERR_NO_BES_EXIST, + None, -1) + return 1 + + string = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, + be_name) + else: + string = lb.beGetErrDesc(rc) + if string == None: + string = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_LIST, string, -1) + return 1 + + # classify according to command line options + if list_options.find("-a") != -1 or \ + (list_options.find("-d") != -1 and list_options.find("-s") != -1): + list_object = CompleteList(dont_display_headers) #all + elif list_options.find("-d") != -1: + list_object = DatasetList(dont_display_headers) #dataset + elif list_options.find("-s") != -1: + list_object = SnapshotList(dont_display_headers) #snapshot + else: list_object = BEList(dont_display_headers) #only BE + + # use list method for object + if list_object.list(be_list, dont_display_headers, be_name) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_LIST_DATA, None, -1) + return 1 + + return 0 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def mount(opts): + """ + Description: Mount a Boot Environment on a directory. + The following is the subcommand, options + and args that make up the opts object + passed in: + + mount beName [mountpoint] + + Parameters: + opts - A object containing the mount subcommand + and all the options and arguments passed in + on the command line mentioned above. + + Returns: + 0 - Success + 1 - Failure + """ + + be = BootEnvironment() + + mountpoint = None + + try: + be_name_mnt_point = getopt.getopt(opts, "")[1] + except getopt.GetoptError: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + mnt_point_len = len(be_name_mnt_point) + + if mnt_point_len != 2: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + else: + # Check for leading / in mount point + mountpoint = be_name_mnt_point[1] + if not mountpoint.startswith('/'): + msg.printMsg(msg.Msgs.BEADM_ERR_MOUNTPOINT, + mountpoint, -1) + return 1 + + if lb.beVerifyBEName(be_name_mnt_point[0]) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + rc = lb.beMount(be_name_mnt_point[0], mountpoint) + if rc == 0: + return 0 + + be.msg_buf["0"] = be_name_mnt_point[0] + if rc == msg.Msgs.BE_ERR_MOUNTED: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_MOUNT_EXISTS, + be_name_mnt_point[0]) + elif rc == msg.Msgs.BE_ERR_BE_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, + be_name_mnt_point[0]) + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_MOUNT, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_MOUNT, be.msg_buf, -1) + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def unmount(opts): + """ + Description: Unmount a Boot Environment. + The following is the subcommand, options + and args that make up the opts object + passed in: + + unmount [-f] beName + + Parameters: + opts - A object containing the unmount subcommand + and all the options and arguments passed in + on the command line mentioned above. + + Returns: + 0 - Success + 1 - Failure + """ + + be = BootEnvironment() + + force_unmount = 0 + + # Counter for detecting multiple options. + # e.g. beadm unmount -f -f newbe + num_f_opts = 0 + + try: + optlist, args = getopt.getopt(opts, "f") + except getopt.GetoptError: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + for opt, arg in optlist: + if opt == "-f": + force_unmount = 1 + num_f_opts += 1 + + if num_f_opts > 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + if len(args) != 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + if lb.beVerifyBEName(args[0]) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + rc = lb.beUnmount(args[0], force_unmount) + if rc == 0: + return 0 + + be.msg_buf["0"] = args[0] + if rc == msg.Msgs.BE_ERR_UMOUNT_CURR_BE: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_UNMOUNT_ACTIVE, + args[0]) + elif rc == msg.Msgs.BE_ERR_UMOUNT_SHARED: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_SHARED_FS, args[0]) + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_UNMOUNT, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_UNMOUNT, be.msg_buf, -1) + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def rename(opts): + """ + Description: Rename the name of a Boot Environment. + The following is the subcommand, options + and args that make up the opts object + passed in: + + rename beName newBeName + + Parameters: + opts - A object containing the mount subcommand + and all the options and arguments passed in + on the command line mentioned above. + + Returns: + 0 - Success + 1 - Failure + """ + + be = BootEnvironment() + + try: + be_names = getopt.getopt(opts, "")[1] + except getopt.GetoptError: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + if len(be_names) != 2: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + usage() + + if lb.beVerifyBEName(be_names[0]) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + if lb.beVerifyBEName(be_names[1]) != 0: + msg.printMsg(msg.Msgs.BEADM_ERR_BENAME, None, -1) + return 1 + + rc = lb.beRename(be_names[0], be_names[1]) + + if rc == 0: + return 0 + + be.msg_buf["0"] = be_names[0] + if rc == msg.Msgs.BE_ERR_BE_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, + be_names[0]) + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_RENAME, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_RENAME, be.msg_buf, -1) + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# End of CLI public functions +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Verify the options and arguments for the beadm create subcommand + +def verifyCreateOptionsArgs(be): + """Check valid BE names.""" + + len_be_args = len(be.trgt_be_name_or_snapshot) + if len_be_args < 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + return 1 + if len_be_args > 1: + msg.printMsg(msg.Msgs.BEADM_ERR_OPT_ARGS, None, -1) + idx = 0 + while len_be_args > idx: + msg.printMsg(msg.Msgs.BEADM_MSG_FREE_FORMAT, + be.trgt_be_name_or_snapshot[idx], -1) + idx += 1 + return 1 + + return 0 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def parseCLI(cli_opts_args): + """Parse command line interface arguments.""" + + gettext.install("beadm", "/usr/lib/locale") + + if len(cli_opts_args) == 0: + usage() + + subcommand = cli_opts_args[0] + opts_args = cli_opts_args[1:] + + if subcommand == "activate": + rc = activate(opts_args) + elif subcommand == "create": + rc = create(opts_args) + elif subcommand == "destroy": + rc = destroy(opts_args) + elif subcommand == "list": + rc = list(opts_args) + elif subcommand == "mount": + rc = mount(opts_args) + elif subcommand == "rename": + rc = rename(opts_args) + elif subcommand == "upgrade": + rc = upgrade(opts_args) + elif subcommand == "unmount" or \ + subcommand == "umount": #aliased for convenience + rc = unmount(opts_args) + elif subcommand == "verify": + rc = verify() + else: + msg.printMsg(msg.Msgs.BEADM_ERR_ILL_SUBCOMMAND, + subcommand, -1) + usage() + + return(rc) + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def main(): + """main function.""" + + gettext.install("beadm", "/usr/lib/locale") + + if not isBeadmSupported(): + return(1) + + return(parseCLI(sys.argv[1:])) + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def initBELog(log_id, be): + """ + Initiate the BE log + + Format of the log + yyyymmdd_hhmmss - 20071130_140558 + yy - year; 2007 + mm - month; 11 + dd - day; 30 + hh - hour; 14 + mm - minute; 05 + ss - second; 58 + """ + + # /var/log/beadm/<beName>/<logId>.log.<yyyymmdd_hhmmss> + + date = time.strftime("%Y%m%d_%H%M%S", time.localtime()) + + be.log = "/var/log/beadm/" + be.trgt_be_name_or_snapshot[0] + \ + "/" + log_id + ".log" + "." + date + + if not os.path.isfile(be.log) and not os.path.islink(be.log): + if not os.path.isdir(os.path.dirname(be.log)): + try: + os.makedirs(os.path.dirname(be.log), 0644) + except OSError: + be.msg_buf["0"] = be.trgt_be_name_or_snapshot[0] + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, + 0) + msg.printMsg(msg.Msgs.BEADM_ERR_CREATE, + be.msg_buf, -1) + return 1 + try: + be.log_id = open(be.log, "a") + except IOError: + msg.printMsg(msg.Msgs.BEADM_ERR_LOG_CREATE, + None, -1) + return 1 + else: + # Should never happen due to new time stamp each call + msg.printMsg(msg.Msgs.BEADM_ERR_LOG_CREATE, None, -1) + return 1 + + return 0 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def cleanupBELog(be): + """Clean up BE log.""" + + be.log_id.close() + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def displayDestructionQuestion(be): + """Display a destruction question and wait for user response.""" + + msg.printMsg(msg.Msgs.BEADM_MSG_DESTROY, be.trgt_be_name_or_snapshot[0], -1) + while True: + try: + value = raw_input().strip().upper() + except KeyboardInterrupt: + return False + if (value == 'Y' or value == 'YES'): + return True + elif len(value) == 0 or value == 'N' or value == 'NO': + msg.printMsg(msg.Msgs.BEADM_MSG_DESTROY_NO, + be.trgt_be_name_or_snapshot[0], -1) + return False + else: + msg.printMsg(msg.Msgs.BEADM_ERR_INVALID_RESPONSE, + -1) + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def setMaxColumnWidths(be_max_w, ds_max_w, ss_max_w, be_list): + """Figure out max column widths for BE's, Datasets and Snapshots.""" + + for be_item in be_list: + if be_item.get("orig_be_name") is not None: + determineMaxBEColWidth(be_item, be_max_w) + if be_item.get("dataset") is not None: + determineMaxDSColWidth(be_item, ds_max_w) + if be_item.get("snap_name") is not None: + determineMaxSSColWidth(be_item, ss_max_w) + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def getActiveBEAndActiveOnBootBE(): + """Return the 'active on boot' BE, the 'active' BE or None.""" + + active_be = None + active_be_on_boot = None + + rc, be_list = lb.beList() + + if rc != 0: + if rc == msg.Msgs.BE_ERR_BE_NOENT: + string = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_BES_EXIST) + else: + string = lb.beGetErrDesc(rc) + if string == None: + string = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_LIST, string, -1) + return None + + for be_vals in be_list: + srcBeName = be_vals.get("orig_be_name") + if be_vals.get("active"): + active_be = srcBeName + if be_vals.get("active_boot"): + active_be_on_boot = srcBeName + if active_be is not None and active_be_on_boot is not None: + break + + return active_be, active_be_on_boot + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def createSnapshot(be): + """Create a snapshot.""" + + be_name, snap_name = be.trgt_be_name_or_snapshot[0].split("@") + + rc = lb.beCreateSnapshot(be_name, snap_name)[0] + + if rc == 0: + return 0 + + be.msg_buf["0"] = be.trgt_be_name_or_snapshot[0] + if rc == msg.Msgs.BE_ERR_BE_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, + be_name) + elif rc == msg.Msgs.BE_ERR_SS_EXISTS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_SNAP_EXISTS, + be.trgt_be_name_or_snapshot[0]) + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_CREATE, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_CREATE, be.msg_buf, -1) + + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def createBE(be): + """Create a Boot Environment.""" + + rc = lb.beCopy(be.trgt_be_name_or_snapshot[0], be.src_be_name_or_snapshot, + None, be.trgt_rpool, be.properties, be.description)[0] + + if rc == 0: + msg.printMsg(msg.Msgs.BEADM_MSG_BE_CREATE_SUCCESS, + be.trgt_be_name_or_snapshot[0], be.log_id) + return 0 + + be.msg_buf["0"] = be.trgt_be_name_or_snapshot[0] + if rc == msg.Msgs.BE_ERR_BE_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, + be.src_be_name_or_snapshot) + elif rc == msg.Msgs.BE_ERR_BE_EXISTS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_BE_EXISTS, + be.trgt_be_name_or_snapshot[0]) + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_CREATE, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_CREATE, be.msg_buf, be.log_id) + + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def createBEFromSnapshot(be): + """Create a BE based off a snapshot.""" + + be_name, snap_name = be.src_be_name_or_snapshot.split("@") + + rc = lb.beCopy(be.trgt_be_name_or_snapshot[0], be_name, snap_name, + be.trgt_rpool, be.properties, be.description)[0] + + if rc == 0: + msg.printMsg(msg.Msgs.BEADM_MSG_BE_CREATE_SUCCESS, + be.trgt_be_name_or_snapshot[0], be.log_id) + return 0 + + be.msg_buf["0"] = be.trgt_be_name_or_snapshot[0] + if rc == msg.Msgs.BE_ERR_SS_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_SNAP_DOES_NOT_EXISTS, + be.src_be_name_or_snapshot) + elif rc == msg.Msgs.BE_ERR_BE_EXISTS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_BE_EXISTS, \ + be.trgt_be_name_or_snapshot[0]) + elif rc == msg.Msgs.BE_ERR_BE_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, \ + be_name) + elif rc == msg.Msgs.BE_ERR_PERM or rc == msg.Msgs.BE_ERR_ACCESS: + be.msg_buf["1"] = msg.getMsg(msg.Msgs.BEADM_ERR_PERMISSIONS, rc) + msg.printMsg(msg.Msgs.BEADM_ERR_CREATE, be.msg_buf, -1) + return 1 + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_CREATE, be.msg_buf, be.log_id) + + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def activateBE(be): + """ + Activate a BE. Called from create() when -a is provided as CLI + Option. + """ + + rc = lb.beActivate(be.trgt_be_name_or_snapshot[0]) + if rc == 0: + return 0 + + be.msg_buf["0"] = be.trgt_be_name_or_snapshot[0] + if rc == msg.Msgs.BE_ERR_BE_NOENT: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_BE_DOES_NOT_EXIST, opts[0]) + else: + be.msg_buf["1"] = lb.beGetErrDesc(rc) + if be.msg_buf["1"] == None: + be.msg_buf["1"] = \ + msg.getMsg(msg.Msgs.BEADM_ERR_NO_MSG, rc) + + msg.printMsg(msg.Msgs.BEADM_ERR_ACTIVATE, be.msg_buf, -1) + + return 1 + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def isBeadmSupported(): + """ + Currently the only environment that beadm is supported in is + a global zone. Check that beadm is executing in a + global zone and not in a non-global zone. + """ + + try: + proc = subprocess.Popen("/sbin/zonename", + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT) + # Grab stdout. + zonename = proc.communicate()[0].rstrip('\n') + except OSError, (errno, strerror): + msg.printMsg(msg.Msgs.BEADM_ERR_OS, strerror, -1) + # Ignore a failed attempt to retreive the zonename. + return True + + if zonename != "global": + msg.printMsg(msg.Msgs.BEADM_ERR_NOT_SUPPORTED_NGZ, None, -1) + return False + + return True + + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +if __name__ == "__main__": + try: + RC = main() + except SystemExit, e: + raise e + except: + traceback.print_exc() + sys.exit(99) + sys.exit(RC) diff --git a/usr/src/cmd/beadm/messages.py b/usr/src/cmd/beadm/messages.py new file mode 100644 index 0000000000..87979a6797 --- /dev/null +++ b/usr/src/cmd/beadm/messages.py @@ -0,0 +1,264 @@ +# 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. +# + +""" +beadm - The Boot Environment Administration tool. + +A module containing all of the messages output by beadm. +""" + +import sys +from beadm import _ + +class Msgs: + """Indices corresponding to message numbers for beadm.""" + + (BEADM_ERR_ACTIVATE, + BEADM_ERR_BE_EXISTS, + BEADM_ERR_SNAP_EXISTS, + BEADM_ERR_CREATE, + BEADM_ERR_DESTROY, + BEADM_ERR_DESTROY_ACTIVE, + BEADM_ERR_BE_DOES_NOT_EXIST, + BEADM_ERR_NO_BES_EXIST, + BEADM_ERR_MSG_SUB, + BEADM_ERR_ILL_SUBCOMMAND, + BEADM_ERR_INVALID_RESPONSE, + BEADM_ERR_LIST, + BEADM_ERR_LIST_DATA, + BEADM_ERR_LOG_CREATE, + BEADM_ERR_LOG_RM, + BEADM_ERR_MOUNT, + BEADM_ERR_MOUNT_EXISTS, + BEADM_ERR_MOUNTED, + BEADM_ERR_MOUNTPOINT, + BEADM_ERR_MUTUALLY_EXCL, + BEADM_ERR_NO_MSG, + BEADM_ERR_NO_ZPOOL, + BEADM_ERR_NOT_SUPPORTED_NGZ, + BEADM_ERR_OPT_ARGS, + BEADM_ERR_OS, + BEADM_ERR_PERMISSIONS, + BEADM_ERR_RENAME, + BEADM_ERR_SHARED_FS, + BEADM_ERR_SNAP_DOES_NOT_EXISTS, + BEADM_ERR_UNMOUNT, + BEADM_ERR_UNMOUNT_ACTIVE, + BEADM_ERR_BENAME, + BEADM_MSG_ACTIVE_ON_BOOT, + BEADM_MSG_DESTROY, + BEADM_MSG_DESTROY_NO, + BEADM_MSG_BE_CREATE_START, + BEADM_MSG_BE_CREATE_SUCCESS, + BEADM_MSG_FREE_FORMAT, + ) = range(38) + + # Indices corresponding to message numbers for libbe that we are + # interested in expanding messages. + (BE_ERR_ACCESS, + BE_ERR_ACTIVATE_CURR, + BE_ERR_AUTONAME, + BE_ERR_BE_NOENT, + BE_ERR_BUSY, + BE_ERR_CANCELED, + BE_ERR_CLONE, + BE_ERR_COPY, + BE_ERR_CREATDS, + BE_ERR_CURR_BE_NOT_FOUND, + BE_ERR_DESTROY, + BE_ERR_DEMOTE, + BE_ERR_DSTYPE, + BE_ERR_BE_EXISTS, + BE_ERR_INIT, + BE_ERR_INTR, + BE_ERR_INVAL, + BE_ERR_INVALPROP, + BE_ERR_INVALMOUNTPOINT, + BE_ERR_MOUNT, + BE_ERR_MOUNTED, + BE_ERR_NAMETOOLONG, + BE_ERR_NOENT, + BE_ERR_POOL_NOENT, + BE_ERR_NODEV, + BE_ERR_NOTMOUNTED, + BE_ERR_NOMEM, + BE_ERR_NONINHERIT, + BE_ERR_NXIO, + BE_ERR_NOSPC, + BE_ERR_NOTSUP, + BE_ERR_OPEN, + BE_ERR_PERM, + BE_ERR_UNAVAIL, + BE_ERR_PROMOTE, + BE_ERR_ROFS, + BE_ERR_READONLYDS, + BE_ERR_READONLYPROP, + BE_ERR_SS_EXISTS, + BE_ERR_SS_NOENT, + BE_ERR_UMOUNT, + BE_ERR_UMOUNT_CURR_BE, + BE_ERR_UMOUNT_SHARED, + BE_ERR_UNKNOWN, + BE_ERR_ZFS, + BE_ERR_DESTROY_CURR_BE, + BE_ERR_GEN_UUID, + BE_ERR_PARSE_UUID, + BE_ERR_NO_UUID, + BE_ERR_ZONE_NO_PARENTBE, + BE_ERR_ZONE_MULTIPLE_ACTIVE, + BE_ERR_ZONE_NO_ACTIVE_ROOT, + BE_ERR_ZONE_ROOT_NOT_LEGACY, + BE_ERR_NO_MOUNTED_ZONE, + BE_ERR_MOUNT_ZONEROOT, + BE_ERR_UMOUNT_ZONEROOT, + BE_ERR_ZONES_UNMOUNT, + BE_ERR_FAULT, + BE_ERR_RENAME_ACTIVE, + BE_ERR_NO_MENU, + BE_ERR_DEV_BUSY, + BE_ERR_BAD_MENU_PATH, + BE_ERR_ZONE_SS_EXISTS + ) = range(4000, 4063) + + # Error message dictionaries. + mBeadmErr = {} + mBeadmOut = {} + mBeadmLog = {} + + # Errors from beadm (to stderr). + mBeadmErr[BEADM_ERR_ACTIVATE] = _("Unable to activate %(0)s.\n%(1)s") + mBeadmErr[BEADM_ERR_BE_EXISTS] = _("BE %s already exists. Please choose a different BE name.") + mBeadmErr[BEADM_ERR_BE_DOES_NOT_EXIST] = _("%s does not exist or appear to be a valid BE.\nPlease check that the name of the BE provided is correct.") + mBeadmErr[BEADM_ERR_NO_BES_EXIST] = _("No boot environments found on this system.") + mBeadmErr[BEADM_ERR_CREATE] = _("Unable to create %(0)s.\n%(1)s") + mBeadmErr[BEADM_ERR_DESTROY] = _("Unable to destroy %(0)s.\n%(1)s") + mBeadmErr[BEADM_ERR_DESTROY_ACTIVE] = _("%(0)s is the currently active BE and cannot be destroyed.\nYou must boot from another BE in order to destroy %(1)s.") + mBeadmErr[BEADM_ERR_MSG_SUB] = _("Fatal error. No message associated with index %d") + mBeadmErr[BEADM_ERR_ILL_SUBCOMMAND] = _("Illegal subcommand %s") + mBeadmErr[BEADM_ERR_INVALID_RESPONSE] = _("Invalid response. Please enter 'y' or 'n'.") + mBeadmErr[BEADM_ERR_LIST] = _("Unable to display Boot Environment: %s") + mBeadmErr[BEADM_ERR_LIST_DATA] = _("Unable to process list data.") + mBeadmErr[BEADM_ERR_LOG_CREATE] = _("Unable to create log file.") + mBeadmErr[BEADM_ERR_LOG_RM] = _("Unable to remove %s") + mBeadmErr[BEADM_ERR_MOUNT] = _("Unable to mount %(0)s.\n%(1)s") + mBeadmErr[BEADM_ERR_MOUNT_EXISTS] = _("%s is already mounted.\nPlease unmount the BE before mounting it again.") + mBeadmErr[BEADM_ERR_MOUNTED] = _("Unable to destroy %(0)s.\nIt is currently mounted and must be unmounted before it can be destroyed.\nUse 'beadm unmount %(1)s' to unmount the BE before destroying\nit or 'beadm destroy -fF %(2)s'.") + mBeadmErr[BEADM_ERR_MOUNTPOINT] = _("Invalid mount point %s. Mount point must start with a /.") + mBeadmErr[BEADM_ERR_MUTUALLY_EXCL] = _("Invalid options: %s are mutually exclusive.") + mBeadmErr[BEADM_ERR_NO_MSG] = _("Unable to find message for error code: %d") + mBeadmErr[BEADM_ERR_NO_ZPOOL] = _("BE: %s was not found in any pool.\n The pool may not exist or the name of the BE is not correct.") + mBeadmErr[BEADM_ERR_NOT_SUPPORTED_NGZ] = _("beadm is not supported in a non-global zone.") + mBeadmErr[BEADM_ERR_OPT_ARGS] = _("Invalid options and arguments:") + mBeadmErr[BEADM_ERR_OS] = _("System error: %s") + mBeadmErr[BEADM_ERR_RENAME] = _("Rename of BE %(0)s failed.\n%(1)s") + mBeadmErr[BEADM_ERR_SHARED_FS] = _("%s is a shared file system and it cannot be unmounted.") + mBeadmErr[BEADM_ERR_SNAP_DOES_NOT_EXISTS] = _("%s does not exist or appear to be a valid snapshot.\nPlease check that the name of the snapshot provided is correct.") + mBeadmErr[BEADM_ERR_SNAP_EXISTS] = _("Snapshot %s already exists.\n Please choose a different snapshot name.") + mBeadmErr[BEADM_ERR_UNMOUNT] = _("Unable to unmount %(0)s.\n%(1)s") + mBeadmErr[BEADM_ERR_UNMOUNT_ACTIVE] = _("%s is the currently active BE.\nIt cannot be unmounted unless another BE is the currently active BE.") + mBeadmErr[BE_ERR_ZONES_UNMOUNT] = _("Unable to destroy one of %(0)s's zone BE's.\nUse 'beadm destroy -fF %(1)s' or 'zfs -f destroy <dataset>'.") + mBeadmErr[BEADM_ERR_PERMISSIONS] = _("You have insufficient privileges to execute this command.\nEither use 'pfexec' to execute the command or become superuser.") + mBeadmErr[BEADM_ERR_BENAME] = _("The BE name provided is invalid.\n Please check it and try again.") + + # Catchall + mBeadmErr[BEADM_MSG_FREE_FORMAT] = "%s" + + # Messages from beadm (to stdout). + mBeadmOut[BEADM_MSG_ACTIVE_ON_BOOT] = _("The BE that was just destroyed was the 'active on boot' BE.\n%s is now the 'active on boot' BE. Use 'beadm activate' to change it.\n") + mBeadmOut[BEADM_MSG_DESTROY] = _("Are you sure you want to destroy %s? This action cannot be undone(y/[n]):") + mBeadmOut[BEADM_MSG_DESTROY_NO] = _("%s has not been destroyed.\n") + + # Messages from beadm (to log only). + mBeadmLog[BEADM_MSG_BE_CREATE_START] = "Attempting to create %s" + mBeadmLog[BEADM_MSG_BE_CREATE_SUCCESS] = "%s was created successfully" + +msgLog, msgOut, msgErr = range(3) + +def printLog(string, log_fd): + """Print log.""" + + sendMsg(string, msgLog, log_fd) + +def printStdout(string, log_fd): + """Print standard output.""" + + sendMsg(string, msgOut, log_fd) + +def printStderr(string, log_fd): + """Print standard error.""" + + sendMsg(string, msgErr, log_fd) + +def composeMsg(string, txt=None): + """ + Compose the message to be dispayed. + txt can be either a list or string object. + Return the newly composed string. + """ + + try: + msg = string % txt + except TypeError: + msg = string + + return (msg) + +def sendMsg(string, mode, log_fd=-1): + """Send message.""" + + if mode == msgOut: + print >> sys.stdout, string, + if mode == msgErr: + print >> sys.stderr, string + if log_fd != -1 or mode == msgLog: + log_fd.write(string + "\n") + +def printMsg(msg_idx=-1, txt="", log_fd=-1): + """Print the message based on the message index.""" + + if msg_idx in Msgs.mBeadmErr: + printStderr(composeMsg(Msgs.mBeadmErr[msg_idx], txt), + log_fd) + elif msg_idx in Msgs.mBeadmOut: + printStdout(composeMsg(Msgs.mBeadmOut[msg_idx], txt), + log_fd) + elif msg_idx in Msgs.mBeadmLog: + printLog(composeMsg(Msgs.mBeadmLog[msg_idx], txt), log_fd) + else: + printStderr(composeMsg(Msgs.mLibbe[BEADM_ERR_MSG_SUB], + msg_idx), -1) + sys.exit(1) + +def getMsg(msg_idx=-1, txt=""): + """Print the message based on the message index.""" + + if msg_idx in Msgs.mBeadmErr: + return(composeMsg(Msgs.mBeadmErr[msg_idx], txt)) + elif msg_idx in Msgs.mBeadmOut: + return(composeMsg(Msgs.mBeadmOut[msg_idx], txt)) + elif msg_idx in Msgs.mBeadmLog: + return(composeMsg(Msgs.mBeadmLog[msg_idx], txt)) + else: + return(composeMsg(Msgs.mLibbe[BEADM_ERR_MSG_SUB])) + sys.exit(1) diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index 73d817e14a..e9e9af7d0f 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -238,6 +238,8 @@ SUBDIRS += \ udapl \ libzpool \ libzfs \ + libbe \ + pylibbe \ libzfs_jni \ pyzfs \ pysolaris \ @@ -569,6 +571,7 @@ $(CLOSED_BUILD)libc: $(CLOSED)/lib/libc_i18n libast: libsocket libadutils: libldap5 libresolv libsocket libnsl nsswitch: libadutils libidmap +libbe: libzfs libbsm: libtsol libcmd: libsum libast libsocket libnsl libcmdutils: libavl @@ -659,6 +662,7 @@ sun_fc: libdevinfo libsysevent libnvpair libsun_ima: libdevinfo libsysevent libnsl sun_sas: libdevinfo libsysevent libnvpair libkstat libdevid libgrubmgmt: libdevinfo libzfs libfstyp +pylibbe: libbe libzfs pyzfs: libnvpair libzfs pysolaris: libsec libidmap libreparse: libnvpair diff --git a/usr/src/lib/libbe/Makefile b/usr/src/lib/libbe/Makefile new file mode 100644 index 0000000000..d9beee702d --- /dev/null +++ b/usr/src/lib/libbe/Makefile @@ -0,0 +1,72 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +include ../Makefile.lib + +HDRS= libbe.h \ + libbe_priv.h + +HDRDIR= common + +SUBDIRS= $(MACH) +TESTDIR= tbeadm + +all := TARGET= all +clean := TARGET= clean +clobber := TARGET= clobber +install := TARGET= install +lint := TARGET= lint +test := TARGET= test + +POFILE = libbe.po +MSGFILES = `$(GREP) -l gettext $(HDRDIR)/*.[ch]` + +.KEEP_STATE: + +all install lint: install_h $(SUBDIRS) + +clean clobber: $(SUBDIRS) $(TESTDIR) + +$(POFILE): pofile_MSGFILES + +install_h: $(ROOTHDRS) + +check: $(CHECKHDRS) + +_msg: $(MSGDOMAINPOFILE) + +test: + cd tbeadm; pwd; $(MAKE) install + +$(TESTDIR): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include $(SRC)/Makefile.msg.targ +include $(SRC)/lib/Makefile.targ diff --git a/usr/src/lib/libbe/Makefile.com b/usr/src/lib/libbe/Makefile.com new file mode 100644 index 0000000000..fad5ba58ed --- /dev/null +++ b/usr/src/lib/libbe/Makefile.com @@ -0,0 +1,62 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +LIBRARY= libbe.a +VERS= .1 + +OBJECTS= \ + be_activate.o \ + be_create.o \ + be_list.o \ + be_mount.o \ + be_rename.o \ + be_snapshot.o \ + be_utils.o \ + be_zones.o + +include ../../Makefile.lib + +LIBS= $(DYNLIB) $(LINTLIB) + +SRCDIR= ../common + +INCS += -I$(SRCDIR) + +C99MODE= $(C99_ENABLE) + +LDLIBS += -lzfs -linstzones -luuid -lnvpair -lc -lgen +CPPFLAGS += $(INCS) + +$(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC) + +.KEEP_STATE: + +all: $(LIBS) $(LIBRARY) + +lint: lintcheck + +install: $(ROOTLIBS) + +include ../../Makefile.targ diff --git a/usr/src/lib/libbe/common/be_activate.c b/usr/src/lib/libbe/common/be_activate.c new file mode 100644 index 0000000000..c83e442fa9 --- /dev/null +++ b/usr/src/lib/libbe/common/be_activate.c @@ -0,0 +1,1191 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include <assert.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/mnttab.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <libbe.h> +#include <libbe_priv.h> + +char *mnttab = MNTTAB; + +/* + * Private function prototypes + */ +static int set_bootfs(char *boot_rpool, char *be_root_ds); +static int set_canmount(be_node_list_t *, char *); +static int be_do_installgrub(be_transaction_data_t *); +static int be_get_grub_vers(be_transaction_data_t *, char **, char **); +static int get_ver_from_capfile(char *, char **); +static int be_promote_zone_ds(char *, char *); +static int be_promote_ds_callback(zfs_handle_t *, void *); + +/* ******************************************************************** */ +/* Public Functions */ +/* ******************************************************************** */ + +/* + * Function: be_activate + * Description: Calls _be_activate which activates the BE named in the + * attributes passed in through be_attrs. The process of + * activation sets the bootfs property of the root pool, resets + * the canmount property to noauto, and sets the default in the + * grub menu to the entry corresponding to the entry for the named + * BE. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The follow attribute values are used by this function: + * + * BE_ATTR_ORIG_BE_NAME *required + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_activate(nvlist_t *be_attrs) +{ + int ret = BE_SUCCESS; + char *be_name = NULL; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get the BE name to activate */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_ORIG_BE_NAME, &be_name) + != 0) { + be_print_err(gettext("be_activate: failed to " + "lookup BE_ATTR_ORIG_BE_NAME attribute\n")); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* Validate BE name */ + if (!be_valid_be_name(be_name)) { + be_print_err(gettext("be_activate: invalid BE name %s\n"), + be_name); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + ret = _be_activate(be_name); + + be_zfs_fini(); + + return (ret); +} + +/* ******************************************************************** */ +/* Semi Private Functions */ +/* ******************************************************************** */ + +/* + * Function: _be_activate + * Description: This does the actual work described in be_activate. + * Parameters: + * be_name - pointer to the name of BE to activate. + * + * Return: + * BE_SUCCESS - Success + * be_errnot_t - Failure + * Scope: + * Public + */ +int +_be_activate(char *be_name) +{ + be_transaction_data_t cb = { 0 }; + zfs_handle_t *zhp = NULL; + char root_ds[MAXPATHLEN]; + char *cur_vers = NULL, *new_vers = NULL; + be_node_list_t *be_nodes = NULL; + uuid_t uu = {0}; + int entry, ret = BE_SUCCESS; + int zret = 0; + + /* + * TODO: The BE needs to be validated to make sure that it is actually + * a bootable BE. + */ + + if (be_name == NULL) + return (BE_ERR_INVAL); + + /* Set obe_name to be_name in the cb structure */ + cb.obe_name = be_name; + + /* find which zpool the be is in */ + if ((zret = zpool_iter(g_zfs, be_find_zpool_callback, &cb)) == 0) { + be_print_err(gettext("be_activate: failed to " + "find zpool for BE (%s)\n"), cb.obe_name); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_activate: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + return (ret); + } + + be_make_root_ds(cb.obe_zpool, cb.obe_name, root_ds, sizeof (root_ds)); + cb.obe_root_ds = strdup(root_ds); + + if (getzoneid() == GLOBAL_ZONEID) { + if (be_has_grub() && (ret = be_get_grub_vers(&cb, &cur_vers, + &new_vers)) != BE_SUCCESS) { + be_print_err(gettext("be_activate: failed to get grub " + "versions from capability files.\n")); + return (ret); + } + if (cur_vers != NULL) { + /* + * We need to check to see if the version number from + * the BE being activated is greater than the current + * one. + */ + if (new_vers != NULL && + atof(cur_vers) < atof(new_vers)) { + if ((ret = be_do_installgrub(&cb)) + != BE_SUCCESS) { + free(new_vers); + free(cur_vers); + return (ret); + } + free(new_vers); + } + free(cur_vers); + } else if (new_vers != NULL) { + if ((ret = be_do_installgrub(&cb)) != BE_SUCCESS) { + free(new_vers); + return (ret); + } + free(new_vers); + } + if (!be_has_menu_entry(root_ds, cb.obe_zpool, &entry)) { + if ((ret = be_append_menu(cb.obe_name, cb.obe_zpool, + NULL, NULL, NULL)) != BE_SUCCESS) { + be_print_err(gettext("be_activate: Failed to " + "add BE (%s) to the GRUB menu\n"), + cb.obe_name); + goto done; + } + } + if (be_has_grub()) { + if ((ret = be_change_grub_default(cb.obe_name, + cb.obe_zpool)) != BE_SUCCESS) { + be_print_err(gettext("be_activate: failed to " + "change the default entry in menu.lst\n")); + goto done; + } + } + } + + if ((ret = _be_list(cb.obe_name, &be_nodes)) != BE_SUCCESS) { + return (ret); + } + + if ((ret = set_canmount(be_nodes, "noauto")) != BE_SUCCESS) { + be_print_err(gettext("be_activate: failed to set " + "canmount dataset property\n")); + goto done; + } + + if ((ret = set_bootfs(be_nodes->be_rpool, root_ds)) != BE_SUCCESS) { + be_print_err(gettext("be_activate: failed to set " + "bootfs pool property for %s\n"), root_ds); + goto done; + } + + if ((zhp = zfs_open(g_zfs, root_ds, ZFS_TYPE_FILESYSTEM)) != NULL) { + /* + * We don't need to close the zfs handle at this + * point because The callback funtion + * be_promote_ds_callback() will close it for us. + */ + if (be_promote_ds_callback(zhp, NULL) != 0) { + be_print_err(gettext("be_activate: " + "failed to activate the " + "datasets for %s: %s\n"), + root_ds, + libzfs_error_description(g_zfs)); + ret = BE_ERR_PROMOTE; + goto done; + } + } else { + be_print_err(gettext("be_activate:: failed to open " + "dataset (%s): %s\n"), root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (getzoneid() == GLOBAL_ZONEID && + be_get_uuid(cb.obe_root_ds, &uu) == BE_SUCCESS && + (ret = be_promote_zone_ds(cb.obe_name, cb.obe_root_ds)) + != BE_SUCCESS) { + be_print_err(gettext("be_activate: failed to promote " + "the active zonepath datasets for zones in BE %s\n"), + cb.obe_name); + } + +done: + be_free_list(be_nodes); + return (ret); +} + +/* + * Function: be_activate_current_be + * Description: Set the currently "active" BE to be "active on boot" + * Paramters: + * none + * Returns: + * BE_SUCCESS - Success + * be_errnot_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_activate_current_be(void) +{ + int ret = BE_SUCCESS; + be_transaction_data_t bt = { 0 }; + + if ((ret = be_find_current_be(&bt)) != BE_SUCCESS) { + return (ret); + } + + if ((ret = _be_activate(bt.obe_name)) != BE_SUCCESS) { + be_print_err(gettext("be_activate_current_be: failed to " + "activate %s\n"), bt.obe_name); + return (ret); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_is_active_on_boot + * Description: Checks if the BE name passed in has the "active on boot" + * property set to B_TRUE. + * Paramters: + * be_name - the name of the BE to check + * Returns: + * B_TRUE - if active on boot. + * B_FALSE - if not active on boot. + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +be_is_active_on_boot(char *be_name) +{ + be_node_list_t *be_node = NULL; + + if (be_name == NULL) { + be_print_err(gettext("be_is_active_on_boot: " + "be_name must not be NULL\n")); + return (B_FALSE); + } + + if (_be_list(be_name, &be_node) != BE_SUCCESS) { + return (B_FALSE); + } + + if (be_node == NULL) { + return (B_FALSE); + } + + if (be_node->be_active_on_boot) { + be_free_list(be_node); + return (B_TRUE); + } else { + be_free_list(be_node); + return (B_FALSE); + } +} + +/* ******************************************************************** */ +/* Private Functions */ +/* ******************************************************************** */ + +/* + * Function: set_bootfs + * Description: Sets the bootfs property on the boot pool to be the + * root dataset of the activated BE. + * Parameters: + * boot_pool - The pool we're setting bootfs in. + * be_root_ds - The main dataset for the BE. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +set_bootfs(char *boot_rpool, char *be_root_ds) +{ + zpool_handle_t *zhp; + int err = BE_SUCCESS; + + if ((zhp = zpool_open(g_zfs, boot_rpool)) == NULL) { + be_print_err(gettext("set_bootfs: failed to open pool " + "(%s): %s\n"), boot_rpool, libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + return (err); + } + + err = zpool_set_prop(zhp, "bootfs", be_root_ds); + if (err) { + be_print_err(gettext("set_bootfs: failed to set " + "bootfs property for pool %s: %s\n"), boot_rpool, + libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + zpool_close(zhp); + return (err); + } + + zpool_close(zhp); + return (BE_SUCCESS); +} + +/* + * Function: set_canmount + * Description: Sets the canmount property on the datasets of the + * activated BE. + * Parameters: + * be_nodes - The be_node_t returned from be_list + * value - The value of canmount we setting, on|off|noauto. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +set_canmount(be_node_list_t *be_nodes, char *value) +{ + char ds_path[MAXPATHLEN]; + zfs_handle_t *zhp = NULL; + be_node_list_t *list = be_nodes; + int err = BE_SUCCESS; + + while (list != NULL) { + be_dataset_list_t *datasets = list->be_node_datasets; + + be_make_root_ds(list->be_rpool, list->be_node_name, ds_path, + sizeof (ds_path)); + + if ((zhp = zfs_open(g_zfs, ds_path, ZFS_TYPE_DATASET)) == + NULL) { + be_print_err(gettext("set_canmount: failed to open " + "dataset (%s): %s\n"), ds_path, + libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + return (err); + } + if (zfs_prop_get_int(zhp, ZFS_PROP_MOUNTED)) { + /* + * it's already mounted so we can't change the + * canmount property anyway. + */ + err = BE_SUCCESS; + } else { + err = zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_CANMOUNT), value); + if (err) { + ZFS_CLOSE(zhp); + be_print_err(gettext("set_canmount: failed to " + "set dataset property (%s): %s\n"), + ds_path, libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + return (err); + } + } + ZFS_CLOSE(zhp); + + while (datasets != NULL) { + be_make_root_ds(list->be_rpool, + datasets->be_dataset_name, ds_path, + sizeof (ds_path)); + + if ((zhp = zfs_open(g_zfs, ds_path, ZFS_TYPE_DATASET)) + == NULL) { + be_print_err(gettext("set_canmount: failed to " + "open dataset %s: %s\n"), ds_path, + libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + return (err); + } + if (zfs_prop_get_int(zhp, ZFS_PROP_MOUNTED)) { + /* + * it's already mounted so we can't change the + * canmount property anyway. + */ + err = BE_SUCCESS; + ZFS_CLOSE(zhp); + break; + } + err = zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_CANMOUNT), value); + if (err) { + ZFS_CLOSE(zhp); + be_print_err(gettext("set_canmount: " + "Failed to set property value %s " + "for dataset %s: %s\n"), value, ds_path, + libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + return (err); + } + ZFS_CLOSE(zhp); + datasets = datasets->be_next_dataset; + } + list = list->be_next_node; + } + return (err); +} + +/* + * Function: be_get_grub_vers + * Description: Gets the grub version number from /boot/grub/capability. If + * capability file doesn't exist NULL is returned. + * Parameters: + * bt - The transaction data for the BE we're getting the grub + * version for. + * cur_vers - used to return the current version of grub from + * the root pool. + * new_vers - used to return the grub version of the BE we're + * activating. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failed to find version + * Scope: + * Private + */ +static int +be_get_grub_vers(be_transaction_data_t *bt, char **cur_vers, char **new_vers) +{ + zfs_handle_t *zhp = NULL; + zfs_handle_t *pool_zhp = NULL; + int ret = BE_SUCCESS; + char cap_file[MAXPATHLEN]; + char *temp_mntpnt = NULL; + char *zpool_mntpt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + boolean_t be_mounted = B_FALSE; + boolean_t pool_mounted = B_FALSE; + + if (!be_has_grub()) { + be_print_err(gettext("be_get_grub_vers: Not supported on " + "this architecture\n")); + return (BE_ERR_NOTSUP); + } + + if (bt == NULL || bt->obe_name == NULL || bt->obe_zpool == NULL || + bt->obe_root_ds == NULL) { + be_print_err(gettext("be_get_grub_vers: Invalid BE\n")); + return (BE_ERR_INVAL); + } + + if ((pool_zhp = zfs_open(g_zfs, bt->obe_zpool, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_get_grub_vers: zfs_open failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if ((ret = be_mount_pool(pool_zhp, &ptmp_mntpnt, + &orig_mntpnt, &pool_mounted)) != BE_SUCCESS) { + be_print_err(gettext("be_get_grub_vers: pool dataset " + "(%s) could not be mounted\n"), bt->obe_zpool); + ZFS_CLOSE(pool_zhp); + return (ret); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(pool_zhp, &zpool_mntpt)) { + be_print_err(gettext("be_get_grub_vers: pool " + "dataset (%s) is not mounted. Can't set the " + "default BE in the grub menu.\n"), bt->obe_zpool); + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + /* + * get the version of the most recent grub update. + */ + (void) snprintf(cap_file, sizeof (cap_file), "%s%s", + zpool_mntpt, BE_CAP_FILE); + free(zpool_mntpt); + zpool_mntpt = NULL; + + if ((ret = get_ver_from_capfile(cap_file, cur_vers)) != BE_SUCCESS) + goto cleanup; + + if ((zhp = zfs_open(g_zfs, bt->obe_root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_get_grub_vers: failed to " + "open BE root dataset (%s): %s\n"), bt->obe_root_ds, + libzfs_error_description(g_zfs)); + free(cur_vers); + ret = zfs_err_to_be_err(g_zfs); + goto cleanup; + } + if (!zfs_is_mounted(zhp, &temp_mntpnt)) { + if ((ret = _be_mount(bt->obe_name, &temp_mntpnt, + BE_MOUNT_FLAG_NO_ZONES)) != BE_SUCCESS) { + be_print_err(gettext("be_get_grub_vers: failed to " + "mount BE (%s)\n"), bt->obe_name); + free(*cur_vers); + *cur_vers = NULL; + ZFS_CLOSE(zhp); + goto cleanup; + } + be_mounted = B_TRUE; + } + ZFS_CLOSE(zhp); + + /* + * Now get the grub version for the BE being activated. + */ + (void) snprintf(cap_file, sizeof (cap_file), "%s%s", temp_mntpnt, + BE_CAP_FILE); + ret = get_ver_from_capfile(cap_file, new_vers); + if (ret != BE_SUCCESS) { + free(*cur_vers); + *cur_vers = NULL; + } + if (be_mounted) + (void) _be_unmount(bt->obe_name, 0); + +cleanup: + if (pool_mounted) { + int iret = BE_SUCCESS; + iret = be_unmount_pool(pool_zhp, ptmp_mntpnt, orig_mntpnt); + if (ret == BE_SUCCESS) + ret = iret; + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(pool_zhp); + + free(temp_mntpnt); + return (ret); +} + +/* + * Function: get_ver_from_capfile + * Description: Parses the capability file passed in looking for the VERSION + * line. If found the version is returned in vers, if not then + * NULL is returned in vers. + * + * Parameters: + * file - the path to the capability file we want to parse. + * vers - the version string that will be passed back. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failed to find version + * Scope: + * Private + */ +static int +get_ver_from_capfile(char *file, char **vers) +{ + FILE *fp = NULL; + char line[BUFSIZ]; + char *last = NULL; + int err = BE_SUCCESS; + errno = 0; + + if (!be_has_grub()) { + be_print_err(gettext("get_ver_from_capfile: Not supported " + "on this architecture\n")); + return (BE_ERR_NOTSUP); + } + + /* + * Set version string to NULL; the only case this shouldn't be set + * to be NULL is when we've actually found a version in the capability + * file, which is set below. + */ + *vers = NULL; + + /* + * If the capability file doesn't exist, we're returning success + * because on older releases, the capability file did not exist + * so this is a valid scenario. + */ + if (access(file, F_OK) == 0) { + if ((fp = fopen(file, "r")) == NULL) { + err = errno; + be_print_err(gettext("get_ver_from_capfile: failed to " + "open file %s with error %s\n"), file, + strerror(err)); + err = errno_to_be_err(err); + return (err); + } + + while (fgets(line, BUFSIZ, fp)) { + char *tok = strtok_r(line, "=", &last); + + if (tok == NULL || tok[0] == '#') { + continue; + } else if (strcmp(tok, "VERSION") == 0) { + *vers = strdup(last); + break; + } + } + (void) fclose(fp); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_do_installgrub + * Description: This function runs installgrub using the grub loader files + * from the BE we're activating and installing them on the + * pool the BE lives in. + * + * Parameters: + * bt - The transaction data for the BE we're activating. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * + * Scope: + * Private + */ +static int +be_do_installgrub(be_transaction_data_t *bt) +{ + zpool_handle_t *zphp = NULL; + zfs_handle_t *zhp = NULL; + nvlist_t **child, *nv, *config; + uint_t c, children = 0; + char *tmp_mntpt = NULL; + char *pool_mntpnt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + FILE *cap_fp = NULL; + FILE *zpool_cap_fp = NULL; + char line[BUFSIZ]; + char cap_file[MAXPATHLEN]; + char zpool_cap_file[MAXPATHLEN]; + char stage1[MAXPATHLEN]; + char stage2[MAXPATHLEN]; + char installgrub_cmd[MAXPATHLEN]; + char *vname; + char be_run_cmd_errbuf[BUFSIZ]; + int ret = BE_SUCCESS; + int err = 0; + boolean_t be_mounted = B_FALSE; + boolean_t pool_mounted = B_FALSE; + + if (!be_has_grub()) { + be_print_err(gettext("be_do_installgrub: Not supported " + "on this architecture\n")); + return (BE_ERR_NOTSUP); + } + + if ((zhp = zfs_open(g_zfs, bt->obe_root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_do_installgrub: failed to " + "open BE root dataset (%s): %s\n"), bt->obe_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + return (ret); + } + if (!zfs_is_mounted(zhp, &tmp_mntpt)) { + if ((ret = _be_mount(bt->obe_name, &tmp_mntpt, + BE_MOUNT_FLAG_NO_ZONES)) != BE_SUCCESS) { + be_print_err(gettext("be_do_installgrub: failed to " + "mount BE (%s)\n"), bt->obe_name); + ZFS_CLOSE(zhp); + return (ret); + } + be_mounted = B_TRUE; + } + ZFS_CLOSE(zhp); + + (void) snprintf(stage1, sizeof (stage1), "%s%s", tmp_mntpt, BE_STAGE_1); + (void) snprintf(stage2, sizeof (stage2), "%s%s", tmp_mntpt, BE_STAGE_2); + + if ((zphp = zpool_open(g_zfs, bt->obe_zpool)) == NULL) { + be_print_err(gettext("be_do_installgrub: failed to open " + "pool (%s): %s\n"), bt->obe_zpool, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + if (be_mounted) + (void) _be_unmount(bt->obe_name, 0); + free(tmp_mntpt); + return (ret); + } + + if ((config = zpool_get_config(zphp, NULL)) == NULL) { + be_print_err(gettext("be_do_installgrub: failed to get zpool " + "configuration information. %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* + * Get the vdev tree + */ + if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nv) != 0) { + be_print_err(gettext("be_do_installgrub: failed to get vdev " + "tree: %s\n"), libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, &child, + &children) != 0) { + be_print_err(gettext("be_do_installgrub: failed to traverse " + "the vdev tree: %s\n"), libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + for (c = 0; c < children; c++) { + uint_t i, nchildren = 0; + nvlist_t **nvchild; + vname = zpool_vdev_name(g_zfs, zphp, child[c], B_FALSE); + if (vname == NULL) { + be_print_err(gettext( + "be_do_installgrub: " + "failed to get device name: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + if (strcmp(vname, "mirror") == 0 || vname[0] != 'c') { + + if (nvlist_lookup_nvlist_array(child[c], + ZPOOL_CONFIG_CHILDREN, &nvchild, &nchildren) != 0) { + be_print_err(gettext("be_do_installgrub: " + "failed to traverse the vdev tree: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + for (i = 0; i < nchildren; i++) { + vname = zpool_vdev_name(g_zfs, zphp, + nvchild[i], B_FALSE); + if (vname == NULL) { + be_print_err(gettext( + "be_do_installgrub: " + "failed to get device name: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + (void) snprintf(installgrub_cmd, + sizeof (installgrub_cmd), + "%s %s %s /dev/rdsk/%s", + BE_INSTALL_GRUB, stage1, stage2, vname); + if (be_run_cmd(installgrub_cmd, + be_run_cmd_errbuf, BUFSIZ, NULL, 0) != + BE_SUCCESS) { + be_print_err(gettext( + "be_do_installgrub: installgrub " + "failed for device %s.\n"), vname); + /* Assume localized cmd err output. */ + be_print_err(gettext( + " Command: \"%s\"\n"), + installgrub_cmd); + be_print_err("%s", be_run_cmd_errbuf); + free(vname); + ret = BE_ERR_BOOTFILE_INST; + goto done; + } + free(vname); + } + } else { + (void) snprintf(installgrub_cmd, + sizeof (installgrub_cmd), "%s %s %s /dev/rdsk/%s", + BE_INSTALL_GRUB, stage1, stage2, vname); + if (be_run_cmd(installgrub_cmd, be_run_cmd_errbuf, + BUFSIZ, NULL, 0) != BE_SUCCESS) { + be_print_err(gettext( + "be_do_installgrub: installgrub " + "failed for device %s.\n"), vname); + /* Assume localized cmd err output. */ + be_print_err(gettext(" Command: \"%s\"\n"), + installgrub_cmd); + be_print_err("%s", be_run_cmd_errbuf); + free(vname); + ret = BE_ERR_BOOTFILE_INST; + goto done; + } + free(vname); + } + } + + /* + * Copy the grub capability file from the BE we're activating into + * the root pool. + */ + (void) snprintf(cap_file, sizeof (cap_file), "%s%s", tmp_mntpt, + BE_CAP_FILE); + + if ((zhp = zfs_open(g_zfs, bt->obe_zpool, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_do_installgrub: zfs_open " + "failed: %s\n"), libzfs_error_description(g_zfs)); + zpool_close(zphp); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if ((ret = be_mount_pool(zhp, &ptmp_mntpnt, + &orig_mntpnt, &pool_mounted)) != BE_SUCCESS) { + be_print_err(gettext("be_do_installgrub: pool dataset " + "(%s) could not be mounted\n"), bt->obe_zpool); + ZFS_CLOSE(zhp); + zpool_close(zphp); + return (ret); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(zhp, &pool_mntpnt)) { + be_print_err(gettext("be_do_installgrub: pool " + "dataset (%s) is not mounted. Can't check the grub " + "version from the grub capability file.\n"), bt->obe_zpool); + ret = BE_ERR_NO_MENU; + goto done; + } + + (void) snprintf(zpool_cap_file, sizeof (zpool_cap_file), "%s%s", + pool_mntpnt, BE_CAP_FILE); + + free(pool_mntpnt); + pool_mntpnt = NULL; + + if ((cap_fp = fopen(cap_file, "r")) == NULL) { + err = errno; + be_print_err(gettext("be_do_installgrub: failed to open grub " + "capability file\n")); + ret = errno_to_be_err(err); + goto done; + } + if ((zpool_cap_fp = fopen(zpool_cap_file, "w")) == NULL) { + err = errno; + be_print_err(gettext("be_do_installgrub: failed to open new " + "grub capability file\n")); + ret = errno_to_be_err(err); + (void) fclose(cap_fp); + goto done; + } + + while (fgets(line, BUFSIZ, cap_fp)) { + (void) fputs(line, zpool_cap_fp); + } + + (void) fclose(zpool_cap_fp); + (void) fclose(cap_fp); + +done: + if (pool_mounted) { + int iret = 0; + iret = be_unmount_pool(zhp, ptmp_mntpnt, orig_mntpnt); + if (ret == BE_SUCCESS) + ret = iret; + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(zhp); + if (be_mounted) + (void) _be_unmount(bt->obe_name, 0); + zpool_close(zphp); + free(tmp_mntpt); + return (ret); +} + +/* + * Function: be_promote_zone_ds + * Description: This function finds the zones for the BE being activated + * and the active zonepath dataset for each zone. Then each + * active zonepath dataset is promoted. + * + * Parameters: + * be_name - the name of the global zone BE that we need to + * find the zones for. + * be_root_ds - the root dataset for be_name. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * + * Scope: + * Private + */ +static int +be_promote_zone_ds(char *be_name, char *be_root_ds) +{ + char *zone_ds = NULL; + char *temp_mntpt = NULL; + char origin[MAXPATHLEN]; + char zoneroot_ds[MAXPATHLEN]; + zfs_handle_t *zhp = NULL; + zfs_handle_t *z_zhp = NULL; + zoneList_t zone_list = NULL; + zoneBrandList_t *brands = NULL; + boolean_t be_mounted = B_FALSE; + int zone_index = 0; + int err = BE_SUCCESS; + + /* + * Get the supported zone brands so we can pass that + * to z_get_nonglobal_zone_list_by_brand. Currently + * only the ipkg and labeled brand zones are supported + * + */ + if ((brands = be_get_supported_brandlist()) == NULL) { + be_print_err(gettext("be_promote_zone_ds: no supported " + "brands\n")); + return (BE_SUCCESS); + } + + if ((zhp = zfs_open(g_zfs, be_root_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_promote_zone_ds: Failed to open " + "dataset (%s): %s\n"), be_root_ds, + libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + z_free_brand_list(brands); + return (err); + } + + if (!zfs_is_mounted(zhp, &temp_mntpt)) { + if ((err = _be_mount(be_name, &temp_mntpt, + BE_MOUNT_FLAG_NO_ZONES)) != BE_SUCCESS) { + be_print_err(gettext("be_promote_zone_ds: failed to " + "mount the BE for zones procesing.\n")); + ZFS_CLOSE(zhp); + z_free_brand_list(brands); + return (err); + } + be_mounted = B_TRUE; + } + + /* + * Set the zone root to the temp mount point for the BE we just mounted. + */ + z_set_zone_root(temp_mntpt); + + /* + * Get all the zones based on the brands we're looking for. If no zones + * are found that we're interested in unmount the BE and move on. + */ + if ((zone_list = z_get_nonglobal_zone_list_by_brand(brands)) == NULL) { + if (be_mounted) + (void) _be_unmount(be_name, 0); + ZFS_CLOSE(zhp); + z_free_brand_list(brands); + free(temp_mntpt); + return (BE_SUCCESS); + } + for (zone_index = 0; z_zlist_get_zonename(zone_list, zone_index) + != NULL; zone_index++) { + char *zone_path = NULL; + + /* Skip zones that aren't at least installed */ + if (z_zlist_get_current_state(zone_list, zone_index) < + ZONE_STATE_INSTALLED) + continue; + + if (((zone_path = + z_zlist_get_zonepath(zone_list, zone_index)) == NULL) || + ((zone_ds = be_get_ds_from_dir(zone_path)) == NULL) || + !be_zone_supported(zone_ds)) + continue; + + if (be_find_active_zone_root(zhp, zone_ds, + zoneroot_ds, sizeof (zoneroot_ds)) != 0) { + be_print_err(gettext("be_promote_zone_ds: " + "Zone does not have an active root " + "dataset, skipping this zone.\n")); + continue; + } + + if ((z_zhp = zfs_open(g_zfs, zoneroot_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_promote_zone_ds: " + "Failed to open dataset " + "(%s): %s\n"), zoneroot_ds, + libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (zfs_prop_get(z_zhp, ZFS_PROP_ORIGIN, origin, + sizeof (origin), NULL, NULL, 0, B_FALSE) != 0) { + ZFS_CLOSE(z_zhp); + continue; + } + + /* + * We don't need to close the zfs handle at this + * point because the callback funtion + * be_promote_ds_callback() will close it for us. + */ + if (be_promote_ds_callback(z_zhp, NULL) != 0) { + be_print_err(gettext("be_promote_zone_ds: " + "failed to activate the " + "datasets for %s: %s\n"), + zoneroot_ds, + libzfs_error_description(g_zfs)); + err = BE_ERR_PROMOTE; + goto done; + } + } +done: + if (be_mounted) + (void) _be_unmount(be_name, 0); + ZFS_CLOSE(zhp); + free(temp_mntpt); + z_free_brand_list(brands); + z_free_zone_list(zone_list); + return (err); +} + +/* + * Function: be_promote_ds_callback + * Description: This function is used to promote the datasets for the BE + * being activated as well as the datasets for the zones BE + * being activated. + * + * Parameters: + * zhp - the zfs handle for zone BE being activated. + * data - not used. + * Return: + * 0 - Success + * be_errno_t - Failure + * + * Scope: + * Private + */ +static int +/* LINTED */ +be_promote_ds_callback(zfs_handle_t *zhp, void *data) +{ + char origin[MAXPATHLEN]; + char *sub_dataset = NULL; + int ret = 0; + + if (zhp != NULL) { + sub_dataset = strdup(zfs_get_name(zhp)); + if (sub_dataset == NULL) { + ret = BE_ERR_NOMEM; + goto done; + } + } else { + be_print_err(gettext("be_promote_ds_callback: " + "Invalid zfs handle passed into function\n")); + ret = BE_ERR_INVAL; + goto done; + } + + /* + * This loop makes sure that we promote the dataset to the + * top of the tree so that it is no longer a decendent of any + * dataset. The ZFS close and then open is used to make sure that + * the promotion is updated before we move on. + */ + while (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin, + sizeof (origin), NULL, NULL, 0, B_FALSE) == 0) { + if (zfs_promote(zhp) != 0) { + if (libzfs_errno(g_zfs) != EZFS_EXISTS) { + be_print_err(gettext("be_promote_ds_callback: " + "promote of %s failed: %s\n"), + zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } else { + /* + * If the call to zfs_promote returns the + * error EZFS_EXISTS we've hit a snapshot name + * collision. This means we're probably + * attemping to promote a zone dataset above a + * parent dataset that belongs to another zone + * which this zone was cloned from. + * + * TODO: If this is a zone dataset at some + * point we should skip this if the zone + * paths for the dataset and the snapshot + * don't match. + */ + be_print_err(gettext("be_promote_ds_callback: " + "promote of %s failed due to snapshot " + "name collision: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + } + ZFS_CLOSE(zhp); + if ((zhp = zfs_open(g_zfs, sub_dataset, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_promote_ds_callback: " + "Failed to open dataset (%s): %s\n"), sub_dataset, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + } + + /* Iterate down this dataset's children and promote them */ + ret = zfs_iter_filesystems(zhp, be_promote_ds_callback, NULL); + +done: + free(sub_dataset); + ZFS_CLOSE(zhp); + return (ret); +} diff --git a/usr/src/lib/libbe/common/be_create.c b/usr/src/lib/libbe/common/be_create.c new file mode 100644 index 0000000000..07548efa6c --- /dev/null +++ b/usr/src/lib/libbe/common/be_create.c @@ -0,0 +1,2995 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * System includes + */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <libgen.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mnttab.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <libbe.h> +#include <libbe_priv.h> + +/* Library wide variables */ +libzfs_handle_t *g_zfs = NULL; + +/* Private function prototypes */ +static int _be_destroy(const char *, be_destroy_data_t *); +static int be_destroy_zones(char *, char *, be_destroy_data_t *); +static int be_destroy_zone_roots(char *, be_destroy_data_t *); +static int be_destroy_zone_roots_callback(zfs_handle_t *, void *); +static int be_copy_zones(char *, char *, char *); +static int be_clone_fs_callback(zfs_handle_t *, void *); +static int be_destroy_callback(zfs_handle_t *, void *); +static int be_send_fs_callback(zfs_handle_t *, void *); +static int be_demote_callback(zfs_handle_t *, void *); +static int be_demote_find_clone_callback(zfs_handle_t *, void *); +static int be_demote_get_one_clone(zfs_handle_t *, void *); +static int be_get_snap(char *, char **); +static int be_prep_clone_send_fs(zfs_handle_t *, be_transaction_data_t *, + char *, int); +static boolean_t be_create_container_ds(char *); +static char *be_get_zone_be_name(char *root_ds, char *container_ds); +static int be_zone_root_exists_callback(zfs_handle_t *, void *); + +/* ******************************************************************** */ +/* Public Functions */ +/* ******************************************************************** */ + +/* + * Function: be_init + * Description: Creates the initial datasets for a BE and leaves them + * unpopulated. The resultant BE can be mounted but can't + * yet be activated or booted. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attributes are used by this function: + * + * BE_ATTR_NEW_BE_NAME *required + * BE_ATTR_NEW_BE_POOL *required + * BE_ATTR_ZFS_PROPERTIES *optional + * BE_ATTR_FS_NAMES *optional + * BE_ATTR_FS_NUM *optional + * BE_ATTR_SHARED_FS_NAMES *optional + * BE_ATTR_SHARED_FS_NUM *optional + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_init(nvlist_t *be_attrs) +{ + be_transaction_data_t bt = { 0 }; + zpool_handle_t *zlp; + nvlist_t *zfs_props = NULL; + char nbe_root_ds[MAXPATHLEN]; + char child_fs[MAXPATHLEN]; + char **fs_names = NULL; + char **shared_fs_names = NULL; + uint16_t fs_num = 0; + uint16_t shared_fs_num = 0; + int nelem; + int i; + int zret = 0, ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get new BE name */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_NEW_BE_NAME, &bt.nbe_name) + != 0) { + be_print_err(gettext("be_init: failed to lookup " + "BE_ATTR_NEW_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* Validate new BE name */ + if (!be_valid_be_name(bt.nbe_name)) { + be_print_err(gettext("be_init: invalid BE name %s\n"), + bt.nbe_name); + return (BE_ERR_INVAL); + } + + /* Get zpool name */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_NEW_BE_POOL, &bt.nbe_zpool) + != 0) { + be_print_err(gettext("be_init: failed to lookup " + "BE_ATTR_NEW_BE_POOL attribute\n")); + return (BE_ERR_INVAL); + } + + /* Get file system attributes */ + nelem = 0; + if (nvlist_lookup_pairs(be_attrs, 0, + BE_ATTR_FS_NUM, DATA_TYPE_UINT16, &fs_num, + BE_ATTR_FS_NAMES, DATA_TYPE_STRING_ARRAY, &fs_names, &nelem, + NULL) != 0) { + be_print_err(gettext("be_init: failed to lookup fs " + "attributes\n")); + return (BE_ERR_INVAL); + } + if (nelem != fs_num) { + be_print_err(gettext("be_init: size of FS_NAMES array (%d) " + "does not match FS_NUM (%d)\n"), nelem, fs_num); + return (BE_ERR_INVAL); + } + + /* Get shared file system attributes */ + nelem = 0; + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_SHARED_FS_NUM, DATA_TYPE_UINT16, &shared_fs_num, + BE_ATTR_SHARED_FS_NAMES, DATA_TYPE_STRING_ARRAY, &shared_fs_names, + &nelem, NULL) != 0) { + be_print_err(gettext("be_init: failed to lookup " + "shared fs attributes\n")); + return (BE_ERR_INVAL); + } + if (nelem != shared_fs_num) { + be_print_err(gettext("be_init: size of SHARED_FS_NAMES " + "array does not match SHARED_FS_NUM\n")); + return (BE_ERR_INVAL); + } + + /* Verify that nbe_zpool exists */ + if ((zlp = zpool_open(g_zfs, bt.nbe_zpool)) == NULL) { + be_print_err(gettext("be_init: failed to " + "find existing zpool (%s): %s\n"), bt.nbe_zpool, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + zpool_close(zlp); + + /* + * Verify BE container dataset in nbe_zpool exists. + * If not, create it. + */ + if (!be_create_container_ds(bt.nbe_zpool)) + return (BE_ERR_CREATDS); + + /* + * Verify that nbe_name doesn't already exist in some pool. + */ + if ((zret = zpool_iter(g_zfs, be_exists_callback, bt.nbe_name)) > 0) { + be_print_err(gettext("be_init: BE (%s) already exists\n"), + bt.nbe_name); + return (BE_ERR_BE_EXISTS); + } else if (zret < 0) { + be_print_err(gettext("be_init: zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Generate string for BE's root dataset */ + be_make_root_ds(bt.nbe_zpool, bt.nbe_name, nbe_root_ds, + sizeof (nbe_root_ds)); + + /* + * Create property list for new BE root dataset. If some + * zfs properties were already provided by the caller, dup + * that list. Otherwise initialize a new property list. + */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_ZFS_PROPERTIES, DATA_TYPE_NVLIST, &zfs_props, NULL) + != 0) { + be_print_err(gettext("be_init: failed to lookup " + "BE_ATTR_ZFS_PROPERTIES attribute\n")); + return (BE_ERR_INVAL); + } + if (zfs_props != NULL) { + /* Make sure its a unique nvlist */ + if (!(zfs_props->nvl_nvflag & NV_UNIQUE_NAME) && + !(zfs_props->nvl_nvflag & NV_UNIQUE_NAME_TYPE)) { + be_print_err(gettext("be_init: ZFS property list " + "not unique\n")); + return (BE_ERR_INVAL); + } + + /* Dup the list */ + if (nvlist_dup(zfs_props, &bt.nbe_zfs_props, 0) != 0) { + be_print_err(gettext("be_init: failed to dup ZFS " + "property list\n")); + return (BE_ERR_NOMEM); + } + } else { + /* Initialize new nvlist */ + if (nvlist_alloc(&bt.nbe_zfs_props, NV_UNIQUE_NAME, 0) != 0) { + be_print_err(gettext("be_init: internal " + "error: out of memory\n")); + return (BE_ERR_NOMEM); + } + } + + /* Set the mountpoint property for the root dataset */ + if (nvlist_add_string(bt.nbe_zfs_props, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), "/") != 0) { + be_print_err(gettext("be_init: internal error " + "out of memory\n")); + ret = BE_ERR_NOMEM; + goto done; + } + + /* Set the 'canmount' property */ + if (nvlist_add_string(bt.nbe_zfs_props, + zfs_prop_to_name(ZFS_PROP_CANMOUNT), "noauto") != 0) { + be_print_err(gettext("be_init: internal error " + "out of memory\n")); + ret = BE_ERR_NOMEM; + goto done; + } + + /* Create BE root dataset for the new BE */ + if (zfs_create(g_zfs, nbe_root_ds, ZFS_TYPE_FILESYSTEM, + bt.nbe_zfs_props) != 0) { + be_print_err(gettext("be_init: failed to " + "create BE root dataset (%s): %s\n"), nbe_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Set UUID for new BE */ + if ((ret = be_set_uuid(nbe_root_ds)) != BE_SUCCESS) { + be_print_err(gettext("be_init: failed to " + "set uuid for new BE\n")); + } + + /* + * Clear the mountpoint property so that the non-shared + * file systems created below inherit their mountpoints. + */ + (void) nvlist_remove(bt.nbe_zfs_props, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), DATA_TYPE_STRING); + + /* Create the new BE's non-shared file systems */ + for (i = 0; i < fs_num && fs_names[i]; i++) { + /* + * If fs == "/", skip it; + * we already created the root dataset + */ + if (strcmp(fs_names[i], "/") == 0) + continue; + + /* Generate string for file system */ + (void) snprintf(child_fs, sizeof (child_fs), "%s%s", + nbe_root_ds, fs_names[i]); + + /* Create file system */ + if (zfs_create(g_zfs, child_fs, ZFS_TYPE_FILESYSTEM, + bt.nbe_zfs_props) != 0) { + be_print_err(gettext("be_init: failed to create " + "BE's child dataset (%s): %s\n"), child_fs, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + } + + /* Create the new BE's shared file systems */ + if (shared_fs_num > 0) { + nvlist_t *props = NULL; + + if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) { + be_print_err(gettext("be_init: nvlist_alloc failed\n")); + ret = BE_ERR_NOMEM; + goto done; + } + + for (i = 0; i < shared_fs_num; i++) { + /* Generate string for shared file system */ + (void) snprintf(child_fs, sizeof (child_fs), "%s%s", + bt.nbe_zpool, shared_fs_names[i]); + + if (nvlist_add_string(props, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + shared_fs_names[i]) != 0) { + be_print_err(gettext("be_init: " + "internal error: out of memory\n")); + nvlist_free(props); + ret = BE_ERR_NOMEM; + goto done; + } + + /* Create file system if it doesn't already exist */ + if (zfs_dataset_exists(g_zfs, child_fs, + ZFS_TYPE_FILESYSTEM)) { + continue; + } + if (zfs_create(g_zfs, child_fs, ZFS_TYPE_FILESYSTEM, + props) != 0) { + be_print_err(gettext("be_init: failed to " + "create BE's shared dataset (%s): %s\n"), + child_fs, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + nvlist_free(props); + goto done; + } + } + + nvlist_free(props); + } + +done: + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + + be_zfs_fini(); + + return (ret); +} + +/* + * Function: be_destroy + * Description: Destroy a BE and all of its children datasets, snapshots and + * zones that belong to the parent BE. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attributes are used by this function: + * + * BE_ATTR_ORIG_BE_NAME *required + * BE_ATTR_DESTROY_FLAGS *optional + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_destroy(nvlist_t *be_attrs) +{ + zfs_handle_t *zhp = NULL; + be_transaction_data_t bt = { 0 }; + be_transaction_data_t cur_bt = { 0 }; + be_destroy_data_t dd = { 0 }; + int ret = BE_SUCCESS; + uint16_t flags = 0; + int zret; + char obe_root_ds[MAXPATHLEN]; + char *mp = NULL; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get name of BE to delete */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_ORIG_BE_NAME, &bt.obe_name) + != 0) { + be_print_err(gettext("be_destroy: failed to lookup " + "BE_ATTR_ORIG_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* + * Validate BE name. If valid, then check that the original BE is not + * the active BE. If it is the 'active' BE then return an error code + * since we can't destroy the active BE. + */ + if (!be_valid_be_name(bt.obe_name)) { + be_print_err(gettext("be_destroy: invalid BE name %s\n"), + bt.obe_name); + return (BE_ERR_INVAL); + } else if (bt.obe_name != NULL) { + if ((ret = be_find_current_be(&cur_bt)) != BE_SUCCESS) { + return (ret); + } + if (strcmp(cur_bt.obe_name, bt.obe_name) == 0) { + return (BE_ERR_DESTROY_CURR_BE); + } + } + + /* Get destroy flags if provided */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_DESTROY_FLAGS, DATA_TYPE_UINT16, &flags, NULL) + != 0) { + be_print_err(gettext("be_destroy: failed to lookup " + "BE_ATTR_DESTROY_FLAGS attribute\n")); + return (BE_ERR_INVAL); + } + + dd.destroy_snaps = flags & BE_DESTROY_FLAG_SNAPSHOTS; + dd.force_unmount = flags & BE_DESTROY_FLAG_FORCE_UNMOUNT; + + /* Find which zpool obe_name lives in */ + if ((zret = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_destroy: failed to find zpool " + "for BE (%s)\n"), bt.obe_name); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_destroy: zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Generate string for obe_name's root dataset */ + be_make_root_ds(bt.obe_zpool, bt.obe_name, obe_root_ds, + sizeof (obe_root_ds)); + bt.obe_root_ds = obe_root_ds; + + /* + * Detect if the BE to destroy has the 'active on boot' property set. + * If so, set the 'active on boot' property on the the 'active' BE. + */ + if (be_is_active_on_boot(bt.obe_name)) { + if ((ret = be_activate_current_be()) != BE_SUCCESS) { + be_print_err(gettext("be_destroy: failed to " + "make the current BE 'active on boot'\n")); + return (ret); + } + } + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_destroy: failed to " + "open BE root dataset (%s): %s\n"), bt.obe_root_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Get the UUID of the global BE */ + if (be_get_uuid(zfs_get_name(zhp), &dd.gz_be_uuid) != BE_SUCCESS) { + be_print_err(gettext("be_destroy: BE has no UUID (%s)\n"), + zfs_get_name(zhp)); + } + + /* + * If the global BE is mounted, make sure we've been given the + * flag to forcibly unmount it. + */ + if (zfs_is_mounted(zhp, &mp)) { + if (!(dd.force_unmount)) { + be_print_err(gettext("be_destroy: " + "%s is currently mounted at %s, cannot destroy\n"), + bt.obe_name, mp != NULL ? mp : "<unknown>"); + + free(mp); + ZFS_CLOSE(zhp); + return (BE_ERR_MOUNTED); + } + free(mp); + } + + /* + * Destroy the non-global zone BE's if we are in the global zone + * and there is a UUID associated with the global zone BE + */ + if (getzoneid() == GLOBAL_ZONEID && !uuid_is_null(dd.gz_be_uuid)) { + if ((ret = be_destroy_zones(bt.obe_name, bt.obe_root_ds, &dd)) + != BE_SUCCESS) { + be_print_err(gettext("be_destroy: failed to " + "destroy one or more zones for BE %s\n"), + bt.obe_name); + goto done; + } + } + + /* Unmount the BE if it was mounted */ + if (zfs_is_mounted(zhp, NULL)) { + if ((ret = _be_unmount(bt.obe_name, BE_UNMOUNT_FLAG_FORCE)) + != BE_SUCCESS) { + be_print_err(gettext("be_destroy: " + "failed to unmount %s\n"), bt.obe_name); + ZFS_CLOSE(zhp); + return (ret); + } + } + ZFS_CLOSE(zhp); + + /* Destroy this BE */ + if ((ret = _be_destroy((const char *)bt.obe_root_ds, &dd)) + != BE_SUCCESS) { + goto done; + } + + /* Remove BE's entry from the boot menu */ + if (getzoneid() == GLOBAL_ZONEID) { + if ((ret = be_remove_menu(bt.obe_name, bt.obe_zpool, NULL)) + != BE_SUCCESS) { + be_print_err(gettext("be_destroy: failed to " + "remove BE %s from the boot menu\n"), + bt.obe_root_ds); + goto done; + } + } + +done: + be_zfs_fini(); + + return (ret); +} + +/* + * Function: be_copy + * Description: This function makes a copy of an existing BE. If the original + * BE and the new BE are in the same pool, it uses zfs cloning to + * create the new BE, otherwise it does a physical copy. + * If the original BE name isn't provided, it uses the currently + * booted BE. If the new BE name isn't provided, it creates an + * auto named BE and returns that name to the caller. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attributes are used by this function: + * + * BE_ATTR_ORIG_BE_NAME *optional + * BE_ATTR_SNAP_NAME *optional + * BE_ATTR_NEW_BE_NAME *optional + * BE_ATTR_NEW_BE_POOL *optional + * BE_ATTR_NEW_BE_DESC *optional + * BE_ATTR_ZFS_PROPERTIES *optional + * BE_ATTR_POLICY *optional + * + * If the BE_ATTR_NEW_BE_NAME was not passed in, upon + * successful BE creation, the following attribute values + * will be returned to the caller by setting them in the + * be_attrs parameter passed in: + * + * BE_ATTR_SNAP_NAME + * BE_ATTR_NEW_BE_NAME + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_copy(nvlist_t *be_attrs) +{ + be_transaction_data_t bt = { 0 }; + be_fs_list_data_t fld = { 0 }; + zfs_handle_t *zhp = NULL; + nvlist_t *zfs_props = NULL; + uuid_t uu = { 0 }; + char obe_root_ds[MAXPATHLEN]; + char nbe_root_ds[MAXPATHLEN]; + char ss[MAXPATHLEN]; + char *new_mp = NULL; + boolean_t autoname = B_FALSE; + boolean_t be_created = B_FALSE; + int i; + int zret; + int ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get original BE name */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_ORIG_BE_NAME, DATA_TYPE_STRING, &bt.obe_name, NULL) != 0) { + be_print_err(gettext("be_copy: failed to lookup " + "BE_ATTR_ORIG_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* If original BE name not provided, use current BE */ + if (bt.obe_name == NULL) { + if ((ret = be_find_current_be(&bt)) != BE_SUCCESS) { + return (ret); + } + } else { + /* Validate original BE name */ + if (!be_valid_be_name(bt.obe_name)) { + be_print_err(gettext("be_copy: " + "invalid BE name %s\n"), bt.obe_name); + return (BE_ERR_INVAL); + } + } + + /* Find which zpool obe_name lives in */ + if ((zret = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_copy: failed to " + "find zpool for BE (%s)\n"), bt.obe_name); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_copy: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Get snapshot name of original BE if one was provided */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_SNAP_NAME, DATA_TYPE_STRING, &bt.obe_snap_name, NULL) + != 0) { + be_print_err(gettext("be_copy: failed to lookup " + "BE_ATTR_SNAP_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* Get new BE name */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_NEW_BE_NAME, DATA_TYPE_STRING, &bt.nbe_name, NULL) + != 0) { + be_print_err(gettext("be_copy: failed to lookup " + "BE_ATTR_NEW_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* Get zpool name to create new BE in */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_NEW_BE_POOL, DATA_TYPE_STRING, &bt.nbe_zpool, NULL) != 0) { + be_print_err(gettext("be_copy: failed to lookup " + "BE_ATTR_NEW_BE_POOL attribute\n")); + return (BE_ERR_INVAL); + } + + /* Get new BE's description if one was provided */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_NEW_BE_DESC, DATA_TYPE_STRING, &bt.nbe_desc, NULL) != 0) { + be_print_err(gettext("be_copy: failed to lookup " + "BE_ATTR_NEW_BE_DESC attribute\n")); + return (BE_ERR_INVAL); + } + + /* Get BE policy to create this snapshot under */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_POLICY, DATA_TYPE_STRING, &bt.policy, NULL) != 0) { + be_print_err(gettext("be_copy: failed to lookup " + "BE_ATTR_POLICY attribute\n")); + return (BE_ERR_INVAL); + } + + /* + * Create property list for new BE root dataset. If some + * zfs properties were already provided by the caller, dup + * that list. Otherwise initialize a new property list. + */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_ZFS_PROPERTIES, DATA_TYPE_NVLIST, &zfs_props, NULL) + != 0) { + be_print_err(gettext("be_copy: failed to lookup " + "BE_ATTR_ZFS_PROPERTIES attribute\n")); + return (BE_ERR_INVAL); + } + if (zfs_props != NULL) { + /* Make sure its a unique nvlist */ + if (!(zfs_props->nvl_nvflag & NV_UNIQUE_NAME) && + !(zfs_props->nvl_nvflag & NV_UNIQUE_NAME_TYPE)) { + be_print_err(gettext("be_copy: ZFS property list " + "not unique\n")); + return (BE_ERR_INVAL); + } + + /* Dup the list */ + if (nvlist_dup(zfs_props, &bt.nbe_zfs_props, 0) != 0) { + be_print_err(gettext("be_copy: " + "failed to dup ZFS property list\n")); + return (BE_ERR_NOMEM); + } + } else { + /* Initialize new nvlist */ + if (nvlist_alloc(&bt.nbe_zfs_props, NV_UNIQUE_NAME, 0) != 0) { + be_print_err(gettext("be_copy: internal " + "error: out of memory\n")); + return (BE_ERR_NOMEM); + } + } + + /* + * If new BE name provided, validate the BE name and then verify + * that new BE name doesn't already exist in some pool. + */ + if (bt.nbe_name) { + /* Validate original BE name */ + if (!be_valid_be_name(bt.nbe_name)) { + be_print_err(gettext("be_copy: " + "invalid BE name %s\n"), bt.nbe_name); + ret = BE_ERR_INVAL; + goto done; + } + + /* Verify it doesn't already exist */ + if ((zret = zpool_iter(g_zfs, be_exists_callback, bt.nbe_name)) + > 0) { + be_print_err(gettext("be_copy: BE (%s) already " + "exists\n"), bt.nbe_name); + ret = BE_ERR_BE_EXISTS; + goto done; + } else if (zret < 0) { + be_print_err(gettext("be_copy: zpool_iter failed: " + "%s\n"), libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + } else { + /* + * If an auto named BE is desired, it must be in the same + * pool is the original BE. + */ + if (bt.nbe_zpool != NULL) { + be_print_err(gettext("be_copy: cannot specify pool " + "name when creating an auto named BE\n")); + ret = BE_ERR_INVAL; + goto done; + } + + /* + * Generate auto named BE + */ + if ((bt.nbe_name = be_auto_be_name(bt.obe_name)) + == NULL) { + be_print_err(gettext("be_copy: " + "failed to generate auto BE name\n")); + ret = BE_ERR_AUTONAME; + goto done; + } + + autoname = B_TRUE; + } + + /* + * If zpool name to create new BE in is not provided, + * create new BE in original BE's pool. + */ + if (bt.nbe_zpool == NULL) { + bt.nbe_zpool = bt.obe_zpool; + } + + /* Get root dataset names for obe_name and nbe_name */ + be_make_root_ds(bt.obe_zpool, bt.obe_name, obe_root_ds, + sizeof (obe_root_ds)); + be_make_root_ds(bt.nbe_zpool, bt.nbe_name, nbe_root_ds, + sizeof (nbe_root_ds)); + + bt.obe_root_ds = obe_root_ds; + bt.nbe_root_ds = nbe_root_ds; + + /* + * If an existing snapshot name has been provided to create from, + * verify that it exists for the original BE's root dataset. + */ + if (bt.obe_snap_name != NULL) { + + /* Generate dataset name for snapshot to use. */ + (void) snprintf(ss, sizeof (ss), "%s@%s", bt.obe_root_ds, + bt.obe_snap_name); + + /* Verify snapshot exists */ + if (!zfs_dataset_exists(g_zfs, ss, ZFS_TYPE_SNAPSHOT)) { + be_print_err(gettext("be_copy: " + "snapshot does not exist (%s): %s\n"), ss, + libzfs_error_description(g_zfs)); + ret = BE_ERR_SS_NOENT; + goto done; + } + } else { + /* + * Else snapshot name was not provided, generate an + * auto named snapshot to use as its origin. + */ + if ((ret = _be_create_snapshot(bt.obe_name, + &bt.obe_snap_name, bt.policy)) != BE_SUCCESS) { + be_print_err(gettext("be_copy: " + "failed to create auto named snapshot\n")); + goto done; + } + + if (nvlist_add_string(be_attrs, BE_ATTR_SNAP_NAME, + bt.obe_snap_name) != 0) { + be_print_err(gettext("be_copy: " + "failed to add snap name to be_attrs\n")); + ret = BE_ERR_NOMEM; + goto done; + } + } + + /* Get handle to original BE's root dataset. */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_copy: failed to " + "open BE root dataset (%s): %s\n"), bt.obe_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* If original BE is currently mounted, record its altroot. */ + if (zfs_is_mounted(zhp, &bt.obe_altroot) && bt.obe_altroot == NULL) { + be_print_err(gettext("be_copy: failed to " + "get altroot of mounted BE %s: %s\n"), + bt.obe_name, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (strcmp(bt.obe_zpool, bt.nbe_zpool) == 0) { + + /* Do clone */ + + /* + * Iterate through original BE's datasets and clone + * them to create new BE. This call will end up closing + * the zfs handle passed in whether it succeeds for fails. + */ + if ((ret = be_clone_fs_callback(zhp, &bt)) != 0) { + zhp = NULL; + /* Creating clone BE failed */ + if (!autoname || ret != BE_ERR_BE_EXISTS) { + be_print_err(gettext("be_copy: " + "failed to clone new BE (%s) from " + "orig BE (%s)\n"), + bt.nbe_name, bt.obe_name); + ret = BE_ERR_CLONE; + goto done; + } + + /* + * We failed to create the new BE because a BE with + * the auto-name we generated above has since come + * into existence. Regenerate a new auto-name + * and retry. + */ + for (i = 1; i < BE_AUTO_NAME_MAX_TRY; i++) { + + /* Sleep 1 before retrying */ + (void) sleep(1); + + /* Generate new auto BE name */ + free(bt.nbe_name); + if ((bt.nbe_name = be_auto_be_name(bt.obe_name)) + == NULL) { + be_print_err(gettext("be_copy: " + "failed to generate auto " + "BE name\n")); + ret = BE_ERR_AUTONAME; + goto done; + } + + /* + * Regenerate string for new BE's + * root dataset name + */ + be_make_root_ds(bt.nbe_zpool, bt.nbe_name, + nbe_root_ds, sizeof (nbe_root_ds)); + bt.nbe_root_ds = nbe_root_ds; + + /* + * Get handle to original BE's root dataset. + */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_copy: " + "failed to open BE root dataset " + "(%s): %s\n"), bt.obe_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* + * Try to clone the BE again. This + * call will end up closing the zfs + * handle passed in whether it + * succeeds or fails. + */ + ret = be_clone_fs_callback(zhp, &bt); + zhp = NULL; + if (ret == 0) { + break; + } else if (ret != BE_ERR_BE_EXISTS) { + be_print_err(gettext("be_copy: " + "failed to clone new BE " + "(%s) from orig BE (%s)\n"), + bt.nbe_name, bt.obe_name); + ret = BE_ERR_CLONE; + goto done; + } + } + + /* + * If we've exhausted the maximum number of + * tries, free the auto BE name and return + * error. + */ + if (i == BE_AUTO_NAME_MAX_TRY) { + be_print_err(gettext("be_copy: failed " + "to create unique auto BE name\n")); + free(bt.nbe_name); + bt.nbe_name = NULL; + ret = BE_ERR_AUTONAME; + goto done; + } + } + zhp = NULL; + + } else { + + /* Do copy (i.e. send BE datasets via zfs_send/recv) */ + + /* + * Verify BE container dataset in nbe_zpool exists. + * If not, create it. + */ + if (!be_create_container_ds(bt.nbe_zpool)) { + ret = BE_ERR_CREATDS; + goto done; + } + + /* + * Iterate through original BE's datasets and send + * them to the other pool. This call will end up closing + * the zfs handle passed in whether it succeeds or fails. + */ + if ((ret = be_send_fs_callback(zhp, &bt)) != 0) { + be_print_err(gettext("be_copy: failed to " + "send BE (%s) to pool (%s)\n"), bt.obe_name, + bt.nbe_zpool); + ret = BE_ERR_COPY; + zhp = NULL; + goto done; + } + zhp = NULL; + } + + /* + * Set flag to note that the dataset(s) for the new BE have been + * successfully created so that if a failure happens from this point + * on, we know to cleanup these datasets. + */ + be_created = B_TRUE; + + /* + * Validate that the new BE is mountable. + * Do not attempt to mount non-global zone datasets + * since they are not cloned yet. + */ + if ((ret = _be_mount(bt.nbe_name, &new_mp, BE_MOUNT_FLAG_NO_ZONES)) + != BE_SUCCESS) { + be_print_err(gettext("be_copy: failed to " + "mount newly created BE\n")); + (void) _be_unmount(bt.nbe_name, 0); + goto done; + } + + /* Set UUID for new BE */ + if (be_set_uuid(bt.nbe_root_ds) != BE_SUCCESS) { + be_print_err(gettext("be_copy: failed to " + "set uuid for new BE\n")); + } + + /* + * Process zones outside of the private BE namespace. + * This has to be done here because we need the uuid set in the + * root dataset of the new BE. The uuid is use to set the parentbe + * property for the new zones datasets. + */ + if (getzoneid() == GLOBAL_ZONEID && + be_get_uuid(bt.obe_root_ds, &uu) == BE_SUCCESS) { + if ((ret = be_copy_zones(bt.obe_name, bt.obe_root_ds, + bt.nbe_root_ds)) != BE_SUCCESS) { + be_print_err(gettext("be_copy: failed to process " + "zones\n")); + goto done; + } + } + + /* + * Generate a list of file systems from the original BE that are + * legacy mounted. We use this list to determine which entries in + * vfstab we need to update for the new BE we've just created. + */ + if ((ret = be_get_legacy_fs(bt.obe_name, bt.obe_root_ds, NULL, NULL, + &fld)) != BE_SUCCESS) { + be_print_err(gettext("be_copy: failed to " + "get legacy mounted file system list for %s\n"), + bt.obe_name); + goto done; + } + + /* + * Update new BE's vfstab. + */ + if ((ret = be_update_vfstab(bt.nbe_name, bt.obe_zpool, bt.nbe_zpool, + &fld, new_mp)) != BE_SUCCESS) { + be_print_err(gettext("be_copy: failed to " + "update new BE's vfstab (%s)\n"), bt.nbe_name); + goto done; + } + + /* Unmount the new BE */ + if ((ret = _be_unmount(bt.nbe_name, 0)) != BE_SUCCESS) { + be_print_err(gettext("be_copy: failed to " + "unmount newly created BE\n")); + goto done; + } + + /* + * Add boot menu entry for newly created clone + */ + if (getzoneid() == GLOBAL_ZONEID && + (ret = be_append_menu(bt.nbe_name, bt.nbe_zpool, + NULL, bt.obe_root_ds, bt.nbe_desc)) != BE_SUCCESS) { + be_print_err(gettext("be_copy: failed to " + "add BE (%s) to boot menu\n"), bt.nbe_name); + goto done; + } + + /* + * If we succeeded in creating an auto named BE, set its policy + * type and return the auto generated name to the caller by storing + * it in the nvlist passed in by the caller. + */ + if (autoname) { + /* Get handle to new BE's root dataset. */ + if ((zhp = zfs_open(g_zfs, bt.nbe_root_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_copy: failed to " + "open BE root dataset (%s): %s\n"), bt.nbe_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* + * Set the policy type property into the new BE's root dataset + */ + if (bt.policy == NULL) { + /* If no policy type provided, use default type */ + bt.policy = be_default_policy(); + } + + if (zfs_prop_set(zhp, BE_POLICY_PROPERTY, bt.policy) != 0) { + be_print_err(gettext("be_copy: failed to " + "set BE policy for %s: %s\n"), bt.nbe_name, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* + * Return the auto generated name to the caller + */ + if (bt.nbe_name) { + if (nvlist_add_string(be_attrs, BE_ATTR_NEW_BE_NAME, + bt.nbe_name) != 0) { + be_print_err(gettext("be_copy: failed to " + "add snap name to be_attrs\n")); + } + } + } + +done: + ZFS_CLOSE(zhp); + be_free_fs_list(&fld); + + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + + free(bt.obe_altroot); + free(new_mp); + + /* + * If a failure occurred and we already created the datasets for + * the new boot environment, destroy them. + */ + if (ret != BE_SUCCESS && be_created) { + be_destroy_data_t cdd = { 0 }; + + cdd.force_unmount = B_TRUE; + + be_print_err(gettext("be_copy: " + "destroying partially created boot environment\n")); + + if (getzoneid() == GLOBAL_ZONEID && be_get_uuid(bt.nbe_root_ds, + &cdd.gz_be_uuid) == 0) + (void) be_destroy_zones(bt.nbe_name, bt.nbe_root_ds, + &cdd); + + (void) _be_destroy(bt.nbe_root_ds, &cdd); + } + + be_zfs_fini(); + + return (ret); +} + +/* ******************************************************************** */ +/* Semi-Private Functions */ +/* ******************************************************************** */ + +/* + * Function: be_find_zpool_callback + * Description: Callback function used to find the pool that a BE lives in. + * Parameters: + * zlp - zpool_handle_t pointer for the current pool being + * looked at. + * data - be_transaction_data_t pointer providing information + * about the BE that's being searched for. + * This function uses the obe_name member of this + * parameter to use as the BE name to search for. + * Upon successfully locating the BE, it populates + * obe_zpool with the pool name that the BE is found in. + * Returns: + * 1 - BE exists in this pool. + * 0 - BE does not exist in this pool. + * Scope: + * Semi-private (library wide use only) + */ +int +be_find_zpool_callback(zpool_handle_t *zlp, void *data) +{ + be_transaction_data_t *bt = data; + const char *zpool = zpool_get_name(zlp); + char be_root_ds[MAXPATHLEN]; + + /* + * Generate string for the BE's root dataset + */ + be_make_root_ds(zpool, bt->obe_name, be_root_ds, sizeof (be_root_ds)); + + /* + * Check if dataset exists + */ + if (zfs_dataset_exists(g_zfs, be_root_ds, ZFS_TYPE_FILESYSTEM)) { + /* BE's root dataset exists in zpool */ + bt->obe_zpool = strdup(zpool); + zpool_close(zlp); + return (1); + } + + zpool_close(zlp); + return (0); +} + +/* + * Function: be_exists_callback + * Description: Callback function used to find out if a BE exists. + * Parameters: + * zlp - zpool_handle_t pointer to the current pool being + * looked at. + * data - BE name to look for. + * Return: + * 1 - BE exists in this pool. + * 0 - BE does not exist in this pool. + * Scope: + * Semi-private (library wide use only) + */ +int +be_exists_callback(zpool_handle_t *zlp, void *data) +{ + const char *zpool = zpool_get_name(zlp); + char *be_name = data; + char be_root_ds[MAXPATHLEN]; + + /* + * Generate string for the BE's root dataset + */ + be_make_root_ds(zpool, be_name, be_root_ds, sizeof (be_root_ds)); + + /* + * Check if dataset exists + */ + if (zfs_dataset_exists(g_zfs, be_root_ds, ZFS_TYPE_FILESYSTEM)) { + /* BE's root dataset exists in zpool */ + zpool_close(zlp); + return (1); + } + + zpool_close(zlp); + return (0); +} + +/* + * Function: be_set_uuid + * Description: This function generates a uuid, unparses it into + * string representation, and sets that string into + * a zfs user property for a root dataset of a BE. + * The name of the user property used to store the + * uuid is org.opensolaris.libbe:uuid + * + * Parameters: + * root_ds - Root dataset of the BE to set a uuid on. + * Return: + * be_errno_t - Failure + * BE_SUCCESS - Success + * Scope: + * Semi-private (library wide ues only) + */ +int +be_set_uuid(char *root_ds) +{ + zfs_handle_t *zhp = NULL; + uuid_t uu = { 0 }; + char uu_string[UUID_PRINTABLE_STRING_LENGTH] = { 0 }; + int ret = BE_SUCCESS; + + /* Generate a UUID and unparse it into string form */ + uuid_generate(uu); + if (uuid_is_null(uu) != 0) { + be_print_err(gettext("be_set_uuid: failed to " + "generate uuid\n")); + return (BE_ERR_GEN_UUID); + } + uuid_unparse(uu, uu_string); + + /* Get handle to the BE's root dataset. */ + if ((zhp = zfs_open(g_zfs, root_ds, ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_set_uuid: failed to " + "open BE root dataset (%s): %s\n"), root_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Set uuid property for the BE */ + if (zfs_prop_set(zhp, BE_UUID_PROPERTY, uu_string) != 0) { + be_print_err(gettext("be_set_uuid: failed to " + "set uuid property for BE: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + } + + ZFS_CLOSE(zhp); + + return (ret); +} + +/* + * Function: be_get_uuid + * Description: This function gets the uuid string from a BE root + * dataset, parses it into internal format, and returns + * it the caller via a reference pointer passed in. + * + * Parameters: + * rootds - Root dataset of the BE to get the uuid from. + * uu - reference pointer to a uuid_t to return uuid in. + * Return: + * be_errno_t - Failure + * BE_SUCCESS - Success + * Scope: + * Semi-private (library wide use only) + */ +int +be_get_uuid(const char *root_ds, uuid_t *uu) +{ + zfs_handle_t *zhp = NULL; + nvlist_t *userprops = NULL; + nvlist_t *propname = NULL; + char *uu_string = NULL; + int ret = BE_SUCCESS; + + /* Get handle to the BE's root dataset. */ + if ((zhp = zfs_open(g_zfs, root_ds, ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_get_uuid: failed to " + "open BE root dataset (%s): %s\n"), root_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Get user properties for BE's root dataset */ + if ((userprops = zfs_get_user_props(zhp)) == NULL) { + be_print_err(gettext("be_get_uuid: failed to " + "get user properties for BE root dataset (%s): %s\n"), + root_ds, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Get UUID string from BE's root dataset user properties */ + if (nvlist_lookup_nvlist(userprops, BE_UUID_PROPERTY, &propname) != 0 || + nvlist_lookup_string(propname, ZPROP_VALUE, &uu_string) != 0) { + /* + * This probably just means that the BE is simply too old + * to have a uuid or that we haven't created a uuid for + * this BE yet. + */ + be_print_err(gettext("be_get_uuid: failed to " + "get uuid property from BE root dataset user " + "properties.\n")); + ret = BE_ERR_NO_UUID; + goto done; + } + /* Parse uuid string into internal format */ + if (uuid_parse(uu_string, *uu) != 0 || uuid_is_null(*uu)) { + be_print_err(gettext("be_get_uuid: failed to " + "parse uuid\n")); + ret = BE_ERR_PARSE_UUID; + goto done; + } + +done: + ZFS_CLOSE(zhp); + return (ret); +} + +/* ******************************************************************** */ +/* Private Functions */ +/* ******************************************************************** */ + +/* + * Function: _be_destroy + * Description: Destroy a BE and all of its children datasets and snapshots. + * This function is called for both global BEs and non-global BEs. + * The root dataset of either the global BE or non-global BE to be + * destroyed is passed in. + * Parameters: + * root_ds - pointer to the name of the root dataset of the + * BE to destroy. + * dd - pointer to a be_destroy_data_t structure. + * + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +_be_destroy(const char *root_ds, be_destroy_data_t *dd) +{ + zfs_handle_t *zhp = NULL; + char origin[MAXPATHLEN]; + char parent[MAXPATHLEN]; + char *snap = NULL; + boolean_t has_origin = B_FALSE; + int ret = BE_SUCCESS; + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_destroy: failed to " + "open BE root dataset (%s): %s\n"), root_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Demote this BE in case it has dependent clones. This call + * will end up closing the zfs handle passed in whether it + * succeeds or fails. + */ + if (be_demote_callback(zhp, NULL) != 0) { + be_print_err(gettext("be_destroy: " + "failed to demote BE %s\n"), root_ds); + return (BE_ERR_DEMOTE); + } + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_destroy: failed to " + "open BE root dataset (%s): %s\n"), root_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Get the origin of this BE's root dataset. This will be used + * later to destroy the snapshots originally used to create this BE. + */ + if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin, sizeof (origin), NULL, + NULL, 0, B_FALSE) == 0) { + (void) strlcpy(parent, origin, sizeof (parent)); + if (be_get_snap(parent, &snap) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + be_print_err(gettext("be_destroy: failed to " + "get snapshot name from origin %s\n"), origin); + return (BE_ERR_INVAL); + } + has_origin = B_TRUE; + } + + /* + * Destroy the BE's root and its hierarchical children. This call + * will end up closing the zfs handle passed in whether it succeeds + * or fails. + */ + if (be_destroy_callback(zhp, dd) != 0) { + be_print_err(gettext("be_destroy: failed to " + "destroy BE %s\n"), root_ds); + return (BE_ERR_DESTROY); + } + + /* If BE has an origin */ + if (has_origin) { + + /* + * If origin snapshot doesn't have any other + * dependents, delete the origin. + */ + if ((zhp = zfs_open(g_zfs, origin, ZFS_TYPE_SNAPSHOT)) == + NULL) { + be_print_err(gettext("be_destroy: failed to " + "open BE's origin (%s): %s\n"), origin, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + return (ret); + } + + /* If origin has dependents, don't delete it. */ + if (zfs_prop_get_int(zhp, ZFS_PROP_NUMCLONES) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + ZFS_CLOSE(zhp); + + /* Get handle to BE's parent's root dataset */ + if ((zhp = zfs_open(g_zfs, parent, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_destroy: failed to " + "open BE's parent root dataset (%s): %s\n"), parent, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + return (ret); + } + + /* Destroy the snapshot origin used to create this BE. */ + /* + * The boolean set to B_FALSE and passed to zfs_destroy_snaps() + * tells zfs to process and destroy the snapshots now. + * Otherwise the call will potentially return where the + * snapshot isn't actually destroyed yet, and ZFS is waiting + * until all the references to the snapshot have been + * released before actually destroying the snapshot. + */ + if (zfs_destroy_snaps(zhp, snap, B_FALSE) != 0) { + be_print_err(gettext("be_destroy: failed to " + "destroy original snapshots used to create " + "BE: %s\n"), libzfs_error_description(g_zfs)); + + /* + * If a failure happened because a clone exists, + * don't return a failure to the user. Above, we're + * only checking that the root dataset's origin + * snapshot doesn't have dependent clones, but its + * possible that a subordinate dataset origin snapshot + * has a clone. We really need to check for that + * before trying to destroy the origin snapshot. + */ + if (libzfs_errno(g_zfs) != EZFS_EXISTS) { + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + } + ZFS_CLOSE(zhp); + } + + return (ret); +} + +/* + * Function: be_destroy_zones + * Description: Find valid zone's and call be_destroy_zone_roots to destroy its + * corresponding dataset and all of its children datasets + * and snapshots. + * Parameters: + * be_name - name of global boot environment being destroyed + * be_root_ds - root dataset of global boot environment being + * destroyed. + * dd - be_destroy_data_t pointer + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + * + * NOTES - Requires that the BE being deleted has no dependent BEs. If it + * does, the destroy will fail. + */ +static int +be_destroy_zones(char *be_name, char *be_root_ds, be_destroy_data_t *dd) +{ + int i; + int ret = BE_SUCCESS; + int force_umnt = BE_UNMOUNT_FLAG_NULL; + char *zonepath = NULL; + char *zonename = NULL; + char *zonepath_ds = NULL; + char *mp = NULL; + zoneList_t zlist = NULL; + zoneBrandList_t *brands = NULL; + zfs_handle_t *zhp = NULL; + + /* If zones are not implemented, then get out. */ + if (!z_zones_are_implemented()) { + return (BE_SUCCESS); + } + + /* Get list of supported brands */ + if ((brands = be_get_supported_brandlist()) == NULL) { + be_print_err(gettext("be_destroy_zones: " + "no supported brands\n")); + return (BE_SUCCESS); + } + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, be_root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_destroy_zones: failed to " + "open BE root dataset (%s): %s\n"), be_root_ds, + libzfs_error_description(g_zfs)); + z_free_brand_list(brands); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * If the global BE is not mounted, we must mount it here to + * gather data about the non-global zones in it. + */ + if (!zfs_is_mounted(zhp, &mp)) { + if ((ret = _be_mount(be_name, &mp, + BE_MOUNT_FLAG_NO_ZONES)) != BE_SUCCESS) { + be_print_err(gettext("be_destroy_zones: failed to " + "mount the BE (%s) for zones processing.\n"), + be_name); + ZFS_CLOSE(zhp); + z_free_brand_list(brands); + return (ret); + } + } + ZFS_CLOSE(zhp); + + z_set_zone_root(mp); + free(mp); + + /* Get list of supported zones. */ + if ((zlist = z_get_nonglobal_zone_list_by_brand(brands)) == NULL) { + z_free_brand_list(brands); + return (BE_SUCCESS); + } + + /* Unmount the BE before destroying the zones in it. */ + if (dd->force_unmount) + force_umnt = BE_UNMOUNT_FLAG_FORCE; + if ((ret = _be_unmount(be_name, force_umnt)) != BE_SUCCESS) { + be_print_err(gettext("be_destroy_zones: failed to " + "unmount the BE (%s)\n"), be_name); + goto done; + } + + /* Iterate through the zones and destroy them. */ + for (i = 0; (zonename = z_zlist_get_zonename(zlist, i)) != NULL; i++) { + + /* Skip zones that aren't at least installed */ + if (z_zlist_get_current_state(zlist, i) < ZONE_STATE_INSTALLED) + continue; + + zonepath = z_zlist_get_zonepath(zlist, i); + + /* + * Get the dataset of this zonepath. If its not + * a dataset, skip it. + */ + if ((zonepath_ds = be_get_ds_from_dir(zonepath)) == NULL) + continue; + + /* + * Check if this zone is supported based on the + * dataset of its zonepath. + */ + if (!be_zone_supported(zonepath_ds)) { + free(zonepath_ds); + continue; + } + + /* Find the zone BE root datasets for this zone. */ + if ((ret = be_destroy_zone_roots(zonepath_ds, dd)) + != BE_SUCCESS) { + be_print_err(gettext("be_destroy_zones: failed to " + "find and destroy zone roots for zone %s\n"), + zonename); + free(zonepath_ds); + goto done; + } + free(zonepath_ds); + } + +done: + z_free_brand_list(brands); + z_free_zone_list(zlist); + + return (ret); +} + +/* + * Function: be_destroy_zone_roots + * Description: This function will open the zone's root container dataset + * and iterate the datasets within, looking for roots that + * belong to the given global BE and destroying them. + * If no other zone roots remain in the zone's root container + * dataset, the function will destroy it and the zone's + * zonepath dataset as well. + * Parameters: + * zonepath_ds - pointer to zone's zonepath dataset. + * dd - pointer to a linked destroy data. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_destroy_zone_roots(char *zonepath_ds, be_destroy_data_t *dd) +{ + zfs_handle_t *zhp; + char zone_container_ds[MAXPATHLEN]; + int ret = BE_SUCCESS; + + /* Generate string for the root container dataset for this zone. */ + be_make_container_ds(zonepath_ds, zone_container_ds, + sizeof (zone_container_ds)); + + /* Get handle to this zone's root container dataset. */ + if ((zhp = zfs_open(g_zfs, zone_container_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_destroy_zone_roots: failed to " + "open zone root container dataset (%s): %s\n"), + zone_container_ds, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Iterate through all of this zone's BEs, destroying the ones + * that belong to the parent global BE. + */ + if ((ret = zfs_iter_filesystems(zhp, be_destroy_zone_roots_callback, + dd)) != 0) { + be_print_err(gettext("be_destroy_zone_roots: failed to " + "destroy zone roots under zonepath dataset %s: %s\n"), + zonepath_ds, libzfs_error_description(g_zfs)); + ZFS_CLOSE(zhp); + return (ret); + } + ZFS_CLOSE(zhp); + + /* Get handle to this zone's root container dataset. */ + if ((zhp = zfs_open(g_zfs, zone_container_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_destroy_zone_roots: failed to " + "open zone root container dataset (%s): %s\n"), + zone_container_ds, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * If there are no more zone roots in this zone's root container, + * dataset, destroy it and the zonepath dataset as well. + */ + if (zfs_iter_filesystems(zhp, be_zone_root_exists_callback, NULL) + == 0) { + /* Destroy the zone root container dataset */ + if (zfs_unmount(zhp, NULL, MS_FORCE) != 0 || + zfs_destroy(zhp, B_FALSE) != 0) { + be_print_err(gettext("be_destroy_zone_roots: failed to " + "destroy zone root container dataset (%s): %s\n"), + zone_container_ds, libzfs_error_description(g_zfs)); + goto done; + } + ZFS_CLOSE(zhp); + + /* Get handle to zonepath dataset */ + if ((zhp = zfs_open(g_zfs, zonepath_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_destroy_zone_roots: failed to " + "open zonepath dataset (%s): %s\n"), + zonepath_ds, libzfs_error_description(g_zfs)); + goto done; + } + + /* Destroy zonepath dataset */ + if (zfs_unmount(zhp, NULL, MS_FORCE) != 0 || + zfs_destroy(zhp, B_FALSE) != 0) { + be_print_err(gettext("be_destroy_zone_roots: " + "failed to destroy zonepath dataest %s: %s\n"), + zonepath_ds, libzfs_error_description(g_zfs)); + goto done; + } + } + +done: + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_destroy_zone_roots_callback + * Description: This function is used as a callback to iterate over all of + * a zone's root datasets, finding the one's that + * correspond to the current BE. The name's + * of the zone root datasets are then destroyed by _be_destroy(). + * Parameters: + * zhp - zfs_handle_t pointer to current dataset being processed + * data - be_destroy_data_t pointer + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_destroy_zone_roots_callback(zfs_handle_t *zhp, void *data) +{ + be_destroy_data_t *dd = data; + uuid_t parent_uuid = { 0 }; + int ret = 0; + + if (be_zone_get_parent_uuid(zfs_get_name(zhp), &parent_uuid) + != BE_SUCCESS) { + be_print_err(gettext("be_destroy_zone_roots_callback: " + "could not get parentuuid for zone root dataset %s\n"), + zfs_get_name(zhp)); + ZFS_CLOSE(zhp); + return (0); + } + + if (uuid_compare(dd->gz_be_uuid, parent_uuid) == 0) { + /* + * Found a zone root dataset belonging to the parent + * BE being destroyed. Destroy this zone BE. + */ + if ((ret = _be_destroy(zfs_get_name(zhp), dd)) != BE_SUCCESS) { + be_print_err(gettext("be_destroy_zone_root_callback: " + "failed to destroy zone root %s\n"), + zfs_get_name(zhp)); + ZFS_CLOSE(zhp); + return (ret); + } + } + ZFS_CLOSE(zhp); + + return (ret); +} + +/* + * Function: be_copy_zones + * Description: Find valid zones and clone them to create their + * corresponding datasets for the BE being created. + * Parameters: + * obe_name - name of source global BE being copied. + * obe_root_ds - root dataset of source global BE being copied. + * nbe_root_ds - root dataset of target global BE. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_copy_zones(char *obe_name, char *obe_root_ds, char *nbe_root_ds) +{ + int i, num_retries; + int ret = BE_SUCCESS; + int iret = 0; + char *zonename = NULL; + char *zonepath = NULL; + char *zone_be_name = NULL; + char *temp_mntpt = NULL; + char *new_zone_be_name = NULL; + char zoneroot[MAXPATHLEN]; + char zoneroot_ds[MAXPATHLEN]; + char zone_container_ds[MAXPATHLEN]; + char new_zoneroot_ds[MAXPATHLEN]; + char ss[MAXPATHLEN]; + uuid_t uu = { 0 }; + char uu_string[UUID_PRINTABLE_STRING_LENGTH] = { 0 }; + be_transaction_data_t bt = { 0 }; + zfs_handle_t *obe_zhp = NULL; + zfs_handle_t *nbe_zhp = NULL; + zfs_handle_t *z_zhp = NULL; + zoneList_t zlist = NULL; + zoneBrandList_t *brands = NULL; + boolean_t mounted_here = B_FALSE; + char *snap_name = NULL; + + /* If zones are not implemented, then get out. */ + if (!z_zones_are_implemented()) { + return (BE_SUCCESS); + } + + /* Get list of supported brands */ + if ((brands = be_get_supported_brandlist()) == NULL) { + be_print_err(gettext("be_copy_zones: " + "no supported brands\n")); + return (BE_SUCCESS); + } + + /* Get handle to origin BE's root dataset */ + if ((obe_zhp = zfs_open(g_zfs, obe_root_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_copy_zones: failed to open " + "the origin BE root dataset (%s) for zones processing: " + "%s\n"), obe_root_ds, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Get handle to newly cloned BE's root dataset */ + if ((nbe_zhp = zfs_open(g_zfs, nbe_root_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_copy_zones: failed to open " + "the new BE root dataset (%s): %s\n"), nbe_root_ds, + libzfs_error_description(g_zfs)); + ZFS_CLOSE(obe_zhp); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Get the uuid of the newly cloned parent BE. */ + if (be_get_uuid(zfs_get_name(nbe_zhp), &uu) != BE_SUCCESS) { + be_print_err(gettext("be_copy_zones: " + "failed to get uuid for BE root " + "dataset %s\n"), zfs_get_name(nbe_zhp)); + ZFS_CLOSE(nbe_zhp); + goto done; + } + ZFS_CLOSE(nbe_zhp); + uuid_unparse(uu, uu_string); + + /* + * If the origin BE is not mounted, we must mount it here to + * gather data about the non-global zones in it. + */ + if (!zfs_is_mounted(obe_zhp, &temp_mntpt)) { + if ((ret = _be_mount(obe_name, &temp_mntpt, + BE_MOUNT_FLAG_NULL)) != BE_SUCCESS) { + be_print_err(gettext("be_copy_zones: failed to " + "mount the BE (%s) for zones procesing.\n"), + obe_name); + goto done; + } + mounted_here = B_TRUE; + } + + z_set_zone_root(temp_mntpt); + + /* Get list of supported zones. */ + if ((zlist = z_get_nonglobal_zone_list_by_brand(brands)) == NULL) { + ret = BE_SUCCESS; + goto done; + } + + for (i = 0; (zonename = z_zlist_get_zonename(zlist, i)) != NULL; i++) { + + be_fs_list_data_t fld = { 0 }; + char zonepath_ds[MAXPATHLEN]; + char *ds = NULL; + + /* Get zonepath of zone */ + zonepath = z_zlist_get_zonepath(zlist, i); + + /* Skip zones that aren't at least installed */ + if (z_zlist_get_current_state(zlist, i) < ZONE_STATE_INSTALLED) + continue; + + /* + * Get the dataset of this zonepath. If its not + * a dataset, skip it. + */ + if ((ds = be_get_ds_from_dir(zonepath)) == NULL) + continue; + + (void) strlcpy(zonepath_ds, ds, sizeof (zonepath_ds)); + free(ds); + ds = NULL; + + /* Get zoneroot directory */ + be_make_zoneroot(zonepath, zoneroot, sizeof (zoneroot)); + + /* If zonepath dataset not supported, skip it. */ + if (!be_zone_supported(zonepath_ds)) { + continue; + } + + if ((ret = be_find_active_zone_root(obe_zhp, zonepath_ds, + zoneroot_ds, sizeof (zoneroot_ds))) != BE_SUCCESS) { + be_print_err(gettext("be_copy_zones: " + "failed to find active zone root for zone %s " + "in BE %s\n"), zonename, obe_name); + goto done; + } + + be_make_container_ds(zonepath_ds, zone_container_ds, + sizeof (zone_container_ds)); + + if ((z_zhp = zfs_open(g_zfs, zoneroot_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_copy_zones: " + "failed to open zone root dataset (%s): %s\n"), + zoneroot_ds, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + zone_be_name = + be_get_zone_be_name(zoneroot_ds, zone_container_ds); + + if ((new_zone_be_name = be_auto_zone_be_name(zone_container_ds, + zone_be_name)) == NULL) { + be_print_err(gettext("be_copy_zones: failed " + "to generate auto name for zone BE.\n")); + ret = BE_ERR_AUTONAME; + goto done; + } + + if ((snap_name = be_auto_snap_name()) == NULL) { + be_print_err(gettext("be_copy_zones: failed to " + "generate snapshot name for zone BE.\n")); + ret = BE_ERR_AUTONAME; + goto done; + } + + (void) snprintf(ss, sizeof (ss), "%s@%s", zoneroot_ds, + snap_name); + + if (zfs_snapshot(g_zfs, ss, B_TRUE, NULL) != 0) { + be_print_err(gettext("be_copy_zones: " + "failed to snapshot zone BE (%s): %s\n"), + ss, libzfs_error_description(g_zfs)); + if (libzfs_errno(g_zfs) == EZFS_EXISTS) + ret = BE_ERR_ZONE_SS_EXISTS; + else + ret = zfs_err_to_be_err(g_zfs); + + goto done; + } + + (void) snprintf(new_zoneroot_ds, sizeof (new_zoneroot_ds), + "%s/%s", zone_container_ds, new_zone_be_name); + + bt.obe_name = zone_be_name; + bt.obe_root_ds = zoneroot_ds; + bt.obe_snap_name = snap_name; + bt.obe_altroot = temp_mntpt; + bt.nbe_name = new_zone_be_name; + bt.nbe_root_ds = new_zoneroot_ds; + + if (nvlist_alloc(&bt.nbe_zfs_props, NV_UNIQUE_NAME, 0) != 0) { + be_print_err(gettext("be_copy_zones: " + "internal error: out of memory\n")); + ret = BE_ERR_NOMEM; + goto done; + } + + /* + * The call to be_clone_fs_callback always closes the + * zfs_handle so there's no need to close z_zhp. + */ + if ((iret = be_clone_fs_callback(z_zhp, &bt)) != 0) { + z_zhp = NULL; + if (iret != BE_ERR_BE_EXISTS) { + be_print_err(gettext("be_copy_zones: " + "failed to create zone BE clone for new " + "zone BE %s\n"), new_zone_be_name); + ret = iret; + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + goto done; + } + /* + * We failed to create the new zone BE because a zone + * BE with the auto-name we generated above has since + * come into existence. Regenerate a new auto-name + * and retry. + */ + for (num_retries = 1; + num_retries < BE_AUTO_NAME_MAX_TRY; + num_retries++) { + + /* Sleep 1 before retrying */ + (void) sleep(1); + + /* Generate new auto zone BE name */ + free(new_zone_be_name); + if ((new_zone_be_name = be_auto_zone_be_name( + zone_container_ds, + zone_be_name)) == NULL) { + be_print_err(gettext("be_copy_zones: " + "failed to generate auto name " + "for zone BE.\n")); + ret = BE_ERR_AUTONAME; + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + goto done; + } + + (void) snprintf(new_zoneroot_ds, + sizeof (new_zoneroot_ds), + "%s/%s", zone_container_ds, + new_zone_be_name); + bt.nbe_name = new_zone_be_name; + bt.nbe_root_ds = new_zoneroot_ds; + + /* + * Get handle to original zone BE's root + * dataset. + */ + if ((z_zhp = zfs_open(g_zfs, zoneroot_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_copy_zones: " + "failed to open zone root " + "dataset (%s): %s\n"), + zoneroot_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + goto done; + } + + /* + * Try to clone the zone BE again. This + * call will end up closing the zfs + * handle passed in whether it + * succeeds or fails. + */ + iret = be_clone_fs_callback(z_zhp, &bt); + z_zhp = NULL; + if (iret == 0) { + break; + } else if (iret != BE_ERR_BE_EXISTS) { + be_print_err(gettext("be_copy_zones: " + "failed to create zone BE clone " + "for new zone BE %s\n"), + new_zone_be_name); + ret = iret; + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + goto done; + } + } + /* + * If we've exhausted the maximum number of + * tries, free the auto zone BE name and return + * error. + */ + if (num_retries == BE_AUTO_NAME_MAX_TRY) { + be_print_err(gettext("be_copy_zones: failed " + "to create a unique auto zone BE name\n")); + free(bt.nbe_name); + bt.nbe_name = NULL; + ret = BE_ERR_AUTONAME; + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + goto done; + } + } + + if (bt.nbe_zfs_props != NULL) + nvlist_free(bt.nbe_zfs_props); + + z_zhp = NULL; + + if ((z_zhp = zfs_open(g_zfs, new_zoneroot_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_copy_zones: " + "failed to open the new zone BE root dataset " + "(%s): %s\n"), new_zoneroot_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (zfs_prop_set(z_zhp, BE_ZONE_PARENTBE_PROPERTY, + uu_string) != 0) { + be_print_err(gettext("be_copy_zones: " + "failed to set parentbe property\n")); + ZFS_CLOSE(z_zhp); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (zfs_prop_set(z_zhp, BE_ZONE_ACTIVE_PROPERTY, "on") != 0) { + be_print_err(gettext("be_copy_zones: " + "failed to set active property\n")); + ZFS_CLOSE(z_zhp); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* + * Generate a list of file systems from the original + * zone BE that are legacy mounted. We use this list + * to determine which entries in the vfstab we need to + * update for the new zone BE we've just created. + */ + if ((ret = be_get_legacy_fs(obe_name, obe_root_ds, + zoneroot_ds, zoneroot, &fld)) != BE_SUCCESS) { + be_print_err(gettext("be_copy_zones: " + "failed to get legacy mounted file system " + "list for zone %s\n"), zonename); + ZFS_CLOSE(z_zhp); + goto done; + } + + /* + * Update new zone BE's vfstab. + */ + if ((ret = be_update_zone_vfstab(z_zhp, bt.nbe_name, + zonepath_ds, zonepath_ds, &fld)) != BE_SUCCESS) { + be_print_err(gettext("be_copy_zones: " + "failed to update new BE's vfstab (%s)\n"), + bt.nbe_name); + ZFS_CLOSE(z_zhp); + be_free_fs_list(&fld); + goto done; + } + + be_free_fs_list(&fld); + ZFS_CLOSE(z_zhp); + } + +done: + free(snap_name); + if (brands != NULL) + z_free_brand_list(brands); + if (zlist != NULL) + z_free_zone_list(zlist); + + if (mounted_here) + (void) _be_unmount(obe_name, 0); + + ZFS_CLOSE(obe_zhp); + return (ret); +} + +/* + * Function: be_clone_fs_callback + * Description: Callback function used to iterate through a BE's filesystems + * to clone them for the new BE. + * Parameters: + * zhp - zfs_handle_t pointer for the filesystem being processed. + * data - be_transaction_data_t pointer providing information + * about original BE and new BE. + * Return: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_clone_fs_callback(zfs_handle_t *zhp, void *data) +{ + be_transaction_data_t *bt = data; + zfs_handle_t *zhp_ss = NULL; + char prop_buf[MAXPATHLEN]; + char zhp_name[ZFS_MAXNAMELEN]; + char clone_ds[MAXPATHLEN]; + char ss[MAXPATHLEN]; + int ret = 0; + + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, prop_buf, + ZFS_MAXPROPLEN, NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_clone_fs_callback: " + "failed to get dataset mountpoint (%s): %s\n"), + zfs_get_name(zhp), libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + + if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED) != 0 && + strcmp(prop_buf, "legacy") != 0) { + /* + * Since zfs can't currently handle setting the + * mountpoint for a zoned dataset we'll have to skip + * this dataset. This is because the mountpoint is not + * set to "legacy". + */ + goto zoned; + } + /* + * Get a copy of the dataset name from the zfs handle + */ + (void) strlcpy(zhp_name, zfs_get_name(zhp), sizeof (zhp_name)); + + /* + * Get the clone dataset name and prepare the zfs properties for it. + */ + if ((ret = be_prep_clone_send_fs(zhp, bt, clone_ds, + sizeof (clone_ds))) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Generate the name of the snapshot to use. + */ + (void) snprintf(ss, sizeof (ss), "%s@%s", zhp_name, + bt->obe_snap_name); + + /* + * Get handle to snapshot. + */ + if ((zhp_ss = zfs_open(g_zfs, ss, ZFS_TYPE_SNAPSHOT)) == NULL) { + be_print_err(gettext("be_clone_fs_callback: " + "failed to get handle to snapshot (%s): %s\n"), ss, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Clone the dataset. + */ + if (zfs_clone(zhp_ss, clone_ds, bt->nbe_zfs_props) != 0) { + be_print_err(gettext("be_clone_fs_callback: " + "failed to create clone dataset (%s): %s\n"), + clone_ds, libzfs_error_description(g_zfs)); + + ZFS_CLOSE(zhp_ss); + ZFS_CLOSE(zhp); + + return (zfs_err_to_be_err(g_zfs)); + } + + ZFS_CLOSE(zhp_ss); + +zoned: + /* + * Iterate through zhp's children datasets (if any) + * and clone them accordingly. + */ + if ((ret = zfs_iter_filesystems(zhp, be_clone_fs_callback, bt)) != 0) { + /* + * Error occurred while processing a child dataset. + * Destroy this dataset and return error. + */ + zfs_handle_t *d_zhp = NULL; + + ZFS_CLOSE(zhp); + + if ((d_zhp = zfs_open(g_zfs, clone_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + return (ret); + } + + (void) zfs_destroy(d_zhp, B_FALSE); + ZFS_CLOSE(d_zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_send_fs_callback + * Description: Callback function used to iterate through a BE's filesystems + * to copy them for the new BE. + * Parameters: + * zhp - zfs_handle_t pointer for the filesystem being processed. + * data - be_transaction_data_t pointer providing information + * about original BE and new BE. + * Return: + * 0 - Success + * be_errnot_t - Failure + * Scope: + * Private + */ +static int +be_send_fs_callback(zfs_handle_t *zhp, void *data) +{ + be_transaction_data_t *bt = data; + recvflags_t flags = { 0 }; + char zhp_name[ZFS_MAXNAMELEN]; + char clone_ds[MAXPATHLEN]; + sendflags_t send_flags = { 0 }; + int pid, status, retval; + int srpipe[2]; + int ret = 0; + + /* + * Get a copy of the dataset name from the zfs handle + */ + (void) strlcpy(zhp_name, zfs_get_name(zhp), sizeof (zhp_name)); + + /* + * Get the clone dataset name and prepare the zfs properties for it. + */ + if ((ret = be_prep_clone_send_fs(zhp, bt, clone_ds, + sizeof (clone_ds))) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Create the new dataset. + */ + if (zfs_create(g_zfs, clone_ds, ZFS_TYPE_FILESYSTEM, bt->nbe_zfs_props) + != 0) { + be_print_err(gettext("be_send_fs_callback: " + "failed to create new dataset '%s': %s\n"), + clone_ds, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Destination file system is already created + * hence we need to set the force flag on + */ + flags.force = B_TRUE; + + /* + * Initiate the pipe to be used for the send and recv + */ + if (pipe(srpipe) != 0) { + int err = errno; + be_print_err(gettext("be_send_fs_callback: failed to " + "open pipe\n")); + ZFS_CLOSE(zhp); + return (errno_to_be_err(err)); + } + + /* + * Fork off a child to send the dataset + */ + if ((pid = fork()) == -1) { + int err = errno; + be_print_err(gettext("be_send_fs_callback: failed to fork\n")); + (void) close(srpipe[0]); + (void) close(srpipe[1]); + ZFS_CLOSE(zhp); + return (errno_to_be_err(err)); + } else if (pid == 0) { /* child process */ + (void) close(srpipe[0]); + + /* Send dataset */ + if (zfs_send(zhp, NULL, bt->obe_snap_name, send_flags, + srpipe[1], NULL, NULL, NULL) != 0) { + _exit(1); + } + ZFS_CLOSE(zhp); + + _exit(0); + } + + (void) close(srpipe[1]); + + /* Receive dataset */ + if (zfs_receive(g_zfs, clone_ds, flags, srpipe[0], NULL) != 0) { + be_print_err(gettext("be_send_fs_callback: failed to " + "recv dataset (%s)\n"), clone_ds); + } + (void) close(srpipe[0]); + + /* wait for child to exit */ + do { + retval = waitpid(pid, &status, 0); + if (retval == -1) { + status = 0; + } + } while (retval != pid); + + if (WEXITSTATUS(status) != 0) { + be_print_err(gettext("be_send_fs_callback: failed to " + "send dataset (%s)\n"), zhp_name); + ZFS_CLOSE(zhp); + return (BE_ERR_ZFS); + } + + + /* + * Iterate through zhp's children datasets (if any) + * and send them accordingly. + */ + if ((ret = zfs_iter_filesystems(zhp, be_send_fs_callback, bt)) != 0) { + /* + * Error occurred while processing a child dataset. + * Destroy this dataset and return error. + */ + zfs_handle_t *d_zhp = NULL; + + ZFS_CLOSE(zhp); + + if ((d_zhp = zfs_open(g_zfs, clone_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + return (ret); + } + + (void) zfs_destroy(d_zhp, B_FALSE); + ZFS_CLOSE(d_zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_destroy_callback + * Description: Callback function used to destroy a BEs children datasets + * and snapshots. + * Parameters: + * zhp - zfs_handle_t pointer to the filesystem being processed. + * data - Not used. + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_destroy_callback(zfs_handle_t *zhp, void *data) +{ + be_destroy_data_t *dd = data; + int ret = 0; + + /* + * Iterate down this file system's hierarchical children + * and destroy them first. + */ + if ((ret = zfs_iter_filesystems(zhp, be_destroy_callback, dd)) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + + if (dd->destroy_snaps) { + /* + * Iterate through this file system's snapshots and + * destroy them before destroying the file system itself. + */ + if ((ret = zfs_iter_snapshots(zhp, be_destroy_callback, dd)) + != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + } + + /* Attempt to unmount the dataset before destroying it */ + if (dd->force_unmount) { + if ((ret = zfs_unmount(zhp, NULL, MS_FORCE)) != 0) { + be_print_err(gettext("be_destroy_callback: " + "failed to unmount %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + } + + if (zfs_destroy(zhp, B_FALSE) != 0) { + be_print_err(gettext("be_destroy_callback: " + "failed to destroy %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_demote_callback + * Description: This callback function is used to iterate through the file + * systems of a BE, looking for the right clone to promote such + * that this file system is left without any dependent clones. + * If the file system has no dependent clones, it doesn't need + * to get demoted, and the function will return success. + * + * The demotion will be done in two passes. The first pass + * will attempt to find the youngest snapshot that has a clone + * that is part of some other BE. The second pass will attempt + * to find the youngest snapshot that has a clone that is not + * part of a BE. Doing this helps ensure the aggregated set of + * file systems that compose a BE stay coordinated wrt BE + * snapshots and BE dependents. It also prevents a random user + * generated clone of a BE dataset to become the parent of other + * BE datasets after demoting this dataset. + * + * Parameters: + * zhp - zfs_handle_t pointer to the current file system being + * processed. + * data - not used. + * Return: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +/* LINTED */ +be_demote_callback(zfs_handle_t *zhp, void *data) +{ + be_demote_data_t dd = { 0 }; + int i, ret = 0; + + /* + * Initialize be_demote_data for the first pass - this will find a + * clone in another BE, if one exists. + */ + dd.find_in_BE = B_TRUE; + + for (i = 0; i < 2; i++) { + + if (zfs_iter_snapshots(zhp, be_demote_find_clone_callback, &dd) + != 0) { + be_print_err(gettext("be_demote_callback: " + "failed to iterate snapshots for %s: %s\n"), + zfs_get_name(zhp), libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + if (dd.clone_zhp != NULL) { + /* Found the clone to promote. Promote it. */ + if (zfs_promote(dd.clone_zhp) != 0) { + be_print_err(gettext("be_demote_callback: " + "failed to promote %s: %s\n"), + zfs_get_name(dd.clone_zhp), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(dd.clone_zhp); + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(dd.clone_zhp); + } + + /* + * Reinitialize be_demote_data for the second pass. + * This will find a user created clone outside of any BE + * namespace, if one exists. + */ + dd.clone_zhp = NULL; + dd.origin_creation = 0; + dd.snapshot = NULL; + dd.find_in_BE = B_FALSE; + } + + /* Iterate down this file system's children and demote them */ + if ((ret = zfs_iter_filesystems(zhp, be_demote_callback, NULL)) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_demote_find_clone_callback + * Description: This callback function is used to iterate through the + * snapshots of a dataset, looking for the youngest snapshot + * that has a clone. If found, it returns a reference to the + * clone back to the caller in the callback data. + * Parameters: + * zhp - zfs_handle_t pointer to current snapshot being looked at + * data - be_demote_data_t pointer used to store the clone that + * is found. + * Returns: + * 0 - Successfully iterated through all snapshots. + * 1 - Failed to iterate through all snapshots. + * Scope: + * Private + */ +static int +be_demote_find_clone_callback(zfs_handle_t *zhp, void *data) +{ + be_demote_data_t *dd = data; + time_t snap_creation; + int zret = 0; + + /* If snapshot has no clones, no need to look at it */ + if (zfs_prop_get_int(zhp, ZFS_PROP_NUMCLONES) == 0) { + ZFS_CLOSE(zhp); + return (0); + } + + dd->snapshot = zfs_get_name(zhp); + + /* Get the creation time of this snapshot */ + snap_creation = (time_t)zfs_prop_get_int(zhp, ZFS_PROP_CREATION); + + /* + * If this snapshot's creation time is greater than (or younger than) + * the current youngest snapshot found, iterate this snapshot to + * check if it has a clone that we're looking for. + */ + if (snap_creation >= dd->origin_creation) { + /* + * Iterate the dependents of this snapshot to find a + * a clone that's a direct dependent. + */ + if ((zret = zfs_iter_dependents(zhp, B_FALSE, + be_demote_get_one_clone, dd)) == -1) { + be_print_err(gettext("be_demote_find_clone_callback: " + "failed to iterate dependents of %s\n"), + zfs_get_name(zhp)); + ZFS_CLOSE(zhp); + return (1); + } else if (zret == 1) { + /* + * Found a clone, update the origin_creation time + * in the callback data. + */ + dd->origin_creation = snap_creation; + } + } + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_demote_get_one_clone + * Description: This callback function is used to iterate through a + * snapshot's dependencies to find a filesystem that is a + * direct clone of the snapshot being iterated. + * Parameters: + * zhp - zfs_handle_t pointer to current dataset being looked at + * data - be_demote_data_t pointer used to store the clone + * that is found, and also provides flag to note + * whether or not the clone filesystem being searched + * for needs to be found in a BE dataset hierarchy. + * Return: + * 1 - Success, found clone and its also a BE's root dataset. + * 0 - Failure, clone not found. + * Scope: + * Private + */ +static int +be_demote_get_one_clone(zfs_handle_t *zhp, void *data) +{ + be_demote_data_t *dd = data; + char origin[ZFS_MAXNAMELEN]; + char ds_path[ZFS_MAXNAMELEN]; + + if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) { + ZFS_CLOSE(zhp); + return (0); + } + + (void) strlcpy(ds_path, zfs_get_name(zhp), sizeof (ds_path)); + + /* + * Make sure this is a direct clone of the snapshot + * we're iterating. + */ + if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin, sizeof (origin), NULL, + NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_demote_get_one_clone: " + "failed to get origin of %s: %s\n"), ds_path, + libzfs_error_description(g_zfs)); + ZFS_CLOSE(zhp); + return (0); + } + if (strcmp(origin, dd->snapshot) != 0) { + ZFS_CLOSE(zhp); + return (0); + } + + if (dd->find_in_BE) { + if ((zpool_iter(g_zfs, be_check_be_roots_callback, ds_path)) + > 0) { + if (dd->clone_zhp != NULL) + ZFS_CLOSE(dd->clone_zhp); + dd->clone_zhp = zhp; + return (1); + } + + ZFS_CLOSE(zhp); + return (0); + } + + if (dd->clone_zhp != NULL) + ZFS_CLOSE(dd->clone_zhp); + + dd->clone_zhp = zhp; + return (1); +} + +/* + * Function: be_get_snap + * Description: This function takes a snapshot dataset name and separates + * out the parent dataset portion from the snapshot name. + * I.e. it finds the '@' in the snapshot dataset name and + * replaces it with a '\0'. + * Parameters: + * origin - char pointer to a snapshot dataset name. Its + * contents will be modified by this function. + * *snap - pointer to a char pointer. Will be set to the + * snapshot name portion upon success. + * Return: + * BE_SUCCESS - Success + * 1 - Failure + * Scope: + * Private + */ +static int +be_get_snap(char *origin, char **snap) +{ + char *cp; + + /* + * Separate out the origin's dataset and snapshot portions by + * replacing the @ with a '\0' + */ + cp = strrchr(origin, '@'); + if (cp != NULL) { + if (cp[1] != NULL && cp[1] != '\0') { + cp[0] = '\0'; + *snap = cp+1; + } else { + return (1); + } + } else { + return (1); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_create_container_ds + * Description: This function checks that the zpool passed has the BE + * container dataset, and if not, then creates it. + * Parameters: + * zpool - name of pool to create BE container dataset in. + * Return: + * B_TRUE - Successfully created BE container dataset, or it + * already existed. + * B_FALSE - Failed to create container dataset. + * Scope: + * Private + */ +static boolean_t +be_create_container_ds(char *zpool) +{ + nvlist_t *props = NULL; + char be_container_ds[MAXPATHLEN]; + + /* Generate string for BE container dataset for this pool */ + be_make_container_ds(zpool, be_container_ds, + sizeof (be_container_ds)); + + if (!zfs_dataset_exists(g_zfs, be_container_ds, ZFS_TYPE_FILESYSTEM)) { + + if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) { + be_print_err(gettext("be_create_container_ds: " + "nvlist_alloc failed\n")); + return (B_FALSE); + } + + if (nvlist_add_string(props, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + ZFS_MOUNTPOINT_LEGACY) != 0) { + be_print_err(gettext("be_create_container_ds: " + "internal error: out of memory\n")); + nvlist_free(props); + return (B_FALSE); + } + + if (nvlist_add_string(props, + zfs_prop_to_name(ZFS_PROP_CANMOUNT), "off") != 0) { + be_print_err(gettext("be_create_container_ds: " + "internal error: out of memory\n")); + nvlist_free(props); + return (B_FALSE); + } + + if (zfs_create(g_zfs, be_container_ds, ZFS_TYPE_FILESYSTEM, + props) != 0) { + be_print_err(gettext("be_create_container_ds: " + "failed to create container dataset (%s): %s\n"), + be_container_ds, libzfs_error_description(g_zfs)); + nvlist_free(props); + return (B_FALSE); + } + + nvlist_free(props); + } + + return (B_TRUE); +} + +/* + * Function: be_prep_clone_send_fs + * Description: This function takes a zfs handle to a dataset from the + * original BE, and generates the name of the clone dataset + * to create for the new BE. It also prepares the zfs + * properties to be used for the new BE. + * Parameters: + * zhp - pointer to zfs_handle_t of the file system being + * cloned/copied. + * bt - be_transaction_data pointer providing information + * about the original BE and new BE. + * clone_ds - buffer to store the name of the dataset + * for the new BE. + * clone_ds_len - length of clone_ds buffer + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_prep_clone_send_fs(zfs_handle_t *zhp, be_transaction_data_t *bt, + char *clone_ds, int clone_ds_len) +{ + zprop_source_t sourcetype; + char source[ZFS_MAXNAMELEN]; + char zhp_name[ZFS_MAXNAMELEN]; + char mountpoint[MAXPATHLEN]; + char *child_fs = NULL; + char *zhp_mountpoint = NULL; + int err = 0; + + /* + * Get a copy of the dataset name zfs_name from zhp + */ + (void) strlcpy(zhp_name, zfs_get_name(zhp), sizeof (zhp_name)); + + /* + * Get file system name relative to the root. + */ + if (strncmp(zhp_name, bt->obe_root_ds, strlen(bt->obe_root_ds)) + == 0) { + child_fs = zhp_name + strlen(bt->obe_root_ds); + + /* + * if child_fs is NULL, this means we're processing the + * root dataset itself; set child_fs to the empty string. + */ + if (child_fs == NULL) + child_fs = ""; + } else { + return (BE_ERR_INVAL); + } + + /* + * Generate the name of the clone file system. + */ + (void) snprintf(clone_ds, clone_ds_len, "%s%s", bt->nbe_root_ds, + child_fs); + + /* Get the mountpoint and source properties of the existing dataset */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), &sourcetype, source, sizeof (source), + B_FALSE) != 0) { + be_print_err(gettext("be_prep_clone_send_fs: " + "failed to get mountpoint for (%s): %s\n"), + zhp_name, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Workaround for 6668667 where a mountpoint property of "/" comes + * back as "". + */ + if (strcmp(mountpoint, "") == 0) { + (void) snprintf(mountpoint, sizeof (mountpoint), "/"); + } + + /* + * Figure out what to set as the mountpoint for the new dataset. + * If the source of the mountpoint property is local, use the + * mountpoint value itself. Otherwise, remove it from the + * zfs properties list so that it gets inherited. + */ + if (sourcetype & ZPROP_SRC_LOCAL) { + /* + * If the BE that this file system is a part of is + * currently mounted, strip off the BE altroot portion + * from the mountpoint. + */ + zhp_mountpoint = mountpoint; + + if (strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) != 0 && + bt->obe_altroot != NULL && strcmp(bt->obe_altroot, + "/") != 0 && zfs_is_mounted(zhp, NULL)) { + + int altroot_len = strlen(bt->obe_altroot); + + if (strncmp(bt->obe_altroot, mountpoint, altroot_len) + == 0) { + if (mountpoint[altroot_len] == '/') + zhp_mountpoint = mountpoint + + altroot_len; + else if (mountpoint[altroot_len] == '\0') + (void) snprintf(mountpoint, + sizeof (mountpoint), "/"); + } + } + + if (nvlist_add_string(bt->nbe_zfs_props, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + zhp_mountpoint) != 0) { + be_print_err(gettext("be_prep_clone_send_fs: " + "internal error: out of memory\n")); + return (BE_ERR_NOMEM); + } + } else { + err = nvlist_remove_all(bt->nbe_zfs_props, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT)); + if (err != 0 && err != ENOENT) { + be_print_err(gettext("be_prep_clone_send_fs: " + "failed to remove mountpoint from " + "nvlist\n")); + return (BE_ERR_INVAL); + } + } + + /* + * Set the 'canmount' property + */ + if (nvlist_add_string(bt->nbe_zfs_props, + zfs_prop_to_name(ZFS_PROP_CANMOUNT), "noauto") != 0) { + be_print_err(gettext("be_prep_clone_send_fs: " + "internal error: out of memory\n")); + return (BE_ERR_NOMEM); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_get_zone_be_name + * Description: This function takes the zones root dataset, the container + * dataset and returns the zones BE name based on the zone + * root dataset. + * Parameters: + * root_ds - the zones root dataset. + * container_ds - the container dataset for the zone. + * Returns: + * char * - the BE name of this zone based on the root dataset. + */ +static char * +be_get_zone_be_name(char *root_ds, char *container_ds) +{ + return (root_ds + (strlen(container_ds) + 1)); +} + +/* + * Function: be_zone_root_exists_callback + * Description: This callback function is used to determine if a + * zone root container dataset has any children. It always + * returns 1, signifying a hierarchical child of the zone + * root container dataset has been traversed and therefore + * it has children. + * Parameters: + * zhp - zfs_handle_t pointer to current dataset being processed. + * data - not used. + * Returns: + * 1 - dataset exists + * Scope: + * Private + */ +static int +/* LINTED */ +be_zone_root_exists_callback(zfs_handle_t *zhp, void *data) +{ + ZFS_CLOSE(zhp); + return (1); +} diff --git a/usr/src/lib/libbe/common/be_list.c b/usr/src/lib/libbe/common/be_list.c new file mode 100644 index 0000000000..b5469f55b9 --- /dev/null +++ b/usr/src/lib/libbe/common/be_list.c @@ -0,0 +1,1122 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include <assert.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include <libbe.h> +#include <libbe_priv.h> + +/* + * Callback data used for zfs_iter calls. + */ +typedef struct list_callback_data { + char *zpool_name; + char *be_name; + be_node_list_t *be_nodes_head; + be_node_list_t *be_nodes; + char current_be[MAXPATHLEN]; +} list_callback_data_t; + +/* + * Private function prototypes + */ +static int be_add_children_callback(zfs_handle_t *zhp, void *data); +static int be_get_list_callback(zpool_handle_t *, void *); +static int be_get_node_data(zfs_handle_t *, be_node_list_t *, char *, + const char *, char *, char *); +static int be_get_zone_node_data(be_node_list_t *, char *); +static int be_get_ds_data(zfs_handle_t *, char *, be_dataset_list_t *, + be_node_list_t *); +static int be_get_ss_data(zfs_handle_t *, char *, be_snapshot_list_t *, + be_node_list_t *); +static void be_sort_list(be_node_list_t **); +static int be_qsort_compare_BEs(const void *, const void *); +static int be_qsort_compare_snapshots(const void *x, const void *y); +static int be_qsort_compare_datasets(const void *x, const void *y); +static void *be_list_alloc(int *, size_t); + +/* + * Private data. + */ +static char be_container_ds[MAXPATHLEN]; +static boolean_t zone_be = B_FALSE; + +/* ******************************************************************** */ +/* Public Functions */ +/* ******************************************************************** */ + +/* + * Function: be_list + * Description: Calls _be_list which finds all the BEs on the system and + * returns the datasets and snapshots belonging to each BE. + * Also data, such as dataset and snapshot properties, + * for each BE and their snapshots and datasets is + * returned. The data returned is as described in the + * be_dataset_list_t, be_snapshot_list_t and be_node_list_t + * structures. + * Parameters: + * be_name - The name of the BE to look up. + * If NULL a list of all BEs will be returned. + * be_nodes - A reference pointer to the list of BEs. The list + * structure will be allocated by _be_list and must + * be freed by a call to be_free_list. If there are no + * BEs found on the system this reference will be + * set to NULL. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_list(char *be_name, be_node_list_t **be_nodes) +{ + int ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Validate be_name if its not NULL */ + if (be_name != NULL) { + if (!be_valid_be_name(be_name)) { + be_print_err(gettext("be_list: " + "invalid BE name %s\n"), be_name); + return (BE_ERR_INVAL); + } + } + + ret = _be_list(be_name, be_nodes); + + be_zfs_fini(); + + return (ret); +} + +/* ******************************************************************** */ +/* Semi-Private Functions */ +/* ******************************************************************** */ + +/* + * Function: _be_list + * Description: This does the actual work described in be_list. + * Parameters: + * be_name - The name of the BE to look up. + * If NULL a list of all BEs will be returned. + * be_nodes - A reference pointer to the list of BEs. The list + * structure will be allocated here and must + * be freed by a call to be_free_list. If there are no + * BEs found on the system this reference will be + * set to NULL. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +_be_list(char *be_name, be_node_list_t **be_nodes) +{ + list_callback_data_t cb = { 0 }; + be_transaction_data_t bt = { 0 }; + int ret = BE_SUCCESS; + + if (be_nodes == NULL) + return (BE_ERR_INVAL); + + if (be_find_current_be(&bt) != BE_SUCCESS) { + /* + * We were unable to find a currently booted BE which + * probably means that we're not booted in a BE envoronment. + * None of the BE's will be marked as the active BE. + */ + (void) strcpy(cb.current_be, "-"); + } else { + (void) strncpy(cb.current_be, bt.obe_name, + sizeof (cb.current_be)); + } + + /* + * If be_name is NULL we'll look for all BE's on the system. + * If not then we will only return data for the specified BE. + */ + if (be_name != NULL) + cb.be_name = strdup(be_name); + + if ((zpool_iter(g_zfs, be_get_list_callback, &cb)) != 0) { + if (cb.be_nodes_head != NULL) { + be_free_list(cb.be_nodes_head); + cb.be_nodes_head = NULL; + cb.be_nodes = NULL; + } + ret = BE_ERR_BE_NOENT; + } + + if (cb.be_nodes_head == NULL) { + if (be_name != NULL) + be_print_err(gettext("be_list: BE (%s) does not " + "exist\n"), be_name); + else + be_print_err(gettext("be_list: No BE's found\n")); + ret = BE_ERR_BE_NOENT; + } + + *be_nodes = cb.be_nodes_head; + + free(cb.be_name); + + be_sort_list(be_nodes); + + return (ret); +} + +/* + * Function: be_free_list + * Description: Frees up all the data allocated for the list of BEs, + * datasets and snapshots returned by be_list. + * Parameters: + * be_node - be_nodes_t structure returned from call to be_list. + * Returns: + * none + * Scope: + * Semi-private (library wide use only) + */ +void +be_free_list(be_node_list_t *be_nodes) +{ + be_node_list_t *temp_node = NULL; + be_node_list_t *list = be_nodes; + + while (list != NULL) { + be_dataset_list_t *datasets = list->be_node_datasets; + be_snapshot_list_t *snapshots = list->be_node_snapshots; + + while (datasets != NULL) { + be_dataset_list_t *temp_ds = datasets; + datasets = datasets->be_next_dataset; + free(temp_ds->be_dataset_name); + free(temp_ds->be_ds_mntpt); + free(temp_ds->be_ds_plcy_type); + free(temp_ds); + } + + while (snapshots != NULL) { + be_snapshot_list_t *temp_ss = snapshots; + snapshots = snapshots->be_next_snapshot; + free(temp_ss->be_snapshot_name); + free(temp_ss->be_snapshot_type); + free(temp_ss); + } + + temp_node = list; + list = list->be_next_node; + free(temp_node->be_node_name); + free(temp_node->be_root_ds); + free(temp_node->be_rpool); + free(temp_node->be_mntpt); + free(temp_node->be_policy_type); + free(temp_node->be_uuid_str); + free(temp_node); + } +} + +/* + * Function: be_get_zone_be_list + * Description: Finds all the BEs for this zone on the system. + * Parameters: + * zone_be_name - The name of the BE to look up. + * zone_be_container_ds - The dataset for the zone. + * zbe_nodes - A reference pointer to the list of BEs. The list + * structure will be allocated here and must + * be freed by a call to be_free_list. If there are no + * BEs found on the system this reference will be + * set to NULL. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_get_zone_be_list( +/* LINTED */ + char *zone_be_name, + char *zone_be_container_ds, + be_node_list_t **zbe_nodes) +{ + zfs_handle_t *zhp = NULL; + list_callback_data_t cb = { 0 }; + int ret = BE_SUCCESS; + + if (zbe_nodes == NULL) + return (BE_ERR_INVAL); + + if (!zfs_dataset_exists(g_zfs, zone_be_container_ds, + ZFS_TYPE_FILESYSTEM)) { + return (BE_ERR_BE_NOENT); + } + + zone_be = B_TRUE; + + if ((zhp = zfs_open(g_zfs, zone_be_container_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_get_zone_be_list: failed to open " + "the zone BE dataset %s: %s\n"), zone_be_container_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto cleanup; + } + + (void) strcpy(be_container_ds, zone_be_container_ds); + + if (cb.be_nodes_head == NULL) { + if ((cb.be_nodes_head = be_list_alloc(&ret, + sizeof (be_node_list_t))) == NULL) { + ZFS_CLOSE(zhp); + goto cleanup; + } + cb.be_nodes = cb.be_nodes_head; + } + if (ret == 0) + ret = zfs_iter_filesystems(zhp, be_add_children_callback, &cb); + ZFS_CLOSE(zhp); + + *zbe_nodes = cb.be_nodes_head; + +cleanup: + zone_be = B_FALSE; + + return (ret); +} + +/* ******************************************************************** */ +/* Private Functions */ +/* ******************************************************************** */ + +/* + * Function: be_get_list_callback + * Description: Callback function used by zfs_iter to look through all + * the pools on the system looking for BEs. If a BE name was + * specified only that BE's information will be collected and + * returned. + * Parameters: + * zlp - handle to the first zfs dataset. (provided by the + * zfs_iter_* call) + * data - pointer to the callback data and where we'll pass + * the BE information back. + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_get_list_callback(zpool_handle_t *zlp, void *data) +{ + list_callback_data_t *cb = (list_callback_data_t *)data; + char be_ds[MAXPATHLEN]; + char *open_ds = NULL; + char *rpool = NULL; + zfs_handle_t *zhp = NULL; + int ret = 0; + + cb->zpool_name = rpool = (char *)zpool_get_name(zlp); + + /* + * Generate string for the BE container dataset + */ + be_make_container_ds(rpool, be_container_ds, + sizeof (be_container_ds)); + + /* + * If a BE name was specified we use it's root dataset in place of + * the container dataset. This is because we only want to collect + * the information for the specified BE. + */ + if (cb->be_name != NULL) { + /* + * Generate string for the BE root dataset + */ + be_make_root_ds(rpool, cb->be_name, be_ds, sizeof (be_ds)); + open_ds = be_ds; + } else { + open_ds = be_container_ds; + } + + /* + * Check if the dataset exists + */ + if (!zfs_dataset_exists(g_zfs, open_ds, + ZFS_TYPE_FILESYSTEM)) { + /* + * The specified dataset does not exist in this pool or + * there are no valid BE's in this pool. Try the next zpool. + */ + zpool_close(zlp); + return (0); + } + + if ((zhp = zfs_open(g_zfs, open_ds, ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_get_list_callback: failed to open " + "the BE dataset %s: %s\n"), open_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + zpool_close(zlp); + return (ret); + } + + if (cb->be_nodes_head == NULL) { + if ((cb->be_nodes_head = be_list_alloc(&ret, + sizeof (be_node_list_t))) == NULL) { + ZFS_CLOSE(zhp); + zpool_close(zlp); + return (ret); + } + cb->be_nodes = cb->be_nodes_head; + } + + /* + * If a BE name was specified we iterate through the datasets + * and snapshots for this BE only. Otherwise we will iterate + * through the next level of datasets to find all the BE's + * within the pool + */ + if (cb->be_name != NULL) { + if ((ret = be_get_node_data(zhp, cb->be_nodes, cb->be_name, + rpool, cb->current_be, be_ds)) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + zpool_close(zlp); + return (ret); + } + ret = zfs_iter_snapshots(zhp, be_add_children_callback, cb); + } + + if (ret == 0) + ret = zfs_iter_filesystems(zhp, be_add_children_callback, cb); + ZFS_CLOSE(zhp); + + zpool_close(zlp); + return (ret); +} + +/* + * Function: be_add_children_callback + * Description: Callback function used by zfs_iter to look through all + * the datasets and snapshots for each BE and add them to + * the lists of information to be passed back. + * Parameters: + * zhp - handle to the first zfs dataset. (provided by the + * zfs_iter_* call) + * data - pointer to the callback data and where we'll pass + * the BE information back. + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_add_children_callback(zfs_handle_t *zhp, void *data) +{ + list_callback_data_t *cb = (list_callback_data_t *)data; + char *str = NULL, *ds_path = NULL; + int ret = 0; + + ds_path = str = strdup(zfs_get_name(zhp)); + + /* + * get past the end of the container dataset plus the trailing "/" + */ + str = str + (strlen(be_container_ds) + 1); + if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT && !zone_be) { + be_snapshot_list_t *snapshots = NULL; + if (cb->be_nodes->be_node_snapshots == NULL) { + if ((cb->be_nodes->be_node_snapshots = + be_list_alloc(&ret, sizeof (be_snapshot_list_t))) + == NULL || ret != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + cb->be_nodes->be_node_snapshots->be_next_snapshot = + NULL; + snapshots = cb->be_nodes->be_node_snapshots; + } else { + for (snapshots = cb->be_nodes->be_node_snapshots; + snapshots != NULL; + snapshots = snapshots->be_next_snapshot) { + if (snapshots->be_next_snapshot != NULL) + continue; + /* + * We're at the end of the list add the + * new snapshot. + */ + if ((snapshots->be_next_snapshot = + be_list_alloc(&ret, + sizeof (be_snapshot_list_t))) == NULL || + ret != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + snapshots = snapshots->be_next_snapshot; + snapshots->be_next_snapshot = NULL; + break; + } + } + if ((ret = be_get_ss_data(zhp, str, snapshots, + cb->be_nodes)) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + } else if (strchr(str, '/') == NULL) { + if (cb->be_nodes->be_node_name != NULL) { + if ((cb->be_nodes->be_next_node = + be_list_alloc(&ret, sizeof (be_node_list_t))) == + NULL || ret != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + cb->be_nodes = cb->be_nodes->be_next_node; + cb->be_nodes->be_next_node = NULL; + } + + /* + * If this is a zone root dataset then we only need + * the name of the zone BE at this point. We grab that + * and return. + */ + if (zone_be) { + ret = be_get_zone_node_data(cb->be_nodes, str); + ZFS_CLOSE(zhp); + return (ret); + } + + if ((ret = be_get_node_data(zhp, cb->be_nodes, str, + cb->zpool_name, cb->current_be, ds_path)) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + } else if (strchr(str, '/') != NULL && !zone_be) { + be_dataset_list_t *datasets = NULL; + if (cb->be_nodes->be_node_datasets == NULL) { + if ((cb->be_nodes->be_node_datasets = + be_list_alloc(&ret, sizeof (be_dataset_list_t))) + == NULL || ret != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + cb->be_nodes->be_node_datasets->be_next_dataset = NULL; + datasets = cb->be_nodes->be_node_datasets; + } else { + for (datasets = cb->be_nodes->be_node_datasets; + datasets != NULL; + datasets = datasets->be_next_dataset) { + if (datasets->be_next_dataset != NULL) + continue; + /* + * We're at the end of the list add + * the new dataset. + */ + if ((datasets->be_next_dataset = + be_list_alloc(&ret, + sizeof (be_dataset_list_t))) + == NULL || ret != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + datasets = datasets->be_next_dataset; + datasets->be_next_dataset = NULL; + break; + } + } + + if ((ret = be_get_ds_data(zhp, str, + datasets, cb->be_nodes)) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + } + ret = zfs_iter_children(zhp, be_add_children_callback, cb); + if (ret != 0) { + be_print_err(gettext("be_add_children_callback: " + "encountered error: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + } + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_sort_list + * Description: Sort BE node list + * Parameters: + * pointer to address of list head + * Returns: + * nothing + * Side effect: + * node list sorted by name + * Scope: + * Private + */ +static void +be_sort_list(be_node_list_t **pstart) +{ + size_t ibe, nbe; + be_node_list_t *p = NULL; + be_node_list_t **ptrlist = NULL; + + if (pstart == NULL) + return; + /* build array of linked list BE struct pointers */ + for (p = *pstart, nbe = 0; p != NULL; nbe++, p = p->be_next_node) { + ptrlist = realloc(ptrlist, + sizeof (be_node_list_t *) * (nbe + 2)); + ptrlist[nbe] = p; + } + if (nbe == 0) + return; + /* in-place list quicksort using qsort(3C) */ + if (nbe > 1) /* no sort if less than 2 BEs */ + qsort(ptrlist, nbe, sizeof (be_node_list_t *), + be_qsort_compare_BEs); + + ptrlist[nbe] = NULL; /* add linked list terminator */ + *pstart = ptrlist[0]; /* set new linked list header */ + /* for each BE in list */ + for (ibe = 0; ibe < nbe; ibe++) { + size_t k, ns; /* subordinate index, count */ + + /* rewrite list pointer chain, including terminator */ + ptrlist[ibe]->be_next_node = ptrlist[ibe + 1]; + /* sort subordinate snapshots */ + if (ptrlist[ibe]->be_node_num_snapshots > 1) { + const size_t nmax = ptrlist[ibe]->be_node_num_snapshots; + be_snapshot_list_t ** const slist = + malloc(sizeof (be_snapshot_list_t *) * (nmax + 1)); + be_snapshot_list_t *p; + + if (slist == NULL) + continue; + /* build array of linked list snapshot struct ptrs */ + for (ns = 0, p = ptrlist[ibe]->be_node_snapshots; + ns < nmax && p != NULL; + ns++, p = p->be_next_snapshot) { + slist[ns] = p; + } + if (ns < 2) + goto end_snapshot; + slist[ns] = NULL; /* add terminator */ + /* in-place list quicksort using qsort(3C) */ + qsort(slist, ns, sizeof (be_snapshot_list_t *), + be_qsort_compare_snapshots); + /* rewrite list pointer chain, including terminator */ + ptrlist[ibe]->be_node_snapshots = slist[0]; + for (k = 0; k < ns; k++) + slist[k]->be_next_snapshot = slist[k + 1]; +end_snapshot: + free(slist); + } + /* sort subordinate datasets */ + if (ptrlist[ibe]->be_node_num_datasets > 1) { + const size_t nmax = ptrlist[ibe]->be_node_num_datasets; + be_dataset_list_t ** const slist = + malloc(sizeof (be_dataset_list_t *) * (nmax + 1)); + be_dataset_list_t *p; + + if (slist == NULL) + continue; + /* build array of linked list dataset struct ptrs */ + for (ns = 0, p = ptrlist[ibe]->be_node_datasets; + ns < nmax && p != NULL; + ns++, p = p->be_next_dataset) { + slist[ns] = p; + } + if (ns < 2) /* subordinate datasets < 2 - no sort */ + goto end_dataset; + slist[ns] = NULL; /* add terminator */ + /* in-place list quicksort using qsort(3C) */ + qsort(slist, ns, sizeof (be_dataset_list_t *), + be_qsort_compare_datasets); + /* rewrite list pointer chain, including terminator */ + ptrlist[ibe]->be_node_datasets = slist[0]; + for (k = 0; k < ns; k++) + slist[k]->be_next_dataset = slist[k + 1]; +end_dataset: + free(slist); + } + } +free: + free(ptrlist); +} + +/* + * Function: be_qsort_compare_BEs + * Description: lexical compare of BE names for qsort(3C) + * Parameters: + * x,y - BEs with names to compare + * Returns: + * positive if y>x, negative if x>y, 0 if equal + * Scope: + * Private + */ +static int +be_qsort_compare_BEs(const void *x, const void *y) +{ + be_node_list_t *p = *(be_node_list_t **)x; + be_node_list_t *q = *(be_node_list_t **)y; + + if (p == NULL || p->be_node_name == NULL) + return (1); + if (q == NULL || q->be_node_name == NULL) + return (-1); + return (strcmp(p->be_node_name, q->be_node_name)); +} + +/* + * Function: be_qsort_compare_snapshots + * Description: lexical compare of BE names for qsort(3C) + * Parameters: + * x,y - BE snapshots with names to compare + * Returns: + * positive if y>x, negative if x>y, 0 if equal + * Scope: + * Private + */ +static int +be_qsort_compare_snapshots(const void *x, const void *y) +{ + be_snapshot_list_t *p = *(be_snapshot_list_t **)x; + be_snapshot_list_t *q = *(be_snapshot_list_t **)y; + + if (p == NULL || p->be_snapshot_name == NULL) + return (1); + if (q == NULL || q->be_snapshot_name == NULL) + return (-1); + return (strcmp(p->be_snapshot_name, q->be_snapshot_name)); +} + +/* + * Function: be_qsort_compare_datasets + * Description: lexical compare of dataset names for qsort(3C) + * Parameters: + * x,y - BE snapshots with names to compare + * Returns: + * positive if y>x, negative if x>y, 0 if equal + * Scope: + * Private + */ +static int +be_qsort_compare_datasets(const void *x, const void *y) +{ + be_dataset_list_t *p = *(be_dataset_list_t **)x; + be_dataset_list_t *q = *(be_dataset_list_t **)y; + + if (p == NULL || p->be_dataset_name == NULL) + return (1); + if (q == NULL || q->be_dataset_name == NULL) + return (-1); + return (strcmp(p->be_dataset_name, q->be_dataset_name)); +} + +/* + * Function: be_get_node_data + * Description: Helper function used to collect all the information to fill + * in the be_node_list structure to be returned by be_list. + * Parameters: + * zhp - Handle to the root dataset for the BE whose information + * we're collecting. + * be_node - a pointer to the node structure we're filling in. + * be_name - The BE name of the node whose information we're + * collecting. + * current_be - the name of the currently active BE. + * be_ds - The dataset name for the BE. + * + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_get_node_data( + zfs_handle_t *zhp, + be_node_list_t *be_node, + char *be_name, + const char *rpool, + char *current_be, + char *be_ds) +{ + char prop_buf[MAXPATHLEN]; + nvlist_t *userprops = NULL; + nvlist_t *propval = NULL; + char *prop_str = NULL; + char *grub_default_bootfs = NULL; + zpool_handle_t *zphp = NULL; + int err = 0; + + if (be_node == NULL || be_name == NULL || current_be == NULL || + be_ds == NULL) { + be_print_err(gettext("be_get_node_data: invalid arguments, " + "can not be NULL\n")); + return (BE_ERR_INVAL); + } + + errno = 0; + + be_node->be_root_ds = strdup(be_ds); + if ((err = errno) != 0 || be_node->be_root_ds == NULL) { + be_print_err(gettext("be_get_node_data: failed to " + "copy root dataset name\n")); + return (errno_to_be_err(err)); + } + + be_node->be_node_name = strdup(be_name); + if ((err = errno) != 0 || be_node->be_node_name == NULL) { + be_print_err(gettext("be_get_node_data: failed to " + "copy BE name\n")); + return (errno_to_be_err(err)); + } + if (strncmp(be_name, current_be, MAXPATHLEN) == 0) + be_node->be_active = B_TRUE; + else + be_node->be_active = B_FALSE; + + be_node->be_rpool = strdup(rpool); + if (be_node->be_rpool == NULL || (err = errno) != 0) { + be_print_err(gettext("be_get_node_data: failed to " + "copy root pool name\n")); + return (errno_to_be_err(err)); + } + + be_node->be_space_used = zfs_prop_get_int(zhp, ZFS_PROP_USED); + + if ((zphp = zpool_open(g_zfs, rpool)) == NULL) { + be_print_err(gettext("be_get_node_data: failed to open pool " + "(%s): %s\n"), rpool, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + (void) zpool_get_prop(zphp, ZPOOL_PROP_BOOTFS, prop_buf, ZFS_MAXPROPLEN, + NULL); + if (be_has_grub() && + (be_default_grub_bootfs(rpool, &grub_default_bootfs) + == BE_SUCCESS) && grub_default_bootfs != NULL) + if (strcmp(grub_default_bootfs, be_ds) == 0) + be_node->be_active_on_boot = B_TRUE; + else + be_node->be_active_on_boot = B_FALSE; + else if (prop_buf != NULL && strcmp(prop_buf, be_ds) == 0) + be_node->be_active_on_boot = B_TRUE; + else + be_node->be_active_on_boot = B_FALSE; + free(grub_default_bootfs); + zpool_close(zphp); + + /* + * If the dataset is mounted use the mount point + * returned from the zfs_is_mounted call. If the + * dataset is not mounted then pull the mount + * point information out of the zfs properties. + */ + be_node->be_mounted = zfs_is_mounted(zhp, + &(be_node->be_mntpt)); + if (!be_node->be_mounted) { + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, prop_buf, + ZFS_MAXPROPLEN, NULL, NULL, 0, B_FALSE) == 0) + be_node->be_mntpt = strdup(prop_buf); + else + return (zfs_err_to_be_err(g_zfs)); + } + + be_node->be_node_creation = (time_t)zfs_prop_get_int(zhp, + ZFS_PROP_CREATION); + + /* Get all user properties used for libbe */ + if ((userprops = zfs_get_user_props(zhp)) == NULL) { + be_node->be_policy_type = strdup(be_default_policy()); + } else { + if (nvlist_lookup_nvlist(userprops, BE_POLICY_PROPERTY, + &propval) != 0 || propval == NULL) { + be_node->be_policy_type = + strdup(be_default_policy()); + } else { + verify(nvlist_lookup_string(propval, ZPROP_VALUE, + &prop_str) == 0); + if (prop_str == NULL || strcmp(prop_str, "-") == 0 || + strcmp(prop_str, "") == 0) + be_node->be_policy_type = + strdup(be_default_policy()); + else + be_node->be_policy_type = strdup(prop_str); + } + + if (nvlist_lookup_nvlist(userprops, BE_UUID_PROPERTY, &propval) + == 0 && nvlist_lookup_string(propval, ZPROP_VALUE, + &prop_str) == 0) { + be_node->be_uuid_str = strdup(prop_str); + } + } + + /* + * Increment the dataset counter to include the root dataset + * of the BE. + */ + be_node->be_node_num_datasets++; + + return (BE_SUCCESS); +} + +/* + * Function: be_get_ds_data + * Description: Helper function used by be_add_children_callback to collect + * the dataset related information that will be returned by + * be_list. + * Parameters: + * zhp - Handle to the zfs dataset whose information we're + * collecting. + * name - The name of the dataset we're processing. + * dataset - A pointer to the be_dataset_list structure + * we're filling in. + * node - The node structure that this dataset belongs to. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_get_ds_data( + zfs_handle_t *zfshp, + char *name, + be_dataset_list_t *dataset, + be_node_list_t *node) +{ + char prop_buf[ZFS_MAXPROPLEN]; + nvlist_t *propval = NULL; + nvlist_t *userprops = NULL; + char *prop_str = NULL; + int err = 0; + + if (zfshp == NULL || name == NULL || dataset == NULL || node == NULL) { + be_print_err(gettext("be_get_ds_data: invalid arguments, " + "can not be NULL\n")); + return (BE_ERR_INVAL); + } + + errno = 0; + + dataset->be_dataset_name = strdup(name); + if ((err = errno) != 0) { + be_print_err(gettext("be_get_ds_data: failed to copy " + "dataset name\n")); + return (errno_to_be_err(err)); + } + + dataset->be_ds_space_used = zfs_prop_get_int(zfshp, ZFS_PROP_USED); + + /* + * If the dataset is mounted use the mount point + * returned from the zfs_is_mounted call. If the + * dataset is not mounted then pull the mount + * point information out of the zfs properties. + */ + if (!(dataset->be_ds_mounted = zfs_is_mounted(zfshp, + &(dataset->be_ds_mntpt)))) { + if (zfs_prop_get(zfshp, ZFS_PROP_MOUNTPOINT, + prop_buf, ZFS_MAXPROPLEN, NULL, NULL, 0, + B_FALSE) == 0) + dataset->be_ds_mntpt = strdup(prop_buf); + else + return (zfs_err_to_be_err(g_zfs)); + } + dataset->be_ds_creation = + (time_t)zfs_prop_get_int(zfshp, ZFS_PROP_CREATION); + + /* + * Get the user property used for the libbe + * cleaup policy + */ + if ((userprops = zfs_get_user_props(zfshp)) == NULL) { + dataset->be_ds_plcy_type = + strdup(node->be_policy_type); + } else { + if (nvlist_lookup_nvlist(userprops, + BE_POLICY_PROPERTY, &propval) != 0 || + propval == NULL) { + dataset->be_ds_plcy_type = + strdup(node->be_policy_type); + } else { + verify(nvlist_lookup_string(propval, + ZPROP_VALUE, &prop_str) == 0); + if (prop_str == NULL || + strcmp(prop_str, "-") == 0 || + strcmp(prop_str, "") == 0) + dataset->be_ds_plcy_type + = strdup(node->be_policy_type); + else + dataset->be_ds_plcy_type = strdup(prop_str); + } + } + + node->be_node_num_datasets++; + return (BE_SUCCESS); +} + +/* + * Function: be_get_ss_data + * Description: Helper function used by be_add_children_callback to collect + * the dataset related information that will be returned by + * be_list. + * Parameters: + * zhp - Handle to the zfs snapshot whose information we're + * collecting. + * name - The name of the snapshot we're processing. + * shapshot - A pointer to the be_snapshot_list structure + * we're filling in. + * node - The node structure that this snapshot belongs to. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_get_ss_data( + zfs_handle_t *zfshp, + char *name, + be_snapshot_list_t *snapshot, + be_node_list_t *node) +{ + nvlist_t *propval = NULL; + nvlist_t *userprops = NULL; + char *prop_str = NULL; + int err = 0; + + if (zfshp == NULL || name == NULL || snapshot == NULL || node == NULL) { + be_print_err(gettext("be_get_ss_data: invalid arguments, " + "can not be NULL\n")); + return (BE_ERR_INVAL); + } + + errno = 0; + + snapshot->be_snapshot_name = strdup(name); + if ((err = errno) != 0) { + be_print_err(gettext("be_get_ss_data: failed to copy name\n")); + return (errno_to_be_err(err)); + } + + snapshot->be_snapshot_creation = (time_t)zfs_prop_get_int(zfshp, + ZFS_PROP_CREATION); + + /* + * Try to get this snapshot's cleanup policy from its + * user properties first. If not there, use default + * cleanup policy. + */ + if ((userprops = zfs_get_user_props(zfshp)) != NULL && + nvlist_lookup_nvlist(userprops, BE_POLICY_PROPERTY, + &propval) == 0 && nvlist_lookup_string(propval, + ZPROP_VALUE, &prop_str) == 0) { + snapshot->be_snapshot_type = + strdup(prop_str); + } else { + snapshot->be_snapshot_type = + strdup(be_default_policy()); + } + + snapshot->be_snapshot_space_used = zfs_prop_get_int(zfshp, + ZFS_PROP_USED); + + node->be_node_num_snapshots++; + return (BE_SUCCESS); +} + +/* + * Function: be_list_alloc + * Description: Helper function used to allocate memory for the various + * sructures that make up a BE node. + * Parameters: + * err - Used to return any errors encountered. + * BE_SUCCESS - Success + * BE_ERR_NOMEM - Allocation failure + * size - The size of memory to allocate. + * Returns: + * Success - A pointer to the allocated memory + * Failure - NULL + * Scope: + * Private + */ +static void* +be_list_alloc(int *err, size_t size) +{ + void *bep = NULL; + + bep = calloc(1, size); + if (bep == NULL) { + be_print_err(gettext("be_list_alloc: memory " + "allocation failed\n")); + *err = BE_ERR_NOMEM; + } + *err = BE_SUCCESS; + return (bep); +} + +/* + * Function: be_get_zone_node_data + * Description: Helper function used to collect all the information to + * fill in the be_node_list structure to be returned by + * be_get_zone_list. + * Parameters: + * be_node - a pointer to the node structure we're filling in. + * be_name - The BE name of the node whose information we're + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + * + * NOTE: This function currently only collects the zone BE name but when + * support for beadm/libbe in a zone is provided it will need to fill + * in the rest of the information needed for a zone BE. + */ +static int +be_get_zone_node_data(be_node_list_t *be_node, char *be_name) +{ + if ((be_node->be_node_name = strdup(be_name)) != NULL) + return (BE_SUCCESS); + return (BE_ERR_NOMEM); +} diff --git a/usr/src/lib/libbe/common/be_mount.c b/usr/src/lib/libbe/common/be_mount.c new file mode 100644 index 0000000000..bacb5d4ab4 --- /dev/null +++ b/usr/src/lib/libbe/common/be_mount.c @@ -0,0 +1,2684 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * System includes + */ +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mntent.h> +#include <sys/mnttab.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/vfstab.h> +#include <sys/zone.h> +#include <sys/mkdev.h> +#include <unistd.h> + +#include <libbe.h> +#include <libbe_priv.h> + +#define BE_TMP_MNTPNT "/tmp/.be.XXXXXX" + +typedef struct dir_data { + char *dir; + char *ds; +} dir_data_t; + +/* Private function prototypes */ +static int be_mount_callback(zfs_handle_t *, void *); +static int be_unmount_callback(zfs_handle_t *, void *); +static int be_get_legacy_fs_callback(zfs_handle_t *, void *); +static int fix_mountpoint(zfs_handle_t *); +static int fix_mountpoint_callback(zfs_handle_t *, void *); +static int get_mountpoint_from_vfstab(char *, const char *, char *, size_t, + boolean_t); +static int loopback_mount_shared_fs(zfs_handle_t *, be_mount_data_t *); +static int loopback_mount_zonepath(const char *, be_mount_data_t *); +static int iter_shared_fs_callback(zfs_handle_t *, void *); +static int zpool_shared_fs_callback(zpool_handle_t *, void *); +static int unmount_shared_fs(be_unmount_data_t *); +static int add_to_fs_list(be_fs_list_data_t *, const char *); +static int be_mount_root(zfs_handle_t *, char *); +static int be_unmount_root(zfs_handle_t *, be_unmount_data_t *); +static int be_mount_zones(zfs_handle_t *, be_mount_data_t *); +static int be_unmount_zones(be_unmount_data_t *); +static int be_mount_one_zone(zfs_handle_t *, be_mount_data_t *, char *, char *, + char *); +static int be_unmount_one_zone(be_unmount_data_t *, char *, char *, char *); +static int be_get_ds_from_dir_callback(zfs_handle_t *, void *); + + +/* ******************************************************************** */ +/* Public Functions */ +/* ******************************************************************** */ + +/* + * Function: be_mount + * Description: Mounts a BE and its subordinate datasets at a given mountpoint. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attributes are used by this function: + * + * BE_ATTR_ORIG_BE_NAME *required + * BE_ATTR_MOUNTPOINT *required + * BE_ATTR_MOUNT_FLAGS *optional + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_mount(nvlist_t *be_attrs) +{ + char *be_name = NULL; + char *mountpoint = NULL; + uint16_t flags = 0; + int ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get original BE name */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_ORIG_BE_NAME, &be_name) + != 0) { + be_print_err(gettext("be_mount: failed to lookup " + "BE_ATTR_ORIG_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* Validate original BE name */ + if (!be_valid_be_name(be_name)) { + be_print_err(gettext("be_mount: invalid BE name %s\n"), + be_name); + return (BE_ERR_INVAL); + } + + /* Get mountpoint */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_MOUNTPOINT, &mountpoint) + != 0) { + be_print_err(gettext("be_mount: failed to lookup " + "BE_ATTR_MOUNTPOINT attribute\n")); + return (BE_ERR_INVAL); + } + + /* Get flags */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_MOUNT_FLAGS, DATA_TYPE_UINT16, &flags, NULL) != 0) { + be_print_err(gettext("be_mount: failed to lookup " + "BE_ATTR_MOUNT_FLAGS attribute\n")); + return (BE_ERR_INVAL); + } + + ret = _be_mount(be_name, &mountpoint, flags); + + be_zfs_fini(); + + return (ret); +} + +/* + * Function: be_unmount + * Description: Unmounts a BE and its subordinate datasets. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attributes are used by this function: + * + * BE_ATTR_ORIG_BE_NAME *required + * BE_ATTR_UNMOUNT_FLAGS *optional + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_unmount(nvlist_t *be_attrs) +{ + char *be_name = NULL; + uint16_t flags = 0; + int ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get original BE name */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_ORIG_BE_NAME, &be_name) + != 0) { + be_print_err(gettext("be_unmount: failed to lookup " + "BE_ATTR_ORIG_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* Validate original BE name */ + if (!be_valid_be_name(be_name)) { + be_print_err(gettext("be_unmount: invalid BE name %s\n"), + be_name); + return (BE_ERR_INVAL); + } + + /* Get unmount flags */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_UNMOUNT_FLAGS, DATA_TYPE_UINT16, &flags, NULL) != 0) { + be_print_err(gettext("be_unmount: failed to loookup " + "BE_ATTR_UNMOUNT_FLAGS attribute\n")); + return (BE_ERR_INVAL); + } + + ret = _be_unmount(be_name, flags); + + be_zfs_fini(); + + return (ret); +} + +/* ******************************************************************** */ +/* Semi-Private Functions */ +/* ******************************************************************** */ + +/* + * Function: _be_mount + * Description: Mounts a BE. If the altroot is not provided, this function + * will generate a temporary mountpoint to mount the BE at. It + * will return this temporary mountpoint to the caller via the + * altroot reference pointer passed in. This returned value is + * allocated on heap storage and is the repsonsibility of the + * caller to free. + * Parameters: + * be_name - pointer to name of BE to mount. + * altroot - reference pointer to altroot of where to mount BE. + * flags - flag indicating special handling for mounting the BE + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +_be_mount(char *be_name, char **altroot, int flags) +{ + be_transaction_data_t bt = { 0 }; + be_mount_data_t md = { 0 }; + zfs_handle_t *zhp; + char obe_root_ds[MAXPATHLEN]; + char *mp = NULL; + char *tmp_altroot = NULL; + int ret = BE_SUCCESS, err = 0; + uuid_t uu = { 0 }; + boolean_t gen_tmp_altroot = B_FALSE; + + if (be_name == NULL || altroot == NULL) + return (BE_ERR_INVAL); + + /* Set be_name as obe_name in bt structure */ + bt.obe_name = be_name; + + /* Find which zpool obe_name lives in */ + if ((err = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_mount: failed to " + "find zpool for BE (%s)\n"), bt.obe_name); + return (BE_ERR_BE_NOENT); + } else if (err < 0) { + be_print_err(gettext("be_mount: zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Generate string for obe_name's root dataset */ + be_make_root_ds(bt.obe_zpool, bt.obe_name, obe_root_ds, + sizeof (obe_root_ds)); + bt.obe_root_ds = obe_root_ds; + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_mount: failed to " + "open BE root dataset (%s): %s\n"), bt.obe_root_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Make sure BE's root dataset isn't already mounted somewhere */ + if (zfs_is_mounted(zhp, &mp)) { + ZFS_CLOSE(zhp); + be_print_err(gettext("be_mount: %s is already mounted " + "at %s\n"), bt.obe_name, mp != NULL ? mp : ""); + free(mp); + return (BE_ERR_MOUNTED); + } + + /* + * Fix this BE's mountpoint if its root dataset isn't set to + * either 'legacy' or '/'. + */ + if ((ret = fix_mountpoint(zhp)) != BE_SUCCESS) { + be_print_err(gettext("be_mount: mountpoint check " + "failed for %s\n"), bt.obe_root_ds); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * If altroot not provided, create a temporary alternate root + * to mount on + */ + if (*altroot == NULL) { + if ((ret = be_make_tmp_mountpoint(&tmp_altroot)) + != BE_SUCCESS) { + be_print_err(gettext("be_mount: failed to " + "make temporary mountpoint\n")); + ZFS_CLOSE(zhp); + return (ret); + } + gen_tmp_altroot = B_TRUE; + } else { + tmp_altroot = *altroot; + } + + /* Mount the BE's root file system */ + if ((ret = be_mount_root(zhp, tmp_altroot)) != BE_SUCCESS) { + be_print_err(gettext("be_mount: failed to " + "mount BE root file system\n")); + if (gen_tmp_altroot) + free(tmp_altroot); + ZFS_CLOSE(zhp); + return (ret); + } + + /* Iterate through BE's children filesystems */ + if ((err = zfs_iter_filesystems(zhp, be_mount_callback, + tmp_altroot)) != 0) { + be_print_err(gettext("be_mount: failed to " + "mount BE (%s) on %s\n"), bt.obe_name, tmp_altroot); + if (gen_tmp_altroot) + free(tmp_altroot); + ZFS_CLOSE(zhp); + return (err); + } + + md.altroot = tmp_altroot; + md.shared_fs = flags & BE_MOUNT_FLAG_SHARED_FS; + md.shared_rw = flags & BE_MOUNT_FLAG_SHARED_RW; + + /* + * Mount shared file systems if mount flag says so. + */ + if (md.shared_fs) { + /* + * Mount all ZFS file systems not under the BE's root dataset + */ + (void) zpool_iter(g_zfs, zpool_shared_fs_callback, &md); + + /* TODO: Mount all non-ZFS file systems - Not supported yet */ + } + + /* + * If we're in the global zone and the global zone has a valid uuid, + * mount all supported non-global zones. + */ + if (getzoneid() == GLOBAL_ZONEID && + !(flags & BE_MOUNT_FLAG_NO_ZONES) && + be_get_uuid(bt.obe_root_ds, &uu) == BE_SUCCESS) { + if ((ret = be_mount_zones(zhp, &md)) != BE_SUCCESS) { + (void) _be_unmount(bt.obe_name, 0); + if (gen_tmp_altroot) + free(tmp_altroot); + ZFS_CLOSE(zhp); + return (ret); + } + } + + ZFS_CLOSE(zhp); + + /* + * If a NULL altroot was passed in, pass the generated altroot + * back to the caller in altroot. + */ + if (gen_tmp_altroot) + *altroot = tmp_altroot; + + return (BE_SUCCESS); +} + +/* + * Function: _be_unmount + * Description: Unmount a BE. + * Parameters: + * be_name - pointer to name of BE to unmount. + * flags - flags for unmounting the BE. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +_be_unmount(char *be_name, int flags) +{ + be_transaction_data_t bt = { 0 }; + be_unmount_data_t ud = { 0 }; + zfs_handle_t *zhp; + uuid_t uu = { 0 }; + char obe_root_ds[MAXPATHLEN]; + char mountpoint[MAXPATHLEN]; + char *mp = NULL; + int ret = BE_SUCCESS; + int zret = 0; + + if (be_name == NULL) + return (BE_ERR_INVAL); + + /* Set be_name as obe_name in bt structure */ + bt.obe_name = be_name; + + /* Find which zpool obe_name lives in */ + if ((zret = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_unmount: failed to " + "find zpool for BE (%s)\n"), bt.obe_name); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_unmount: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + return (ret); + } + + /* Generate string for obe_name's root dataset */ + be_make_root_ds(bt.obe_zpool, bt.obe_name, obe_root_ds, + sizeof (obe_root_ds)); + bt.obe_root_ds = obe_root_ds; + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_unmount: failed to " + "open BE root dataset (%s): %s\n"), bt.obe_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + return (ret); + } + + /* Make sure BE's root dataset is mounted somewhere */ + if (!zfs_is_mounted(zhp, &mp)) { + + be_print_err(gettext("be_unmount: " + "(%s) not mounted\n"), bt.obe_name); + + /* + * BE is not mounted, fix this BE's mountpoint if its root + * dataset isn't set to either 'legacy' or '/'. + */ + if ((ret = fix_mountpoint(zhp)) != BE_SUCCESS) { + be_print_err(gettext("be_unmount: mountpoint check " + "failed for %s\n"), bt.obe_root_ds); + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + return (BE_SUCCESS); + } + + /* + * If we didn't get a mountpoint from the zfs_is_mounted call, + * try and get it from its property. + */ + if (mp == NULL) { + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_unmount: failed to " + "get mountpoint of (%s)\n"), bt.obe_name); + ZFS_CLOSE(zhp); + return (BE_ERR_ZFS); + } + } else { + (void) strlcpy(mountpoint, mp, sizeof (mountpoint)); + free(mp); + } + + /* If BE mounted as current root, fail */ + if (strcmp(mountpoint, "/") == 0) { + be_print_err(gettext("be_unmount: " + "cannot unmount currently running BE\n")); + ZFS_CLOSE(zhp); + return (BE_ERR_UMOUNT_CURR_BE); + } + + ud.altroot = mountpoint; + ud.force = flags & BE_UNMOUNT_FLAG_FORCE; + + /* Unmount all supported non-global zones if we're in the global zone */ + if (getzoneid() == GLOBAL_ZONEID && + be_get_uuid(bt.obe_root_ds, &uu) == BE_SUCCESS) { + if ((ret = be_unmount_zones(&ud)) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + } + + /* TODO: Unmount all non-ZFS file systems - Not supported yet */ + + /* Unmount all ZFS file systems not under the BE root dataset */ + if ((ret = unmount_shared_fs(&ud)) != BE_SUCCESS) { + be_print_err(gettext("be_unmount: failed to " + "unmount shared file systems\n")); + ZFS_CLOSE(zhp); + return (ret); + } + + /* Unmount all children datasets under the BE's root dataset */ + if ((zret = zfs_iter_filesystems(zhp, be_unmount_callback, + &ud)) != 0) { + be_print_err(gettext("be_unmount: failed to " + "unmount BE (%s)\n"), bt.obe_name); + ZFS_CLOSE(zhp); + return (zret); + } + + /* Unmount this BE's root filesystem */ + if ((ret = be_unmount_root(zhp, &ud)) != BE_SUCCESS) { + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + + return (BE_SUCCESS); +} + +/* + * Function: be_mount_zone_root + * Description: Mounts the zone root dataset for a zone. + * Parameters: + * zfs - zfs_handle_t pointer to zone root dataset + * md - be_mount_data_t pointer to data for zone to be mounted + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_mount_zone_root(zfs_handle_t *zhp, be_mount_data_t *md) +{ + char mountpoint[MAXPATHLEN]; + int err = 0; + + /* Get mountpoint property of dataset */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_mount_zone_root: failed to " + "get mountpoint property for %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Make sure zone's root dataset is set to 'legacy'. This is + * currently a requirement in this implementation of zones + * support. + */ + if (strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) != 0) { + be_print_err(gettext("be_mount_zone_root: " + "zone root dataset mountpoint is not 'legacy'\n")); + return (BE_ERR_ZONE_ROOT_NOT_LEGACY); + } + + /* + * Legacy mount the zone root dataset. + * + * As a workaround for 6176743, we mount the zone's root with the + * MS_OVERLAY option in case an alternate BE is mounted, and we're + * mounting the root for the zone from the current BE here. When an + * alternate BE is mounted, it ties up the zone's zoneroot directory + * for the current BE since the zone's zonepath is loopback mounted + * from the current BE. + * + * TODO: The MS_OVERLAY option needs to be removed when 6176743 + * is fixed. + */ + if (mount(zfs_get_name(zhp), md->altroot, MS_OVERLAY, MNTTYPE_ZFS, + NULL, 0, NULL, 0) != 0) { + err = errno; + be_print_err(gettext("be_mount_zone_root: failed to " + "legacy mount zone root dataset (%s) at %s\n"), + zfs_get_name(zhp), md->altroot); + return (errno_to_be_err(err)); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_unmount_zone_root + * Description: Unmounts the zone root dataset for a zone. + * Parameters: + * zhp - zfs_handle_t pointer to zone root dataset + * ud - be_unmount_data_t pointer to data for zone to be unmounted + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wise use only) + */ +int +be_unmount_zone_root(zfs_handle_t *zhp, be_unmount_data_t *ud) +{ + char mountpoint[MAXPATHLEN]; + + /* Unmount the dataset */ + if (zfs_unmount(zhp, NULL, ud->force ? MS_FORCE : 0) != 0) { + be_print_err(gettext("be_unmount_zone_root: failed to " + "unmount zone root dataset %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Get the current mountpoint property for the zone root dataset */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_unmount_zone_root: failed to " + "get mountpoint property for zone root dataset (%s): %s\n"), + zfs_get_name(zhp), libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* If mountpoint not already set to 'legacy', set it to 'legacy' */ + if (strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) != 0) { + if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + ZFS_MOUNTPOINT_LEGACY) != 0) { + be_print_err(gettext("be_unmount_zone_root: " + "failed to set mountpoint of zone root dataset " + "%s to 'legacy': %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + } + + return (BE_SUCCESS); +} + +/* + * Function: be_get_legacy_fs + * Description: This function iterates through all non-shared file systems + * of a BE and finds the ones with a legacy mountpoint. For + * those file systems, it reads the BE's vfstab to get the + * mountpoint. If found, it adds that file system to the + * be_fs_list_data_t passed in. + * + * This function can be used to gather legacy mounted file systems + * for both global BEs and non-global zone BEs. To get data for + * a non-global zone BE, the zoneroot_ds and zoneroot parameters + * will be specified, otherwise they should be set to NULL. + * Parameters: + * be_name - global BE name from which to get legacy file + * system list. + * be_root_ds - root dataset of global BE. + * zoneroot_ds - root dataset of zone. + * zoneroot - zoneroot path of zone. + * fld - be_fs_list_data_t pointer. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_get_legacy_fs(char *be_name, char *be_root_ds, char *zoneroot_ds, + char *zoneroot, be_fs_list_data_t *fld) +{ + zfs_handle_t *zhp = NULL; + char mountpoint[MAXPATHLEN]; + boolean_t mounted_here = B_FALSE; + boolean_t zone_mounted_here = B_FALSE; + int ret = BE_SUCCESS, err = 0; + + if (be_name == NULL || be_root_ds == NULL || fld == NULL) + return (BE_ERR_INVAL); + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, be_root_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_get_legacy_fs: failed to " + "open BE root dataset (%s): %s\n"), be_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + return (ret); + } + + /* If BE is not already mounted, mount it. */ + if (!zfs_is_mounted(zhp, &fld->altroot)) { + if ((ret = _be_mount(be_name, &fld->altroot, + zoneroot_ds ? BE_MOUNT_FLAG_NULL : + BE_MOUNT_FLAG_NO_ZONES)) != BE_SUCCESS) { + be_print_err(gettext("be_get_legacy_fs: " + "failed to mount BE %s\n"), be_name); + goto cleanup; + } + + mounted_here = B_TRUE; + } else if (fld->altroot == NULL) { + be_print_err(gettext("be_get_legacy_fs: failed to " + "get altroot of mounted BE %s: %s\n"), + be_name, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto cleanup; + } + + /* + * If a zone root dataset was passed in, we're wanting to get + * legacy mounted file systems for that zone, not the global + * BE. + */ + if (zoneroot_ds != NULL) { + be_mount_data_t zone_md = { 0 }; + + /* Close off handle to global BE's root dataset */ + ZFS_CLOSE(zhp); + + /* Get handle to zone's root dataset */ + if ((zhp = zfs_open(g_zfs, zoneroot_ds, + ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_get_legacy_fs: failed to " + "open zone BE root dataset (%s): %s\n"), + zoneroot_ds, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto cleanup; + } + + /* Make sure the zone we're looking for is mounted */ + if (!zfs_is_mounted(zhp, &zone_md.altroot)) { + char zone_altroot[MAXPATHLEN]; + + /* Generate alternate root path for zone */ + (void) snprintf(zone_altroot, sizeof (zone_altroot), + "%s%s", fld->altroot, zoneroot); + if ((zone_md.altroot = strdup(zone_altroot)) == NULL) { + be_print_err(gettext("be_get_legacy_fs: " + "memory allocation failed\n")); + ret = BE_ERR_NOMEM; + goto cleanup; + } + + if ((ret = be_mount_zone_root(zhp, &zone_md)) + != BE_SUCCESS) { + be_print_err(gettext("be_get_legacy_fs: " + "failed to mount zone root %s\n"), + zoneroot_ds); + free(zone_md.altroot); + zone_md.altroot = NULL; + goto cleanup; + } + zone_mounted_here = B_TRUE; + } + + free(fld->altroot); + fld->altroot = zone_md.altroot; + } + + /* + * If the root dataset is in the vfstab with a mountpoint of "/", + * add it to the list + */ + if (get_mountpoint_from_vfstab(fld->altroot, zfs_get_name(zhp), + mountpoint, sizeof (mountpoint), B_FALSE) == BE_SUCCESS) { + if (strcmp(mountpoint, "/") == 0) { + if (add_to_fs_list(fld, zfs_get_name(zhp)) + != BE_SUCCESS) { + be_print_err(gettext("be_get_legacy_fs: " + "failed to add %s to fs list\n"), + zfs_get_name(zhp)); + ret = BE_ERR_INVAL; + goto cleanup; + } + } + } + + /* Iterate subordinate file systems looking for legacy mounts */ + if ((ret = zfs_iter_filesystems(zhp, be_get_legacy_fs_callback, + fld)) != 0) { + be_print_err(gettext("be_get_legacy_fs: " + "failed to iterate %s to get legacy mounts\n"), + zfs_get_name(zhp)); + } + +cleanup: + /* If we mounted the zone BE, unmount it */ + if (zone_mounted_here) { + be_unmount_data_t zone_ud = { 0 }; + + zone_ud.altroot = fld->altroot; + zone_ud.force = B_TRUE; + if ((err = be_unmount_zone_root(zhp, &zone_ud)) != BE_SUCCESS) { + be_print_err(gettext("be_get_legacy_fs: " + "failed to unmount zone root %s\n"), + zoneroot_ds); + if (ret == BE_SUCCESS) + ret = err; + } + } + + /* If we mounted this BE, unmount it */ + if (mounted_here) { + if ((err = _be_unmount(be_name, 0)) != BE_SUCCESS) { + be_print_err(gettext("be_get_legacy_fs: " + "failed to unmount %s\n"), be_name); + if (ret == BE_SUCCESS) + ret = err; + } + } + + ZFS_CLOSE(zhp); + + free(fld->altroot); + fld->altroot = NULL; + + return (ret); +} + +/* + * Function: be_free_fs_list + * Description: Function used to free the members of a be_fs_list_data_t + * structure. + * Parameters: + * fld - be_fs_list_data_t pointer to free. + * Returns: + * None + * Scope: + * Semi-private (library wide use only) + */ +void +be_free_fs_list(be_fs_list_data_t *fld) +{ + int i; + + if (fld == NULL) + return; + + free(fld->altroot); + + if (fld->fs_list == NULL) + return; + + for (i = 0; i < fld->fs_num; i++) + free(fld->fs_list[i]); + + free(fld->fs_list); +} + +/* + * Function: be_get_ds_from_dir(char *dir) + * Description: Given a directory path, find the underlying dataset mounted + * at that directory path if there is one. The returned name + * is allocated in heap storage, so the caller is responsible + * for freeing it. + * Parameters: + * dir - char pointer of directory to find. + * Returns: + * NULL - if directory is not mounted from a dataset. + * name of dataset mounted at dir. + * Scope: + * Semi-private (library wide use only) + */ +char * +be_get_ds_from_dir(char *dir) +{ + dir_data_t dd = { 0 }; + char resolved_dir[MAXPATHLEN]; + + /* Make sure length of dir is within the max length */ + if (dir == NULL || strlen(dir) >= MAXPATHLEN) + return (NULL); + + /* Resolve dir in case its lofs mounted */ + (void) strlcpy(resolved_dir, dir, sizeof (resolved_dir)); + z_resolve_lofs(resolved_dir, sizeof (resolved_dir)); + + dd.dir = resolved_dir; + + (void) zfs_iter_root(g_zfs, be_get_ds_from_dir_callback, &dd); + + return (dd.ds); +} + +/* + * Function: be_make_tmp_mountpoint + * Description: This function generates a random temporary mountpoint + * and creates that mountpoint directory. It returns the + * mountpoint in heap storage, so the caller is responsible + * for freeing it. + * Parameters: + * tmp_mp - reference to pointer of where to store generated + * temporary mountpoint. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_make_tmp_mountpoint(char **tmp_mp) +{ + int err = 0; + + if ((*tmp_mp = (char *)calloc(1, sizeof (BE_TMP_MNTPNT) + 1)) == NULL) { + be_print_err(gettext("be_make_tmp_mountpoint: " + "malloc failed\n")); + return (BE_ERR_NOMEM); + } + (void) strlcpy(*tmp_mp, BE_TMP_MNTPNT, sizeof (BE_TMP_MNTPNT) + 1); + if (mkdtemp(*tmp_mp) == NULL) { + err = errno; + be_print_err(gettext("be_make_tmp_mountpoint: mkdtemp() failed " + "for %s: %s\n"), *tmp_mp, strerror(err)); + free(*tmp_mp); + *tmp_mp = NULL; + return (errno_to_be_err(err)); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_mount_pool + * Description: This function determines if the pool's datase is mounted + * and if not it is used to mount the pool's dataset. The + * function returns the current mountpoint if we are able + * to mount the dataset. + * Parameters: + * zhp - handle to the pool's dataset + * tmp_mntpnt - The temporary mountpoint that the pool's + * dataset is mounted on. This is set only + * if the attempt to mount the dataset at it's + * set mountpoint fails, and we've used a + * temporary mount point for this dataset. It + * is expected that the caller will free this + * memory. + * orig_mntpnt - The original mountpoint for the pool. If a + * temporary mount point was needed this will + * be used to reset the mountpoint property to + * it's original mountpoint. It is expected that + * the caller will free this memory. + * pool_mounted - This flag indicates that the pool was mounted + * in this function. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_mount_pool( + zfs_handle_t *zhp, + char **tmp_mntpnt, + char **orig_mntpnt, + boolean_t *pool_mounted) +{ + + char mountpoint[MAXPATHLEN]; + int ret = 0; + + *tmp_mntpnt = NULL; + *orig_mntpnt = NULL; + *pool_mounted = B_FALSE; + + if (!zfs_is_mounted(zhp, NULL)) { + if (zfs_mount(zhp, NULL, 0) != 0) { + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_mount_pool: failed to " + "get mountpoint of (%s): %s\n"), + zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + if ((*orig_mntpnt = strdup(mountpoint)) == NULL) { + be_print_err(gettext("be_mount_pool: memory " + "allocation failed\n")); + return (BE_ERR_NOMEM); + } + /* + * attempt to mount on a temp mountpoint + */ + if ((ret = be_make_tmp_mountpoint(tmp_mntpnt)) + != BE_SUCCESS) { + be_print_err(gettext("be_mount_pool: failed " + "to make temporary mountpoint\n")); + free(*orig_mntpnt); + *orig_mntpnt = NULL; + return (ret); + } + + if (zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + *tmp_mntpnt) != 0) { + be_print_err(gettext("be_mount_pool: failed " + "to set mountpoint of pool dataset %s to " + "%s: %s\n"), zfs_get_name(zhp), + *orig_mntpnt, + libzfs_error_description(g_zfs)); + free(*tmp_mntpnt); + free(*orig_mntpnt); + *orig_mntpnt = NULL; + *tmp_mntpnt = NULL; + return (zfs_err_to_be_err(g_zfs)); + } + + if (zfs_mount(zhp, NULL, 0) != 0) { + be_print_err(gettext("be_mount_pool: failed " + "to mount dataset %s at %s: %s\n"), + zfs_get_name(zhp), *tmp_mntpnt, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + if (zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + mountpoint) != 0) { + be_print_err(gettext("be_mount_pool: " + "failed to set mountpoint of pool " + "dataset %s to %s: %s\n"), + zfs_get_name(zhp), *tmp_mntpnt, + libzfs_error_description(g_zfs)); + } + free(*tmp_mntpnt); + free(*orig_mntpnt); + *orig_mntpnt = NULL; + *tmp_mntpnt = NULL; + return (ret); + } + } + *pool_mounted = B_TRUE; + } + + return (BE_SUCCESS); +} + +/* + * Function: be_unmount_pool + * Description: This function is used to unmount the pool's dataset if we + * mounted it previously using be_mount_pool(). + * Parameters: + * zhp - handle to the pool's dataset + * tmp_mntpnt - If a temprary mount point was used this will + * be set. Since this was created in be_mount_pool + * we will need to clean it up here. + * orig_mntpnt - The original mountpoint for the pool. This is + * used to set the dataset mountpoint property + * back to it's original value in the case where a + * temporary mountpoint was used. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_unmount_pool( + zfs_handle_t *zhp, + char *tmp_mntpnt, + char *orig_mntpnt) +{ + if (zfs_unmount(zhp, NULL, 0) != 0) { + be_print_err(gettext("be_unmount_pool: failed to " + "unmount pool (%s): %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + if (orig_mntpnt != NULL) { + if (tmp_mntpnt != NULL && + strcmp(orig_mntpnt, tmp_mntpnt) != 0) { + (void) rmdir(tmp_mntpnt); + } + if (zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + orig_mntpnt) != 0) { + be_print_err(gettext("be_unmount_pool: failed " + "to set the mountpoint for dataset (%s) to " + "%s: %s\n"), zfs_get_name(zhp), orig_mntpnt, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + } + + return (BE_SUCCESS); +} + +/* ******************************************************************** */ +/* Private Functions */ +/* ******************************************************************** */ + +/* + * Function: be_mount_callback + * Description: Callback function used to iterate through all of a BE's + * subordinate file systems and to mount them accordingly. + * Parameters: + * zhp - zfs_handle_t pointer to current file system being + * processed. + * data - pointer to the altroot of where to mount BE. + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_mount_callback(zfs_handle_t *zhp, void *data) +{ + zprop_source_t sourcetype; + const char *fs_name = zfs_get_name(zhp); + char source[ZFS_MAXNAMELEN]; + char *altroot = data; + char zhp_mountpoint[MAXPATHLEN]; + char mountpoint[MAXPATHLEN]; + int ret = 0; + + /* Get dataset's mountpoint and source values */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, zhp_mountpoint, + sizeof (zhp_mountpoint), &sourcetype, source, sizeof (source), + B_FALSE) != 0) { + be_print_err(gettext("be_mount_callback: failed to " + "get mountpoint and sourcetype for %s\n"), + fs_name); + ZFS_CLOSE(zhp); + return (BE_ERR_ZFS); + } + + /* + * Set this filesystem's 'canmount' property to 'noauto' just incase + * it's been set 'on'. We do this so that when we change its + * mountpoint zfs won't immediately try to mount it. + */ + if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_CANMOUNT), "noauto")) { + be_print_err(gettext("be_mount_callback: failed to " + "set canmount to 'noauto' (%s)\n"), fs_name); + ZFS_CLOSE(zhp); + return (BE_ERR_ZFS); + } + + /* + * If the mountpoint is none, there's nothing to do, goto next. + * If the mountpoint is legacy, legacy mount it with mount(2). + * If the mountpoint is inherited, its mountpoint should + * already be set. If it's not, then explicitly fix-up + * the mountpoint now by appending its explicitly set + * mountpoint value to the BE mountpoint. + */ + if (strcmp(zhp_mountpoint, ZFS_MOUNTPOINT_NONE) == 0) { + goto next; + } else if (strcmp(zhp_mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0) { + /* + * If the mountpoint is set to 'legacy', we need to + * dig into this BE's vfstab to figure out where to + * mount it, and just mount it via mount(2). + */ + if (get_mountpoint_from_vfstab(altroot, fs_name, + mountpoint, sizeof (mountpoint), B_TRUE) == BE_SUCCESS) { + + /* Legacy mount the file system */ + if (mount(fs_name, mountpoint, MS_DATA, + MNTTYPE_ZFS, NULL, 0, NULL, 0) != 0) { + be_print_err( + gettext("be_mount_callback: " + "failed to mount %s on %s\n"), + fs_name, mountpoint); + } + } else { + be_print_err( + gettext("be_mount_callback: " + "no entry for %s in vfstab, " + "skipping ...\n"), fs_name); + } + + goto next; + + } else if (sourcetype & ZPROP_SRC_INHERITED) { + /* + * If the mountpoint is inherited, its parent should have + * already been processed so its current mountpoint value + * is what its mountpoint ought to be. + */ + (void) strlcpy(mountpoint, zhp_mountpoint, sizeof (mountpoint)); + } else if (sourcetype & ZPROP_SRC_LOCAL) { + /* + * Else process dataset with explicitly set mountpoint. + */ + (void) snprintf(mountpoint, sizeof (mountpoint), + "%s%s", altroot, zhp_mountpoint); + + /* Set the new mountpoint for the dataset */ + if (zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + mountpoint)) { + be_print_err(gettext("be_mount_callback: " + "failed to set mountpoint for %s to " + "%s\n"), fs_name, mountpoint); + ZFS_CLOSE(zhp); + return (BE_ERR_ZFS); + } + } else { + be_print_err(gettext("be_mount_callback: " + "mountpoint sourcetype of %s is %d, skipping ...\n"), + fs_name, sourcetype); + + goto next; + } + + /* Mount this filesystem */ + if (zfs_mount(zhp, NULL, 0) != 0) { + be_print_err(gettext("be_mount_callback: failed to " + "mount dataset %s at %s: %s\n"), fs_name, mountpoint, + libzfs_error_description(g_zfs)); + /* + * Set this filesystem's 'mountpoint' property back to what + * it was + */ + if (sourcetype & ZPROP_SRC_LOCAL && + strcmp(zhp_mountpoint, ZFS_MOUNTPOINT_LEGACY) != 0) { + (void) zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + zhp_mountpoint); + } + + ZFS_CLOSE(zhp); + return (BE_ERR_MOUNT); + } + +next: + /* Iterate through this dataset's children and mount them */ + if ((ret = zfs_iter_filesystems(zhp, be_mount_callback, + altroot)) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_unmount_callback + * Description: Callback function used to iterate through all of a BE's + * subordinate file systems and to unmount them. + * Parameters: + * zhp - zfs_handle_t pointer to current file system being + * processed. + * data - pointer to the mountpoint of where BE is mounted. + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_unmount_callback(zfs_handle_t *zhp, void *data) +{ + be_unmount_data_t *ud = data; + zprop_source_t sourcetype; + const char *fs_name = zfs_get_name(zhp); + char source[ZFS_MAXNAMELEN]; + char mountpoint[MAXPATHLEN]; + char *zhp_mountpoint; + int ret = 0; + + /* Iterate down this dataset's children first */ + if (zfs_iter_filesystems(zhp, be_unmount_callback, ud)) { + ret = BE_ERR_UMOUNT; + goto done; + } + + /* Is dataset even mounted ? */ + if (!zfs_is_mounted(zhp, NULL)) + goto done; + + /* Unmount this file system */ + if (zfs_unmount(zhp, NULL, ud->force ? MS_FORCE : 0) != 0) { + be_print_err(gettext("be_unmount_callback: " + "failed to unmount %s: %s\n"), fs_name, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Get dataset's current mountpoint and source value */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), &sourcetype, source, sizeof (source), + B_FALSE) != 0) { + be_print_err(gettext("be_unmount_callback: " + "failed to get mountpoint and sourcetype for %s: %s\n"), + fs_name, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (sourcetype & ZPROP_SRC_INHERITED) { + /* + * If the mountpoint is inherited we don't need to + * do anything. When its parent gets processed + * its mountpoint will be set accordingly. + */ + goto done; + } else if (sourcetype & ZPROP_SRC_LOCAL) { + + if (strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0) { + /* + * If the mountpoint is set to 'legacy', its already + * been unmounted (from above call to zfs_unmount), and + * we don't need to do anything else with it. + */ + goto done; + + } else { + /* + * Else process dataset with explicitly set mountpoint. + */ + + /* + * Get this dataset's mountpoint relative to + * the BE's mountpoint. + */ + if ((strncmp(mountpoint, ud->altroot, + strlen(ud->altroot)) == 0) && + (mountpoint[strlen(ud->altroot)] == '/')) { + + zhp_mountpoint = mountpoint + + strlen(ud->altroot); + + /* Set this dataset's mountpoint value */ + if (zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + zhp_mountpoint)) { + be_print_err( + gettext("be_unmount_callback: " + "failed to set mountpoint for " + "%s to %s: %s\n"), fs_name, + zhp_mountpoint, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + } + } else { + be_print_err( + gettext("be_unmount_callback: " + "%s not mounted under BE's altroot %s, " + "skipping ...\n"), fs_name, ud->altroot); + /* + * fs_name is mounted but not under the + * root for this BE. + */ + ret = BE_ERR_INVALMOUNTPOINT; + } + } + } else { + be_print_err(gettext("be_unmount_callback: " + "mountpoint sourcetype of %s is %d, skipping ...\n"), + fs_name, sourcetype); + ret = BE_ERR_ZFS; + } + +done: + /* Set this filesystem's 'canmount' property to 'noauto' */ + if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_CANMOUNT), "noauto")) { + be_print_err(gettext("be_unmount_callback: " + "failed to set canmount to 'noauto' (%s)\n"), fs_name); + if (ret == 0) + ret = BE_ERR_ZFS; + } + + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_get_legacy_fs_callback + * Description: The callback function is used to iterate through all + * non-shared file systems of a BE, finding ones that have + * a legacy mountpoint and an entry in the BE's vfstab. + * It adds these file systems to the callback data. + * Parameters: + * zhp - zfs_handle_t pointer to current file system being + * processed. + * data - be_fs_list_data_t pointer + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_get_legacy_fs_callback(zfs_handle_t *zhp, void *data) +{ + be_fs_list_data_t *fld = data; + const char *fs_name = zfs_get_name(zhp); + char zhp_mountpoint[MAXPATHLEN]; + char mountpoint[MAXPATHLEN]; + int ret = 0; + + /* Get this dataset's mountpoint property */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, zhp_mountpoint, + sizeof (zhp_mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_get_legacy_fs_callback: " + "failed to get mountpoint for %s: %s\n"), + fs_name, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * If mountpoint is legacy, try to get its mountpoint from this BE's + * vfstab. If it exists in the vfstab, add this file system to the + * callback data. + */ + if (strcmp(zhp_mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0) { + if (get_mountpoint_from_vfstab(fld->altroot, fs_name, + mountpoint, sizeof (mountpoint), B_FALSE) != BE_SUCCESS) { + be_print_err(gettext("be_get_legacy_fs_callback: " + "no entry for %s in vfstab, " + "skipping ...\n"), fs_name); + + goto next; + } + + /* Record file system into the callback data. */ + if (add_to_fs_list(fld, zfs_get_name(zhp)) != BE_SUCCESS) { + be_print_err(gettext("be_get_legacy_fs_callback: " + "failed to add %s to fs list\n"), mountpoint); + ZFS_CLOSE(zhp); + return (BE_ERR_NOMEM); + } + } + +next: + /* Iterate through this dataset's children file systems */ + if ((ret = zfs_iter_filesystems(zhp, be_get_legacy_fs_callback, + fld)) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: add_to_fs_list + * Description: Function used to add a file system to the fs_list array in + * a be_fs_list_data_t structure. + * Parameters: + * fld - be_fs_list_data_t pointer + * fs - file system to add + * Returns: + * BE_SUCCESS - Success + * 1 - Failure + * Scope: + * Private + */ +static int +add_to_fs_list(be_fs_list_data_t *fld, const char *fs) +{ + if (fld == NULL || fs == NULL) + return (1); + + if ((fld->fs_list = (char **)realloc(fld->fs_list, + sizeof (char *)*(fld->fs_num + 1))) == NULL) { + be_print_err(gettext("add_to_fs_list: " + "memory allocation failed\n")); + return (1); + } + + if ((fld->fs_list[fld->fs_num++] = strdup(fs)) == NULL) { + be_print_err(gettext("add_to_fs_list: " + "memory allocation failed\n")); + return (1); + } + + return (BE_SUCCESS); +} + +/* + * Function: zpool_shared_fs_callback + * Description: Callback function used to iterate through all existing pools + * to find and mount all shared filesystems. This function + * processes the pool's "pool data" dataset, then uses + * iter_shared_fs_callback to iterate through the pool's + * datasets. + * Parameters: + * zlp - zpool_handle_t pointer to the current pool being + * looked at. + * data - be_mount_data_t pointer + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +zpool_shared_fs_callback(zpool_handle_t *zlp, void *data) +{ + be_mount_data_t *md = data; + zfs_handle_t *zhp = NULL; + const char *zpool = zpool_get_name(zlp); + int ret = 0; + + /* + * Get handle to pool's "pool data" dataset + */ + if ((zhp = zfs_open(g_zfs, zpool, ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("zpool_shared_fs: " + "failed to open pool dataset %s: %s\n"), zpool, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + zpool_close(zlp); + return (ret); + } + + /* Process this pool's "pool data" dataset */ + (void) loopback_mount_shared_fs(zhp, md); + + /* Interate through this pool's children */ + (void) zfs_iter_filesystems(zhp, iter_shared_fs_callback, md); + + ZFS_CLOSE(zhp); + zpool_close(zlp); + + return (0); +} + +/* + * Function: iter_shared_fs_callback + * Description: Callback function used to iterate through a pool's datasets + * to find and mount all shared filesystems. It makes sure to + * find the BE container dataset of the pool, if it exists, and + * does not process and iterate down that path. + * + * Note - This function iterates linearly down the + * hierarchical dataset paths and mounts things as it goes + * along. It does not make sure that something deeper down + * a dataset path has an interim mountpoint for something + * processed earlier. + * + * Parameters: + * zhp - zfs_handle_t pointer to the current dataset being + * processed. + * data - be_mount_data_t pointer + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +iter_shared_fs_callback(zfs_handle_t *zhp, void *data) +{ + be_mount_data_t *md = data; + const char *name = zfs_get_name(zhp); + char container_ds[MAXPATHLEN]; + char tmp_name[MAXPATHLEN]; + char *pool; + + /* Get the pool's name */ + (void) strlcpy(tmp_name, name, sizeof (tmp_name)); + pool = strtok(tmp_name, "/"); + + if (pool) { + /* Get the name of this pool's container dataset */ + be_make_container_ds(pool, container_ds, + sizeof (container_ds)); + + /* + * If what we're processing is this pool's BE container + * dataset, skip it. + */ + if (strcmp(name, container_ds) == 0) { + ZFS_CLOSE(zhp); + return (0); + } + } else { + /* Getting the pool name failed, return error */ + be_print_err(gettext("iter_shared_fs_callback: " + "failed to get pool name from %s\n"), name); + ZFS_CLOSE(zhp); + return (BE_ERR_POOL_NOENT); + } + + /* Mount this shared filesystem */ + (void) loopback_mount_shared_fs(zhp, md); + + /* Iterate this dataset's children file systems */ + (void) zfs_iter_filesystems(zhp, iter_shared_fs_callback, md); + ZFS_CLOSE(zhp); + + return (0); +} + +/* + * Function: loopback_mount_shared_fs + * Description: This function loopback mounts a file system into the altroot + * area of the BE being mounted. Since these are shared file + * systems, they are expected to be already mounted for the + * current BE, and this function just loopback mounts them into + * the BE mountpoint. If they are not mounted for the current + * live system, they are skipped and not mounted into the BE + * we're mounting. + * Parameters: + * zhp - zfs_handle_t pointer to the dataset to loopback mount + * md - be_mount_data_t pointer + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +loopback_mount_shared_fs(zfs_handle_t *zhp, be_mount_data_t *md) +{ + char zhp_mountpoint[MAXPATHLEN]; + char mountpoint[MAXPATHLEN]; + char *mp = NULL; + char optstr[MAX_MNTOPT_STR]; + int mflag = MS_OPTIONSTR; + int err; + + /* + * Check if file system is currently mounted and not delegated + * to a non-global zone (if we're in the global zone) + */ + if (zfs_is_mounted(zhp, &mp) && (getzoneid() != GLOBAL_ZONEID || + !zfs_prop_get_int(zhp, ZFS_PROP_ZONED))) { + /* + * If we didn't get a mountpoint from the zfs_is_mounted call, + * get it from the mountpoint property. + */ + if (mp == NULL) { + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, + zhp_mountpoint, sizeof (zhp_mountpoint), NULL, + NULL, 0, B_FALSE) != 0) { + be_print_err( + gettext("loopback_mount_shared_fs: " + "failed to get mountpoint property\n")); + return (BE_ERR_ZFS); + } + } else { + (void) strlcpy(zhp_mountpoint, mp, + sizeof (zhp_mountpoint)); + free(mp); + } + + (void) snprintf(mountpoint, sizeof (mountpoint), "%s%s", + md->altroot, zhp_mountpoint); + + /* Mount it read-only if read-write was not requested */ + if (!md->shared_rw) { + mflag |= MS_RDONLY; + } + + /* Add the "nosub" option to the mount options string */ + (void) strlcpy(optstr, MNTOPT_NOSUB, sizeof (optstr)); + + /* Loopback mount this dataset at the altroot */ + if (mount(zhp_mountpoint, mountpoint, mflag, MNTTYPE_LOFS, + NULL, 0, optstr, sizeof (optstr)) != 0) { + err = errno; + be_print_err(gettext("loopback_mount_shared_fs: " + "failed to loopback mount %s at %s: %s\n"), + zhp_mountpoint, mountpoint, strerror(err)); + return (BE_ERR_MOUNT); + } + } + + return (BE_SUCCESS); +} + +/* + * Function: loopback_mount_zonepath + * Description: This function loopback mounts a zonepath into the altroot + * area of the BE being mounted. Since these are shared file + * systems, they are expected to be already mounted for the + * current BE, and this function just loopback mounts them into + * the BE mountpoint. + * Parameters: + * zonepath - pointer to zone path in the current BE + * md - be_mount_data_t pointer + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +loopback_mount_zonepath(const char *zonepath, be_mount_data_t *md) +{ + FILE *fp = (FILE *)NULL; + struct stat st; + char *p; + char *p1; + char *parent_dir; + struct extmnttab extmtab; + dev_t dev = NODEV; + char *parentmnt; + char alt_parentmnt[MAXPATHLEN]; + struct mnttab mntref; + char altzonepath[MAXPATHLEN]; + char optstr[MAX_MNTOPT_STR]; + int mflag = MS_OPTIONSTR; + int ret; + int err; + + fp = fopen(MNTTAB, "r"); + if (fp == NULL) { + err = errno; + be_print_err(gettext("loopback_mount_zonepath: " + "failed to open /etc/mnttab\n")); + return (errno_to_be_err(err)); + } + + /* + * before attempting the loopback mount of zonepath under altroot, + * we need to make sure that all intermediate file systems in the + * zone path are also mounted under altroot + */ + + /* get the parent directory for zonepath */ + p = strrchr(zonepath, '/'); + if (p != NULL && p != zonepath) { + if ((parent_dir = (char *)calloc(sizeof (char), + p - zonepath + 1)) == NULL) { + ret = BE_ERR_NOMEM; + goto done; + } + (void) strlcpy(parent_dir, zonepath, p - zonepath + 1); + if (stat(parent_dir, &st) < 0) { + ret = errno_to_be_err(errno); + be_print_err(gettext("loopback_mount_zonepath: " + "failed to stat %s"), + parent_dir); + free(parent_dir); + goto done; + } + free(parent_dir); + + /* + * After the above stat call, st.st_dev contains ID of the + * device over which parent dir resides. + * Now, search mnttab and find mount point of parent dir device. + */ + + resetmnttab(fp); + while (getextmntent(fp, &extmtab, sizeof (extmtab)) == 0) { + dev = makedev(extmtab.mnt_major, extmtab.mnt_minor); + if (st.st_dev == dev && strcmp(extmtab.mnt_fstype, + MNTTYPE_ZFS) == 0) { + p1 = strchr(extmtab.mnt_special, '/'); + if (p1 == NULL || strncmp(p1 + 1, + BE_CONTAINER_DS_NAME, 4) != 0 || + (*(p1 + 5) != '/' && *(p1 + 5) != '\0')) { + /* + * if parent dir is in a shared file + * system, check whether it is already + * loopback mounted under altroot or + * not. It would have been mounted + * already under altroot if it is in + * a non-shared filesystem. + */ + parentmnt = strdup(extmtab.mnt_mountp); + (void) snprintf(alt_parentmnt, + sizeof (alt_parentmnt), "%s%s", + md->altroot, parentmnt); + mntref.mnt_mountp = alt_parentmnt; + mntref.mnt_special = parentmnt; + mntref.mnt_fstype = MNTTYPE_LOFS; + mntref.mnt_mntopts = NULL; + mntref.mnt_time = NULL; + resetmnttab(fp); + if (getmntany(fp, (struct mnttab *) + &extmtab, &mntref) != 0) { + ret = loopback_mount_zonepath( + parentmnt, md); + if (ret != BE_SUCCESS) { + free(parentmnt); + goto done; + } + } + free(parentmnt); + } + break; + } + } + } + + + if (!md->shared_rw) { + mflag |= MS_RDONLY; + } + + (void) snprintf(altzonepath, sizeof (altzonepath), "%s%s", + md->altroot, zonepath); + + /* Add the "nosub" option to the mount options string */ + (void) strlcpy(optstr, MNTOPT_NOSUB, sizeof (optstr)); + + /* Loopback mount this dataset at the altroot */ + if (mount(zonepath, altzonepath, mflag, MNTTYPE_LOFS, + NULL, 0, optstr, sizeof (optstr)) != 0) { + err = errno; + be_print_err(gettext("loopback_mount_zonepath: " + "failed to loopback mount %s at %s: %s\n"), + zonepath, altzonepath, strerror(err)); + ret = BE_ERR_MOUNT; + goto done; + } + ret = BE_SUCCESS; + +done : + (void) fclose(fp); + return (ret); +} + +/* + * Function: unmount_shared_fs + * Description: This function iterates through the mnttab and finds all + * loopback mount entries that reside within the altroot of + * where the BE is mounted, and unmounts it. + * Parameters: + * ud - be_unmount_data_t pointer + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +unmount_shared_fs(be_unmount_data_t *ud) +{ + FILE *fp = NULL; + struct mnttab *table = NULL; + struct mnttab ent; + struct mnttab *entp = NULL; + size_t size = 0; + int read_chunk = 32; + int i; + int altroot_len; + int err = 0; + + errno = 0; + + /* Read in the mnttab into a table */ + if ((fp = fopen(MNTTAB, "r")) == NULL) { + err = errno; + be_print_err(gettext("unmount_shared_fs: " + "failed to open mnttab\n")); + return (errno_to_be_err(err)); + } + + while (getmntent(fp, &ent) == 0) { + if (size % read_chunk == 0) { + table = (struct mnttab *)realloc(table, + (size + read_chunk) * sizeof (ent)); + } + entp = &table[size++]; + + /* + * Copy over the current mnttab entry into our table, + * copying only the fields that we care about. + */ + (void) memset(entp, 0, sizeof (*entp)); + if ((entp->mnt_mountp = strdup(ent.mnt_mountp)) == NULL || + (entp->mnt_fstype = strdup(ent.mnt_fstype)) == NULL) { + be_print_err(gettext("unmount_shared_fs: " + "memory allocation failed\n")); + return (BE_ERR_NOMEM); + } + } + (void) fclose(fp); + + /* + * Process the mnttab entries in reverse order, looking for + * loopback mount entries mounted under our altroot. + */ + altroot_len = strlen(ud->altroot); + for (i = size; i > 0; i--) { + entp = &table[i - 1]; + + /* If not of type lofs, skip */ + if (strcmp(entp->mnt_fstype, MNTTYPE_LOFS) != 0) + continue; + + /* If inside the altroot, unmount it */ + if (strncmp(entp->mnt_mountp, ud->altroot, altroot_len) == 0 && + entp->mnt_mountp[altroot_len] == '/') { + if (umount(entp->mnt_mountp) != 0) { + err = errno; + if (err == EBUSY) { + (void) sleep(1); + err = errno = 0; + if (umount(entp->mnt_mountp) != 0) + err = errno; + } + if (err != 0) { + be_print_err(gettext( + "unmount_shared_fs: " + "failed to unmount shared file " + "system %s: %s\n"), + entp->mnt_mountp, strerror(err)); + return (errno_to_be_err(err)); + } + } + } + } + + return (BE_SUCCESS); +} + +/* + * Function: get_mountpoint_from_vfstab + * Description: This function digs into the vfstab in the given altroot, + * and searches for an entry for the fs passed in. If found, + * it returns the mountpoint of that fs in the mountpoint + * buffer passed in. If the get_alt_mountpoint flag is set, + * it returns the mountpoint with the altroot prepended. + * Parameters: + * altroot - pointer to the alternate root location + * fs - pointer to the file system name to look for in the + * vfstab in altroot + * mountpoint - pointer to buffer of where the mountpoint of + * fs will be returned. + * size_mp - size of mountpoint argument + * get_alt_mountpoint - flag to indicate whether or not the + * mountpoint should be populated with the altroot + * prepended. + * Returns: + * BE_SUCCESS - Success + * 1 - Failure + * Scope: + * Private + */ +static int +get_mountpoint_from_vfstab(char *altroot, const char *fs, char *mountpoint, + size_t size_mp, boolean_t get_alt_mountpoint) +{ + struct vfstab vp; + FILE *fp = NULL; + char alt_vfstab[MAXPATHLEN]; + + /* Generate path to alternate root vfstab */ + (void) snprintf(alt_vfstab, sizeof (alt_vfstab), "%s/etc/vfstab", + altroot); + + /* Open alternate root vfstab */ + if ((fp = fopen(alt_vfstab, "r")) == NULL) { + be_print_err(gettext("get_mountpoint_from_vfstab: " + "failed to open vfstab (%s)\n"), alt_vfstab); + return (1); + } + + if (getvfsspec(fp, &vp, (char *)fs) == 0) { + /* + * Found entry for fs, grab its mountpoint. + * If the flag to prepend the altroot into the mountpoint + * is set, prepend it. Otherwise, just return the mountpoint. + */ + if (get_alt_mountpoint) { + (void) snprintf(mountpoint, size_mp, "%s%s", altroot, + vp.vfs_mountp); + } else { + (void) strlcpy(mountpoint, vp.vfs_mountp, size_mp); + } + } else { + (void) fclose(fp); + return (1); + } + + (void) fclose(fp); + + return (BE_SUCCESS); +} + +/* + * Function: fix_mountpoint_callback + * Description: This callback function is used to iterate through a BE's + * children filesystems to check if its mountpoint is currently + * set to be mounted at some specified altroot. If so, fix it by + * removing altroot from the beginning of its mountpoint. + * + * Note - There's no way to tell if a child filesystem's + * mountpoint isn't broken, and just happens to begin with + * the altroot we're looking for. In this case, this function + * will errantly remove the altroot portion from the beginning + * of this filesystem's mountpoint. + * + * Parameters: + * zhp - zfs_handle_t pointer to filesystem being processed. + * data - altroot of where BE is to be mounted. + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +fix_mountpoint_callback(zfs_handle_t *zhp, void *data) +{ + zprop_source_t sourcetype; + char source[ZFS_MAXNAMELEN]; + char mountpoint[MAXPATHLEN]; + char *zhp_mountpoint = NULL; + char *altroot = data; + int ret = 0; + + /* Get dataset's mountpoint and source values */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), &sourcetype, source, sizeof (source), + B_FALSE) != 0) { + be_print_err(gettext("fix_mountpoint_callback: " + "failed to get mountpoint and sourcetype for %s\n"), + zfs_get_name(zhp)); + ZFS_CLOSE(zhp); + return (BE_ERR_ZFS); + } + + /* + * If the mountpoint is not inherited and the mountpoint is not + * 'legacy', this file system potentially needs its mountpoint + * fixed. + */ + if (!(sourcetype & ZPROP_SRC_INHERITED) && + strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) != 0) { + + /* + * Check if this file system's current mountpoint is + * under the altroot we're fixing it against. + */ + if (strncmp(mountpoint, altroot, strlen(altroot)) == 0 && + mountpoint[strlen(altroot)] == '/') { + + /* + * Get this dataset's mountpoint relative to the + * altroot. + */ + zhp_mountpoint = mountpoint + strlen(altroot); + + /* Fix this dataset's mountpoint value */ + if (zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + zhp_mountpoint)) { + be_print_err(gettext("fix_mountpoint_callback: " + "failed to set mountpoint for %s to " + "%s: %s\n"), zfs_get_name(zhp), + zhp_mountpoint, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + } + } + + /* Iterate through this dataset's children and fix them */ + if ((ret = zfs_iter_filesystems(zhp, fix_mountpoint_callback, + altroot)) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_mount_root + * Description: This function mounts the root dataset of a BE at the + * specified altroot. + * Parameters: + * zhp - zfs_handle_t pointer to root dataset of a BE that is + * to be mounted at altroot. + * altroot - location of where to mount the BE root. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_mount_root(zfs_handle_t *zhp, char *altroot) +{ + char mountpoint[MAXPATHLEN]; + + /* Get mountpoint property of dataset */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("be_mount_root: failed to " + "get mountpoint property for %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Set the canmount property for the BE's root dataset to 'noauto' just + * in case it's been set to 'on'. We do this so that when we change its + * mountpoint, zfs won't immediately try to mount it. + */ + if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_CANMOUNT), "noauto") + != 0) { + be_print_err(gettext("be_mount_root: failed to " + "set canmount property to 'noauto' (%s): %s\n"), + zfs_get_name(zhp), libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Set mountpoint for BE's root filesystem */ + if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), altroot) + != 0) { + be_print_err(gettext("be_mount_root: failed to " + "set mountpoint of %s to %s: %s\n"), + zfs_get_name(zhp), altroot, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Mount the BE's root filesystem */ + if (zfs_mount(zhp, NULL, 0) != 0) { + be_print_err(gettext("be_mount_root: failed to " + "mount dataset %s at %s: %s\n"), zfs_get_name(zhp), + altroot, libzfs_error_description(g_zfs)); + /* + * Set this BE's root filesystem 'mountpoint' property + * back to what it was before. + */ + (void) zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + mountpoint); + return (zfs_err_to_be_err(g_zfs)); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_unmount_root + * Description: This function unmounts the root dataset of a BE, but before + * unmounting, it looks at the BE's vfstab to determine + * if the root dataset mountpoint should be left as 'legacy' + * or '/'. If the vfstab contains an entry for this root + * dataset with a mountpoint of '/', it sets the mountpoint + * property to 'legacy'. + * + * Parameters: + * zhp - zfs_handle_t pointer of the BE root dataset that + * is currently mounted. + * ud - be_unmount_data_t pointer providing unmount data + * for the given BE root dataset. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_unmount_root(zfs_handle_t *zhp, be_unmount_data_t *ud) +{ + char mountpoint[MAXPATHLEN]; + boolean_t is_legacy = B_FALSE; + + /* See if this is a legacy mounted root */ + if (get_mountpoint_from_vfstab(ud->altroot, zfs_get_name(zhp), + mountpoint, sizeof (mountpoint), B_FALSE) == BE_SUCCESS && + strcmp(mountpoint, "/") == 0) { + is_legacy = B_TRUE; + } + + /* Unmount the dataset */ + if (zfs_unmount(zhp, NULL, ud->force ? MS_FORCE : 0) != 0) { + be_print_err(gettext("be_unmount_root: failed to " + "unmount BE root dataset %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Set canmount property for this BE's root filesystem to noauto */ + if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_CANMOUNT), "noauto") + != 0) { + be_print_err(gettext("be_unmount_root: failed to " + "set canmount property for %s to 'noauto': %s\n"), + zfs_get_name(zhp), libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Set mountpoint for BE's root dataset back to '/', or 'legacy' + * if its a legacy mounted root. + */ + if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), + is_legacy ? ZFS_MOUNTPOINT_LEGACY : "/") != 0) { + be_print_err(gettext("be_unmount_root: failed to " + "set mountpoint of %s to %s\n"), zfs_get_name(zhp), + is_legacy ? ZFS_MOUNTPOINT_LEGACY : "/"); + return (zfs_err_to_be_err(g_zfs)); + } + + return (BE_SUCCESS); +} + +/* + * Function: fix_mountpoint + * Description: This function checks the mountpoint of an unmounted BE to make + * sure that it is set to either 'legacy' or '/'. If it's not, + * then we're in a situation where an unmounted BE has some random + * mountpoint set for it. (This could happen if the system was + * rebooted while an inactive BE was mounted). This function + * attempts to fix its mountpoints. + * Parameters: + * zhp - zfs_handle_t pointer to root dataset of the BE + * whose mountpoint needs to be checked. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +fix_mountpoint(zfs_handle_t *zhp) +{ + be_unmount_data_t ud = { 0 }; + char *altroot = NULL; + char mountpoint[MAXPATHLEN]; + int ret = BE_SUCCESS; + + /* + * Record what this BE's root dataset mountpoint property is currently + * set to. + */ + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint, + sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0) { + be_print_err(gettext("fix_mountpoint: failed to get " + "mountpoint property of (%s): %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (BE_ERR_ZFS); + } + + /* + * If the root dataset mountpoint is set to 'legacy' or '/', we're okay. + */ + if (strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0 || + strcmp(mountpoint, "/") == 0) { + return (BE_SUCCESS); + } + + /* + * Iterate through this BE's children datasets and fix + * them if they need fixing. + */ + if (zfs_iter_filesystems(zhp, fix_mountpoint_callback, mountpoint) + != 0) { + return (BE_ERR_ZFS); + } + + /* + * The process of mounting and unmounting the root file system + * will fix its mountpoint to correctly be either 'legacy' or '/' + * since be_unmount_root will do the right thing by looking at + * its vfstab. + */ + + /* Generate temporary altroot to mount the root file system */ + if ((ret = be_make_tmp_mountpoint(&altroot)) != BE_SUCCESS) { + be_print_err(gettext("fix_mountpoint: failed to " + "make temporary mountpoint\n")); + return (ret); + } + + /* Mount and unmount the root. */ + if ((ret = be_mount_root(zhp, altroot)) != BE_SUCCESS) { + be_print_err(gettext("fix_mountpoint: failed to " + "mount BE root file system\n")); + goto cleanup; + } + ud.altroot = altroot; + if ((ret = be_unmount_root(zhp, &ud)) != BE_SUCCESS) { + be_print_err(gettext("fix_mountpoint: failed to " + "unmount BE root file system\n")); + goto cleanup; + } + +cleanup: + free(altroot); + + return (ret); +} + +/* + * Function: be_mount_zones + * Description: This function finds all supported non-global zones in the + * given global BE and mounts them with respect to where the + * global BE is currently mounted. The global BE datasets + * (including its shared datasets) are expected to already + * be mounted. + * Parameters: + * be_zhp - zfs_handle_t pointer to the root dataset of the + * global BE. + * md - be_mount_data_t pointer to data for global BE. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_mount_zones(zfs_handle_t *be_zhp, be_mount_data_t *md) +{ + zoneBrandList_t *brands = NULL; + zoneList_t zlst = NULL; + char *zonename = NULL; + char *zonepath = NULL; + char *zonepath_ds = NULL; + int k; + int ret = BE_SUCCESS; + + z_set_zone_root(md->altroot); + + if ((brands = be_get_supported_brandlist()) == NULL) { + be_print_err(gettext("be_mount_zones: " + "no supported brands\n")); + return (BE_SUCCESS); + } + + zlst = z_get_nonglobal_zone_list_by_brand(brands); + if (zlst == NULL) { + z_free_brand_list(brands); + return (BE_SUCCESS); + } + + for (k = 0; (zonename = z_zlist_get_zonename(zlst, k)) != NULL; k++) { + if (z_zlist_get_current_state(zlst, k) == + ZONE_STATE_INSTALLED) { + zonepath = z_zlist_get_zonepath(zlst, k); + + /* + * Get the dataset of this zonepath in current BE. + * If its not a dataset, skip it. + */ + if ((zonepath_ds = be_get_ds_from_dir(zonepath)) + == NULL) + continue; + + /* + * Check if this zone is supported based on + * the dataset of its zonepath + */ + if (!be_zone_supported(zonepath_ds)) { + free(zonepath_ds); + zonepath_ds = NULL; + continue; + } + + /* + * if BE's shared file systems are already mounted, + * zone path dataset would have already been lofs + * mounted under altroot. Otherwise, we need to do + * it here. + */ + if (!md->shared_fs) { + ret = loopback_mount_zonepath(zonepath, md); + if (ret != BE_SUCCESS) + goto done; + } + + + /* Mount this zone */ + ret = be_mount_one_zone(be_zhp, md, zonename, + zonepath, zonepath_ds); + + free(zonepath_ds); + zonepath_ds = NULL; + + if (ret != BE_SUCCESS) { + be_print_err(gettext("be_mount_zones: " + "failed to mount zone %s under " + "altroot %s\n"), zonename, md->altroot); + goto done; + } + } + } + +done: + z_free_brand_list(brands); + z_free_zone_list(zlst); + /* + * libinstzones caches mnttab and uses cached version for resolving lofs + * mounts when we call z_resolve_lofs. It creates the cached version + * when the first call to z_resolve_lofs happens. So, library's cached + * mnttab doesn't contain entries for lofs mounts created in the above + * loop. Because of this, subsequent calls to z_resolve_lofs would fail + * to resolve these lofs mounts. So, here we destroy library's cached + * mnttab to force its recreation when the next call to z_resolve_lofs + * happens. + */ + z_destroyMountTable(); + return (ret); +} + +/* + * Function: be_unmount_zones + * Description: This function finds all supported non-global zones in the + * given mounted global BE and unmounts them. + * Parameters: + * ud - unmount_data_t pointer data for the global BE. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_unmount_zones(be_unmount_data_t *ud) +{ + zoneBrandList_t *brands = NULL; + zoneList_t zlst = NULL; + char *zonename = NULL; + char *zonepath = NULL; + char alt_zonepath[MAXPATHLEN]; + char *zonepath_ds = NULL; + int k; + int ret = BE_SUCCESS; + + z_set_zone_root(ud->altroot); + + if ((brands = be_get_supported_brandlist()) == NULL) { + be_print_err(gettext("be_unmount_zones: " + "no supported brands\n")); + return (BE_SUCCESS); + } + + zlst = z_get_nonglobal_zone_list_by_brand(brands); + if (zlst == NULL) { + z_free_brand_list(brands); + return (BE_SUCCESS); + } + + for (k = 0; (zonename = z_zlist_get_zonename(zlst, k)) != NULL; k++) { + if (z_zlist_get_current_state(zlst, k) == + ZONE_STATE_INSTALLED) { + zonepath = z_zlist_get_zonepath(zlst, k); + + /* Build zone's zonepath wrt the global BE altroot */ + (void) snprintf(alt_zonepath, sizeof (alt_zonepath), + "%s%s", ud->altroot, zonepath); + + /* + * Get the dataset of this zonepath. If its not + * a dataset, skip it. + */ + if ((zonepath_ds = be_get_ds_from_dir(alt_zonepath)) + == NULL) + continue; + + /* + * Check if this zone is supported based on the + * dataset of its zonepath. + */ + if (!be_zone_supported(zonepath_ds)) { + free(zonepath_ds); + zonepath_ds = NULL; + continue; + } + + /* Unmount this zone */ + ret = be_unmount_one_zone(ud, zonename, zonepath, + zonepath_ds); + + free(zonepath_ds); + zonepath_ds = NULL; + + if (ret != BE_SUCCESS) { + be_print_err(gettext("be_unmount_zones:" + " failed to unmount zone %s from " + "altroot %s\n"), zonename, ud->altroot); + goto done; + } + } + } + +done: + z_free_brand_list(brands); + z_free_zone_list(zlst); + return (ret); +} + +/* + * Function: be_mount_one_zone + * Description: This function is called to mount one zone for a given + * global BE. + * Parameters: + * be_zhp - zfs_handle_t pointer to the root dataset of the + * global BE + * md - be_mount_data_t pointer to data for global BE + * zonename - name of zone to mount + * zonepath - zonepath of zone to mount + * zonepath_ds - dataset for the zonepath + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_mount_one_zone(zfs_handle_t *be_zhp, be_mount_data_t *md, char *zonename, + char *zonepath, char *zonepath_ds) +{ + be_mount_data_t zone_md = { 0 }; + zfs_handle_t *zone_zhp = NULL; + char zone_altroot[MAXPATHLEN]; + char zoneroot[MAXPATHLEN]; + char zoneroot_ds[MAXPATHLEN]; + int ret = BE_SUCCESS; + + /* Find the active zone root dataset for this zone for this BE */ + if ((ret = be_find_active_zone_root(be_zhp, zonepath_ds, zoneroot_ds, + sizeof (zoneroot_ds))) == BE_ERR_ZONE_NO_ACTIVE_ROOT) { + be_print_err(gettext("be_mount_one_zone: did not " + "find active zone root for zone %s, skipping ...\n"), + zonename); + return (BE_SUCCESS); + } else if (ret != BE_SUCCESS) { + be_print_err(gettext("be_mount_one_zone: failed to " + "find active zone root for zone %s\n"), zonename); + return (ret); + } + + /* Get handle to active zoneroot dataset */ + if ((zone_zhp = zfs_open(g_zfs, zoneroot_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_mount_one_zone: failed to " + "open zone root dataset (%s): %s\n"), zoneroot_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Generate string for zone's altroot path */ + be_make_zoneroot(zonepath, zoneroot, sizeof (zoneroot)); + (void) strlcpy(zone_altroot, md->altroot, sizeof (zone_altroot)); + (void) strlcat(zone_altroot, zoneroot, sizeof (zone_altroot)); + + /* Build mount_data for the zone */ + zone_md.altroot = zone_altroot; + zone_md.shared_fs = md->shared_fs; + zone_md.shared_rw = md->shared_rw; + + /* Mount the zone's root file system */ + if ((ret = be_mount_zone_root(zone_zhp, &zone_md)) != BE_SUCCESS) { + be_print_err(gettext("be_mount_one_zone: failed to " + "mount zone root file system at %s\n"), zone_altroot); + goto done; + } + + /* Iterate through zone's children filesystems */ + if ((ret = zfs_iter_filesystems(zone_zhp, be_mount_callback, + zone_altroot)) != 0) { + be_print_err(gettext("be_mount_one_zone: failed to " + "mount zone subordinate file systems at %s\n"), + zone_altroot); + goto done; + } + + /* TODO: Mount all shared file systems for this zone */ + +done: + ZFS_CLOSE(zone_zhp); + return (ret); +} + +/* + * Function: be_unmount_one_zone + * Description: This function unmount one zone for a give global BE. + * Parameters: + * ud - be_unmount_data_t pointer to data for global BE + * zonename - name of zone to unmount + * zonepath - zonepath of the zone to unmount + * zonepath_ds - dataset for the zonepath + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_unmount_one_zone(be_unmount_data_t *ud, char *zonename, char *zonepath, + char *zonepath_ds) +{ + be_unmount_data_t zone_ud = { 0 }; + zfs_handle_t *zone_zhp = NULL; + char zone_altroot[MAXPATHLEN]; + char zoneroot[MAXPATHLEN]; + char zoneroot_ds[MAXPATHLEN]; + int ret = BE_SUCCESS; + + /* Generate string for zone's alternate root path */ + be_make_zoneroot(zonepath, zoneroot, sizeof (zoneroot)); + (void) strlcpy(zone_altroot, ud->altroot, sizeof (zone_altroot)); + (void) strlcat(zone_altroot, zoneroot, sizeof (zone_altroot)); + + /* Build be_unmount_data for zone */ + zone_ud.altroot = zone_altroot; + zone_ud.force = ud->force; + + /* Find the mounted zone root dataset for this zone for this BE */ + if ((ret = be_find_mounted_zone_root(zone_altroot, zonepath_ds, + zoneroot_ds, sizeof (zoneroot_ds))) == BE_ERR_NO_MOUNTED_ZONE) { + be_print_err(gettext("be_unmount_one_zone: did not " + "find any zone root mounted for zone %s\n"), zonename); + return (BE_SUCCESS); + } else if (ret != BE_SUCCESS) { + be_print_err(gettext("be_unmount_one_zone: failed to " + "find mounted zone root for zone %s\n"), zonename); + return (ret); + } + + /* Get handle to zoneroot dataset mounted for this BE */ + if ((zone_zhp = zfs_open(g_zfs, zoneroot_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_unmount_one_zone: failed to " + "open mounted zone root dataset (%s): %s\n"), zoneroot_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* TODO: Unmount all shared file systems for this zone */ + + /* Iterate through zone's children filesystems and unmount them */ + if ((ret = zfs_iter_filesystems(zone_zhp, be_unmount_callback, + &zone_ud)) != 0) { + be_print_err(gettext("be_unmount_one_zone: failed to " + "unmount zone subordinate file systems at %s\n"), + zone_altroot); + goto done; + } + + /* Unmount the zone's root filesystem */ + if ((ret = be_unmount_zone_root(zone_zhp, &zone_ud)) != BE_SUCCESS) { + be_print_err(gettext("be_unmount_one_zone: failed to " + "unmount zone root file system at %s\n"), zone_altroot); + goto done; + } + +done: + ZFS_CLOSE(zone_zhp); + return (ret); +} + +/* + * Function: be_get_ds_from_dir_callback + * Description: This is a callback function used to iterate all datasets + * to find the one that is currently mounted at the directory + * being searched for. If matched, the name of the dataset is + * returned in heap storage, so the caller is responsible for + * freeing it. + * Parameters: + * zhp - zfs_handle_t pointer to current dataset being processed. + * data - dir_data_t pointer providing name of directory being + * searched for. + * Returns: + * 1 - This dataset is mounted at directory being searched for. + * 0 - This dataset is not mounted at directory being searched for. + * Scope: + * Private + */ +static int +be_get_ds_from_dir_callback(zfs_handle_t *zhp, void *data) +{ + dir_data_t *dd = data; + char *mp = NULL; + int zret = 0; + + if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) { + ZFS_CLOSE(zhp); + return (0); + } + + if (zfs_is_mounted(zhp, &mp) && mp != NULL && + strcmp(mp, dd->dir) == 0) { + if ((dd->ds = strdup(zfs_get_name(zhp))) == NULL) { + be_print_err(gettext("be_get_ds_from_dir_callback: " + "memory allocation failed\n")); + ZFS_CLOSE(zhp); + return (0); + } + ZFS_CLOSE(zhp); + return (1); + } + + zret = zfs_iter_filesystems(zhp, be_get_ds_from_dir_callback, dd); + + ZFS_CLOSE(zhp); + + return (zret); +} diff --git a/usr/src/lib/libbe/common/be_rename.c b/usr/src/lib/libbe/common/be_rename.c new file mode 100644 index 0000000000..1439424d13 --- /dev/null +++ b/usr/src/lib/libbe/common/be_rename.c @@ -0,0 +1,232 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include <assert.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <libbe.h> +#include <libbe_priv.h> + +/* ******************************************************************** */ +/* Public Functions */ +/* ******************************************************************** */ + +/* + * Function: be_rename + * Description: Renames the BE from the original name to the new name + * passed in through be_attrs. Also the entries in vfstab and + * menu.lst are updated with the new name. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attribute values are used by + * this function: + * + * BE_ATTR_ORIG_BE_NAME *required + * BE_ATTR_NEW_BE_NAME *required + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ + +int +be_rename(nvlist_t *be_attrs) +{ + be_transaction_data_t bt = { 0 }; + be_transaction_data_t cbt = { 0 }; + be_fs_list_data_t fld = { 0 }; + zfs_handle_t *zhp = NULL; + char root_ds[MAXPATHLEN]; + char *mp = NULL; + int zret = 0, ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get original BE name to rename from */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_ORIG_BE_NAME, &bt.obe_name) + != 0) { + be_print_err(gettext("be_rename: failed to " + "lookup BE_ATTR_ORIG_BE_NAME attribute\n")); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* Get new BE name to rename to */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_NEW_BE_NAME, &bt.nbe_name) + != 0) { + be_print_err(gettext("be_rename: failed to " + "lookup BE_ATTR_NEW_BE_NAME attribute\n")); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* + * Get the currently active BE and check to see if this + * is an attempt to rename the currently active BE. + */ + if (be_find_current_be(&cbt) != BE_SUCCESS) { + be_print_err(gettext("be_rename: failed to find the currently " + "active BE\n")); + be_zfs_fini(); + return (BE_ERR_CURR_BE_NOT_FOUND); + } + + if (strncmp(bt.obe_name, cbt.obe_name, + MAX(strlen(bt.obe_name), strlen(cbt.obe_name))) == 0) { + be_print_err(gettext("be_rename: This is an attempt to rename " + "the currently active BE, which is not supported\n")); + be_zfs_fini(); + free(cbt.obe_name); + return (BE_ERR_RENAME_ACTIVE); + } + + /* Validate original BE name */ + if (!be_valid_be_name(bt.obe_name)) { + be_print_err(gettext("be_rename: " + "invalid BE name %s\n"), bt.obe_name); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* Validate new BE name */ + if (!be_valid_be_name(bt.nbe_name)) { + be_print_err(gettext("be_rename: invalid BE name %s\n"), + bt.nbe_name); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* Find which zpool the BE is in */ + if ((zret = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_rename: failed to " + "find zpool for BE (%s)\n"), bt.obe_name); + be_zfs_fini(); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_rename: zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + be_zfs_fini(); + return (ret); + } + + /* New BE will reside in the same zpool as orig BE */ + bt.nbe_zpool = bt.obe_zpool; + + be_make_root_ds(bt.obe_zpool, bt.obe_name, root_ds, sizeof (root_ds)); + bt.obe_root_ds = strdup(root_ds); + be_make_root_ds(bt.nbe_zpool, bt.nbe_name, root_ds, sizeof (root_ds)); + bt.nbe_root_ds = strdup(root_ds); + + /* + * Generate a list of file systems from the BE that are legacy + * mounted before renaming. We use this list to determine which + * entries in the vfstab we need to update after we've renamed the BE. + */ + if ((ret = be_get_legacy_fs(bt.obe_name, bt.obe_root_ds, NULL, NULL, + &fld)) != BE_SUCCESS) { + be_print_err(gettext("be_rename: failed to " + "get legacy mounted file system list for %s\n"), + bt.obe_name); + goto done; + } + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_rename: failed to " + "open BE root dataset (%s): %s\n"), + bt.obe_root_ds, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Rename of BE's root dataset. */ + if (zfs_rename(zhp, bt.nbe_root_ds, B_FALSE) != 0) { + be_print_err(gettext("be_rename: failed to " + "rename dataset (%s): %s\n"), bt.obe_root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Refresh handle to BE's root dataset after the rename */ + ZFS_CLOSE(zhp); + if ((zhp = zfs_open(g_zfs, bt.nbe_root_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_rename: failed to " + "open BE root dataset (%s): %s\n"), + bt.obe_root_ds, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* If BE is already mounted, get its mountpoint */ + if (zfs_is_mounted(zhp, &mp) && mp == NULL) { + be_print_err(gettext("be_rename: failed to " + "get altroot of mounted BE %s: %s\n"), + bt.nbe_name, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Update BE's vfstab */ + if ((ret = be_update_vfstab(bt.nbe_name, bt.obe_zpool, bt.nbe_zpool, + &fld, mp)) != BE_SUCCESS) { + be_print_err(gettext("be_rename: " + "failed to update new BE's vfstab (%s)\n"), bt.nbe_name); + goto done; + } + + /* Update this BE's GRUB menu entry */ + if (getzoneid() == GLOBAL_ZONEID && (ret = be_update_menu(bt.obe_name, + bt.nbe_name, bt.obe_zpool, NULL)) != BE_SUCCESS) { + be_print_err(gettext("be_rename: " + "failed to update grub menu entry from %s to %s\n"), + bt.obe_name, bt.nbe_name); + } + +done: + be_free_fs_list(&fld); + + ZFS_CLOSE(zhp); + + be_zfs_fini(); + + free(bt.obe_root_ds); + free(bt.nbe_root_ds); + return (ret); +} diff --git a/usr/src/lib/libbe/common/be_snapshot.c b/usr/src/lib/libbe/common/be_snapshot.c new file mode 100644 index 0000000000..265bbeba9d --- /dev/null +++ b/usr/src/lib/libbe/common/be_snapshot.c @@ -0,0 +1,769 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * System includes + */ +#include <assert.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <libbe.h> +#include <libbe_priv.h> + +/* Private function prototypes */ +static int be_rollback_check_callback(zfs_handle_t *, void *); +static int be_rollback_callback(zfs_handle_t *, void *); + + +/* ******************************************************************** */ +/* Public Functions */ +/* ******************************************************************** */ + +/* + * Function: be_create_snapshot + * Description: Creates a recursive snapshot of all the datasets within a BE. + * If the name of the BE to snapshot is not provided, it assumes + * we're snapshotting the currently running BE. If the snapshot + * name is not provided it creates an auto named snapshot, which + * will be returned to the caller upon success. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attributes are used by this function: + * + * BE_ATTR_ORIG_BE_NAME *optional + * BE_ATTR_SNAP_NAME *optional + * BE_ATTR_POLICY *optional + * + * If the BE_ATTR_SNAP_NAME was not passed in, upon + * successful BE snapshot creation, the following + * attribute value will be returned to the caller by + * setting it in the be_attrs parameter passed in: + * + * BE_ATTR_SNAP_NAME + * + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_create_snapshot(nvlist_t *be_attrs) +{ + char *be_name = NULL; + char *snap_name = NULL; + char *policy = NULL; + boolean_t autoname = B_FALSE; + int ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get original BE name if one was provided */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_ORIG_BE_NAME, DATA_TYPE_STRING, &be_name, NULL) != 0) { + be_print_err(gettext("be_create_snapshot: failed to " + "lookup BE_ATTR_ORIG_BE_NAME attribute\n")); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* Validate original BE name if one was provided */ + if (be_name != NULL && !be_valid_be_name(be_name)) { + be_print_err(gettext("be_create_snapshot: " + "invalid BE name %s\n"), be_name); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* Get snapshot name to create if one was provided */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_SNAP_NAME, DATA_TYPE_STRING, &snap_name, NULL) != 0) { + be_print_err(gettext("be_create_snapshot: " + "failed to lookup BE_ATTR_SNAP_NAME attribute\n")); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* Get BE policy to create this snapshot under */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_POLICY, DATA_TYPE_STRING, &policy, NULL) != 0) { + be_print_err(gettext("be_create_snapshot: " + "failed to lookup BE_ATTR_POLICY attribute\n")); + be_zfs_fini(); + return (BE_ERR_INVAL); + } + + /* + * If no snap_name ws provided, we're going to create an + * auto named snapshot. Set flag so that we know to pass + * the auto named snapshot to the caller later. + */ + if (snap_name == NULL) + autoname = B_TRUE; + + if ((ret = _be_create_snapshot(be_name, &snap_name, policy)) + == BE_SUCCESS) { + if (autoname == B_TRUE) { + /* + * Set auto named snapshot name in the + * nvlist passed in by the caller. + */ + if (nvlist_add_string(be_attrs, BE_ATTR_SNAP_NAME, + snap_name) != 0) { + be_print_err(gettext("be_create_snapshot: " + "failed to add auto snap name (%s) to " + "be_attrs\n"), snap_name); + ret = BE_ERR_NOMEM; + } + } + } + + be_zfs_fini(); + + return (ret); +} + +/* + * Function: be_destroy_snapshot + * Description: Iterates through all the datasets of the BE and deletes + * the snapshots of each one with the specified name. If the + * BE name is not provided, it assumes we're operating on the + * currently running BE. The name of the snapshot name to + * destroy must be provided. + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attribute values are used by this + * function: + * + * BE_ATTR_ORIG_BE_NAME *optional + * BE_ATTR_SNAP_NAME *required + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_destroy_snapshot(nvlist_t *be_attrs) +{ + char *be_name = NULL; + char *snap_name = NULL; + int ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get original BE name if one was provided */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_ORIG_BE_NAME, DATA_TYPE_STRING, &be_name, NULL) != 0) { + be_print_err(gettext("be_destroy_snapshot: " + "failed to lookup BE_ATTR_ORIG_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* Validate original BE name if one was provided */ + if (be_name != NULL && !be_valid_be_name(be_name)) { + be_print_err(gettext("be_destroy_snapshot: " + "invalid BE name %s\n"), be_name); + return (BE_ERR_INVAL); + } + + /* Get snapshot name to destroy */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_SNAP_NAME, &snap_name) + != 0) { + be_print_err(gettext("be_destroy_snapshot: " + "failed to lookup BE_ATTR_SNAP_NAME attribute.\n")); + return (BE_ERR_INVAL); + } + + ret = _be_destroy_snapshot(be_name, snap_name); + + be_zfs_fini(); + + return (ret); +} + +/* + * Function: be_rollback + * Description: Rolls back a BE and all of its children datasets to the + * named snapshot. All of the BE's datasets must have the + * named snapshot for this function to succeed. If the name + * of the BE is not passed in, this function assumes we're + * operating on the currently booted live BE. + * + * Note - This function does not check if the BE has any + * younger snapshots than the one we're trying to rollback to. + * If it does, then those younger snapshots and their dependent + * clone file systems will get destroyed in the process of + * rolling back. + * + * Parameters: + * be_attrs - pointer to nvlist_t of attributes being passed in. + * The following attributes are used by this function: + * + * BE_ATTR_ORIG_BE_NAME *optional + * BE_ATTR_SNAP_NAME *required + * + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Public + */ +int +be_rollback(nvlist_t *be_attrs) +{ + be_transaction_data_t bt = { 0 }; + zfs_handle_t *zhp = NULL; + char obe_root_ds[MAXPATHLEN]; + int zret = 0, ret = BE_SUCCESS; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + /* Get original BE name if one was provided */ + if (nvlist_lookup_pairs(be_attrs, NV_FLAG_NOENTOK, + BE_ATTR_ORIG_BE_NAME, DATA_TYPE_STRING, &bt.obe_name, NULL) != 0) { + be_print_err(gettext("be_rollback: " + "failed to lookup BE_ATTR_ORIG_BE_NAME attribute\n")); + return (BE_ERR_INVAL); + } + + /* If original BE name not provided, use current BE */ + if (bt.obe_name == NULL) { + if ((ret = be_find_current_be(&bt)) != BE_SUCCESS) { + return (ret); + } + } else { + /* Validate original BE name */ + if (!be_valid_be_name(bt.obe_name)) { + be_print_err(gettext("be_rollback: " + "invalid BE name %s\n"), bt.obe_name); + return (BE_ERR_INVAL); + } + } + + /* Get snapshot name to rollback to */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_SNAP_NAME, &bt.obe_snap_name) + != 0) { + be_print_err(gettext("be_rollback: " + "failed to lookup BE_ATTR_SNAP_NAME attribute.\n")); + return (BE_ERR_INVAL); + } + + /* Find which zpool obe_name lives in */ + if ((zret = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_rollback: " + "failed to find zpool for BE (%s)\n"), bt.obe_name); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_rollback: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Generate string for BE's root dataset */ + be_make_root_ds(bt.obe_zpool, bt.obe_name, obe_root_ds, + sizeof (obe_root_ds)); + bt.obe_root_ds = obe_root_ds; + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_rollback: " + "failed to open BE root dataset (%s): %s\n"), + bt.obe_root_ds, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check that snapshot name exists for this BE and all of its + * children file systems. This call will end up closing the + * zfs handle passed in whether it succeeds or fails. + */ + if ((ret = be_rollback_check_callback(zhp, bt.obe_snap_name)) != 0) { + zhp = NULL; + return (ret); + } + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_rollback: " + "failed to open BE root dataset (%s): %s\n"), + bt.obe_root_ds, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Iterate through a BE's datasets and roll them all back to + * the specified snapshot. This call will end up closing the + * zfs handle passed in whether it succeeds or fails. + */ + if ((ret = be_rollback_callback(zhp, bt.obe_snap_name)) != 0) { + zhp = NULL; + be_print_err(gettext("be_rollback: " + "failed to rollback BE %s to %s\n"), bt.obe_name, + bt.obe_snap_name); + return (ret); + } + zhp = NULL; + be_zfs_fini(); + return (BE_SUCCESS); +} + + +/* ******************************************************************** */ +/* Semi-Private Functions */ +/* ******************************************************************** */ + +/* + * Function: _be_create_snapshot + * Description: see be_create_snapshot + * Parameters: + * be_name - The name of the BE that we're taking a snapshot of. + * snap_name - The name of the snapshot we're creating. If + * snap_name is NULL an auto generated name will be used, + * and upon success, will return that name via this + * reference pointer. The caller is responsible for + * freeing the returned name. + * policy - The clean-up policy type. (library wide use only) + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +_be_create_snapshot(char *be_name, char **snap_name, char *policy) +{ + be_transaction_data_t bt = { 0 }; + zfs_handle_t *zhp = NULL; + nvlist_t *ss_props = NULL; + char ss[MAXPATHLEN]; + char root_ds[MAXPATHLEN]; + int pool_version = 0; + int i = 0; + int zret = 0, ret = BE_SUCCESS; + boolean_t autoname = B_FALSE; + + /* Set parameters in bt structure */ + bt.obe_name = be_name; + bt.obe_snap_name = *snap_name; + bt.policy = policy; + + /* If original BE name not supplied, use current BE */ + if (bt.obe_name == NULL) { + if ((ret = be_find_current_be(&bt)) != BE_SUCCESS) { + return (ret); + } + } + + /* Find which zpool obe_name lives in */ + if ((zret = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_create_snapshot: failed to " + "find zpool for BE (%s)\n"), bt.obe_name); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_create_snapshot: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + be_make_root_ds(bt.obe_zpool, bt.obe_name, root_ds, + sizeof (root_ds)); + bt.obe_root_ds = root_ds; + + /* If BE policy not specified, use the default policy */ + if (bt.policy == NULL) { + bt.policy = be_default_policy(); + } else { + /* Validate policy type */ + if (!valid_be_policy(bt.policy)) { + be_print_err(gettext("be_create_snapshot: " + "invalid BE policy type (%s)\n"), bt.policy); + return (BE_ERR_INVAL); + } + } + + /* + * If snapshot name not specified, set auto name flag and + * generate auto snapshot name. + */ + if (bt.obe_snap_name == NULL) { + autoname = B_TRUE; + if ((bt.obe_snap_name = be_auto_snap_name()) + == NULL) { + be_print_err(gettext("be_create_snapshot: " + "failed to create auto snapshot name\n")); + ret = BE_ERR_AUTONAME; + goto done; + } + } + + /* Generate the name of the snapshot to take. */ + (void) snprintf(ss, sizeof (ss), "%s@%s", bt.obe_root_ds, + bt.obe_snap_name); + + /* Get handle to BE's root dataset */ + if ((zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_DATASET)) + == NULL) { + be_print_err(gettext("be_create_snapshot: " + "failed to open BE root dataset (%s): %s\n"), + bt.obe_root_ds, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Get the ZFS pool version of the pool where this dataset resides */ + if (zfs_spa_version(zhp, &pool_version) != 0) { + be_print_err(gettext("be_create_snapshot: failed to " + "get ZFS pool version for %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + } + + /* + * If ZFS pool version supports snapshot user properties, store + * cleanup policy there. Otherwise don't set one - this snapshot + * will always inherit the cleanup policy from its parent. + */ + if (pool_version >= SPA_VERSION_SNAP_PROPS) { + if (nvlist_alloc(&ss_props, NV_UNIQUE_NAME, 0) != 0) { + be_print_err(gettext("be_create_snapshot: internal " + "error: out of memory\n")); + return (BE_ERR_NOMEM); + } + if (nvlist_add_string(ss_props, BE_POLICY_PROPERTY, bt.policy) + != 0) { + be_print_err(gettext("be_create_snapshot: internal " + "error: out of memory\n")); + nvlist_free(ss_props); + return (BE_ERR_NOMEM); + } + } else if (policy != NULL) { + /* + * If an explicit cleanup policy was requested + * by the caller and we don't support it, error out. + */ + be_print_err(gettext("be_create_snapshot: cannot set " + "cleanup policy: ZFS pool version is %d\n"), pool_version); + return (BE_ERR_NOTSUP); + } + + /* Create the snapshots recursively */ + if (zfs_snapshot(g_zfs, ss, B_TRUE, ss_props) != 0) { + if (!autoname || libzfs_errno(g_zfs) != EZFS_EXISTS) { + be_print_err(gettext("be_create_snapshot: " + "recursive snapshot of %s failed: %s\n"), + ss, libzfs_error_description(g_zfs)); + + if (libzfs_errno(g_zfs) == EZFS_EXISTS) + ret = BE_ERR_SS_EXISTS; + else + ret = zfs_err_to_be_err(g_zfs); + + goto done; + } else { + for (i = 1; i < BE_AUTO_NAME_MAX_TRY; i++) { + + /* Sleep 1 before retrying */ + (void) sleep(1); + + /* Generate new auto snapshot name. */ + free(bt.obe_snap_name); + if ((bt.obe_snap_name = + be_auto_snap_name()) == NULL) { + be_print_err(gettext( + "be_create_snapshot: failed to " + "create auto snapshot name\n")); + ret = BE_ERR_AUTONAME; + goto done; + } + + /* Generate string of the snapshot to take. */ + (void) snprintf(ss, sizeof (ss), "%s@%s", + bt.obe_root_ds, bt.obe_snap_name); + + /* Create the snapshots recursively */ + if (zfs_snapshot(g_zfs, ss, B_TRUE, ss_props) + != 0) { + if (libzfs_errno(g_zfs) != + EZFS_EXISTS) { + be_print_err(gettext( + "be_create_snapshot: " + "recursive snapshot of %s " + "failed: %s\n"), ss, + libzfs_error_description( + g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + } else { + break; + } + } + + /* + * If we exhausted the maximum number of tries, + * free the auto snap name and set error. + */ + if (i == BE_AUTO_NAME_MAX_TRY) { + be_print_err(gettext("be_create_snapshot: " + "failed to create unique auto snapshot " + "name\n")); + free(bt.obe_snap_name); + bt.obe_snap_name = NULL; + ret = BE_ERR_AUTONAME; + } + } + } + + /* + * If we succeeded in creating an auto named snapshot, store + * the name in the nvlist passed in by the caller. + */ + if (autoname && bt.obe_snap_name) { + *snap_name = bt.obe_snap_name; + } + +done: + ZFS_CLOSE(zhp); + + if (ss_props != NULL) + nvlist_free(ss_props); + + return (ret); +} + +/* + * Function: _be_destroy_snapshot + * Description: see be_destroy_snapshot + * Parameters: + * be_name - The name of the BE that the snapshot belongs to. + * snap_name - The name of the snapshot we're destroying. + * Return: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +_be_destroy_snapshot(char *be_name, char *snap_name) +{ + be_transaction_data_t bt = { 0 }; + zfs_handle_t *zhp; + char ss[MAXPATHLEN]; + char root_ds[MAXPATHLEN]; + int err = BE_SUCCESS, ret = BE_SUCCESS; + + /* Make sure we actaully have a snapshot name */ + if (snap_name == NULL) { + be_print_err(gettext("be_destroy_snapshot: " + "invalid snapshot name\n")); + return (BE_ERR_INVAL); + } + + /* Set parameters in bt structure */ + bt.obe_name = be_name; + bt.obe_snap_name = snap_name; + + /* If original BE name not supplied, use current BE */ + if (bt.obe_name == NULL) { + if ((err = be_find_current_be(&bt)) != BE_SUCCESS) { + return (err); + } + } + + /* Find which zpool be_name lives in */ + if ((ret = zpool_iter(g_zfs, be_find_zpool_callback, &bt)) == 0) { + be_print_err(gettext("be_destroy_snapshot: " + "failed to find zpool for BE (%s)\n"), bt.obe_name); + return (BE_ERR_BE_NOENT); + } else if (ret < 0) { + be_print_err(gettext("be_destroy_snapshot: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + be_make_root_ds(bt.obe_zpool, bt.obe_name, root_ds, + sizeof (root_ds)); + bt.obe_root_ds = root_ds; + + zhp = zfs_open(g_zfs, bt.obe_root_ds, ZFS_TYPE_DATASET); + if (zhp == NULL) { + /* + * The zfs_open failed, return an error. + */ + be_print_err(gettext("be_destroy_snapshot: " + "failed to open BE root dataset (%s): %s\n"), + bt.obe_root_ds, libzfs_error_description(g_zfs)); + err = zfs_err_to_be_err(g_zfs); + } else { + /* + * Generate the name of the snapshot to take. + */ + (void) snprintf(ss, sizeof (ss), "%s@%s", bt.obe_name, + bt.obe_snap_name); + + /* + * destroy the snapshot. + */ + /* + * The boolean set to B_FALSE and passed to zfs_destroy_snaps() + * tells zfs to process and destroy the snapshots now. + * Otherwise the call will potentially return where the + * snapshot isn't actually destroyed yet, and ZFS is waiting + * until all the references to the snapshot have been + * released before actually destroying the snapshot. + */ + if (zfs_destroy_snaps(zhp, bt.obe_snap_name, B_FALSE) != 0) { + err = zfs_err_to_be_err(g_zfs); + be_print_err(gettext("be_destroy_snapshot: " + "failed to destroy snapshot %s: %s\n"), ss, + libzfs_error_description(g_zfs)); + } + } + + ZFS_CLOSE(zhp); + + return (err); +} + +/* ******************************************************************** */ +/* Private Functions */ +/* ******************************************************************** */ + +/* + * Function: be_rollback_check_callback + * Description: Callback function used to iterate through a BE's filesystems + * to check if a given snapshot name exists. + * Parameters: + * zhp - zfs_handle_t pointer to filesystem being processed. + * data - name of the snapshot to check for. + * Returns: + * 0 - Success, snapshot name exists for all filesystems. + * be_errno_t - Failure, snapshot name does not exist for all + * filesystems. + * Scope: + * Private + */ +static int +be_rollback_check_callback(zfs_handle_t *zhp, void *data) +{ + char *snap_name = data; + char ss[MAXPATHLEN]; + int ret = BE_SUCCESS; + + /* Generate string for this filesystem's snapshot name */ + (void) snprintf(ss, sizeof (ss), "%s@%s", zfs_get_name(zhp), snap_name); + + /* Check if snapshot exists */ + if (!zfs_dataset_exists(g_zfs, ss, ZFS_TYPE_SNAPSHOT)) { + be_print_err(gettext("be_rollback_check_callback: " + "snapshot does not exist %s\n"), ss); + ZFS_CLOSE(zhp); + return (BE_ERR_SS_NOENT); + } + + /* Iterate this dataset's children and check them */ + if ((ret = zfs_iter_filesystems(zhp, be_rollback_check_callback, + snap_name)) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + return (0); +} + +/* + * Function: be_rollback_callback + * Description: Callback function used to iterate through a BE's filesystems + * and roll them all back to the specified snapshot name. + * Parameters: + * zhp - zfs_handle_t pointer to filesystem being processed. + * data - name of snapshot to rollback to. + * Returns: + * 0 - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_rollback_callback(zfs_handle_t *zhp, void *data) +{ + zfs_handle_t *zhp_snap = NULL; + char *snap_name = data; + char ss[MAXPATHLEN]; + int ret = 0; + + /* Generate string for this filesystem's snapshot name */ + (void) snprintf(ss, sizeof (ss), "%s@%s", zfs_get_name(zhp), snap_name); + + /* Get handle to this filesystem's snapshot */ + if ((zhp_snap = zfs_open(g_zfs, ss, ZFS_TYPE_SNAPSHOT)) == NULL) { + be_print_err(gettext("be_rollback_callback: " + "failed to open snapshot %s: %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp); + return (ret); + } + + /* Rollback dataset */ + if (zfs_rollback(zhp, zhp_snap, B_FALSE) != 0) { + be_print_err(gettext("be_rollback_callback: " + "failed to rollback BE dataset %s to snapshot %s: %s\n"), + zfs_get_name(zhp), ss, libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + ZFS_CLOSE(zhp_snap); + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(zhp_snap); + /* Iterate this dataset's children and roll them back */ + if ((ret = zfs_iter_filesystems(zhp, be_rollback_callback, + snap_name)) != 0) { + ZFS_CLOSE(zhp); + return (ret); + } + + ZFS_CLOSE(zhp); + return (0); +} diff --git a/usr/src/lib/libbe/common/be_utils.c b/usr/src/lib/libbe/common/be_utils.c new file mode 100644 index 0000000000..b0782c8f28 --- /dev/null +++ b/usr/src/lib/libbe/common/be_utils.c @@ -0,0 +1,3647 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * System includes + */ +#include <assert.h> +#include <errno.h> +#include <libgen.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/vfstab.h> +#include <sys/param.h> +#include <sys/systeminfo.h> +#include <ctype.h> +#include <time.h> +#include <unistd.h> +#include <fcntl.h> +#include <wait.h> + +#include <libbe.h> +#include <libbe_priv.h> + +#define INST_ICT "/usr/lib/python2.6/vendor-packages/osol_install/ict.py" + +/* Private function prototypes */ +static int update_dataset(char *, int, char *, char *, char *); +static int _update_vfstab(char *, char *, char *, char *, be_fs_list_data_t *); +static int be_open_menu(char *, char *, char *, FILE **, char *, boolean_t); +static int be_create_menu(char *, char *, char *, FILE **, char *); +static char *be_get_auto_name(char *, char *, boolean_t); + +/* + * Global error printing + */ +boolean_t do_print = B_FALSE; + +/* + * Private datatypes + */ +typedef struct zone_be_name_cb_data { + char *base_be_name; + int num; +} zone_be_name_cb_data_t; + +/* ******************************************************************** */ +/* Public Functions */ +/* ******************************************************************** */ + +/* + * Function: be_max_avail + * Description: Returns the available size for the zfs dataset passed in. + * Parameters: + * dataset - The dataset we want to get the available space for. + * ret - The available size will be returned in this. + * Returns: + * The error returned by the zfs get property function. + * Scope: + * Public + */ +int +be_max_avail(char *dataset, uint64_t *ret) +{ + zfs_handle_t *zhp; + int err = 0; + + /* Initialize libzfs handle */ + if (!be_zfs_init()) + return (BE_ERR_INIT); + + zhp = zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET); + if (zhp == NULL) { + /* + * The zfs_open failed return an error + */ + err = zfs_err_to_be_err(g_zfs); + } else { + err = be_maxsize_avail(zhp, ret); + } + ZFS_CLOSE(zhp); + be_zfs_fini(); + return (err); +} + +/* + * Function: libbe_print_errors + * Description: Turns on/off error output for the library. + * Parameter: + * set_do_print - Boolean that turns library error + * printing on or off. + * Returns: + * None + * Scope: + * Public; + */ +void +libbe_print_errors(boolean_t set_do_print) +{ + do_print = set_do_print; +} + +/* ******************************************************************** */ +/* Semi-Private Functions */ +/* ******************************************************************** */ + +/* + * Function: be_zfs_init + * Description: Initializes the libary global libzfs handle. + * Parameters: + * None + * Returns: + * B_TRUE - Success + * B_FALSE - Failure + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +be_zfs_init(void) +{ + be_zfs_fini(); + + if ((g_zfs = libzfs_init()) == NULL) { + be_print_err(gettext("be_zfs_init: failed to initialize ZFS " + "library\n")); + return (B_FALSE); + } + + return (B_TRUE); +} + +/* + * Function: be_zfs_fini + * Description: Closes the library global libzfs handle if it currently open. + * Parameter: + * None + * Returns: + * None + * Scope: + * Semi-private (library wide use only) + */ +void +be_zfs_fini(void) +{ + if (g_zfs) + libzfs_fini(g_zfs); + + g_zfs = NULL; +} + +/* + * Function: be_make_root_ds + * Description: Generate string for BE's root dataset given the pool + * it lives in and the BE name. + * Parameters: + * zpool - pointer zpool name. + * be_name - pointer to BE name. + * be_root_ds - pointer to buffer to return BE root dataset in. + * be_root_ds_size - size of be_root_ds + * Returns: + * None + * Scope: + * Semi-private (library wide use only) + */ +void +be_make_root_ds(const char *zpool, const char *be_name, char *be_root_ds, + int be_root_ds_size) +{ + (void) snprintf(be_root_ds, be_root_ds_size, "%s/%s/%s", zpool, + BE_CONTAINER_DS_NAME, be_name); +} + +/* + * Function: be_make_container_ds + * Description: Generate string for the BE container dataset given a pool name. + * Parameters: + * zpool - pointer zpool name. + * container_ds - pointer to buffer to return BE container + * dataset in. + * container_ds_size - size of container_ds + * Returns: + * None + * Scope: + * Semi-private (library wide use only) + */ +void +be_make_container_ds(const char *zpool, char *container_ds, + int container_ds_size) +{ + (void) snprintf(container_ds, container_ds_size, "%s/%s", zpool, + BE_CONTAINER_DS_NAME); +} + +/* + * Function: be_make_name_from_ds + * Description: This function takes a dataset name and strips off the + * BE container dataset portion from the beginning. The + * returned name is allocated in heap storage, so the caller + * is responsible for freeing it. + * Parameters: + * dataset - dataset to get name from. + * rc_loc - dataset underwhich the root container dataset lives. + * Returns: + * name of dataset relative to BE container dataset. + * NULL if dataset is not under a BE root dataset. + * Scope: + * Semi-primate (library wide use only) + */ +char * +be_make_name_from_ds(const char *dataset, char *rc_loc) +{ + char ds[ZFS_MAXNAMELEN]; + char *tok = NULL; + char *name = NULL; + + /* + * First token is the location of where the root container dataset + * lives; it must match rc_loc. + */ + if (strncmp(dataset, rc_loc, strlen(rc_loc)) == 0 && + dataset[strlen(rc_loc)] == '/') { + (void) strlcpy(ds, dataset + strlen(rc_loc) + 1, sizeof (ds)); + } else { + return (NULL); + } + + /* Second token must be BE container dataset name */ + if ((tok = strtok(ds, "/")) == NULL || + strcmp(tok, BE_CONTAINER_DS_NAME) != 0) + return (NULL); + + /* Return the remaining token if one exists */ + if ((tok = strtok(NULL, "")) == NULL) + return (NULL); + + if ((name = strdup(tok)) == NULL) { + be_print_err(gettext("be_make_name_from_ds: " + "memory allocation failed\n")); + return (NULL); + } + + return (name); +} + +/* + * Function: be_maxsize_avail + * Description: Returns the available size for the zfs handle passed in. + * Parameters: + * zhp - A pointer to the open zfs handle. + * ret - The available size will be returned in this. + * Returns: + * The error returned by the zfs get property function. + * Scope: + * Semi-private (library wide use only) + */ +int +be_maxsize_avail(zfs_handle_t *zhp, uint64_t *ret) +{ + return ((*ret = zfs_prop_get_int(zhp, ZFS_PROP_AVAILABLE))); +} + +/* + * Function: be_append_menu + * Description: Appends an entry for a BE into the menu.lst. + * Parameters: + * be_name - pointer to name of BE to add boot menu entry for. + * be_root_pool - pointer to name of pool BE lives in. + * boot_pool - Used if the pool containing the grub menu is + * different than the one contaiing the BE. This + * will normally be NULL. + * be_orig_root_ds - The root dataset for the BE. This is + * used to check to see if an entry already exists + * for this BE. + * description - pointer to description of BE to be added in + * the title line for this BEs entry. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_append_menu(char *be_name, char *be_root_pool, char *boot_pool, + char *be_orig_root_ds, char *description) +{ + zfs_handle_t *zhp = NULL; + char menu_file[MAXPATHLEN]; + char be_root_ds[MAXPATHLEN]; + char line[BUFSIZ]; + char temp_line[BUFSIZ]; + char title[MAXPATHLEN]; + char *entries[BUFSIZ]; + char *tmp_entries[BUFSIZ]; + char *pool_mntpnt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + boolean_t found_be = B_FALSE; + boolean_t found_orig_be = B_FALSE; + boolean_t found_title = B_FALSE; + boolean_t pool_mounted = B_FALSE; + boolean_t collect_lines = B_FALSE; + FILE *menu_fp = NULL; + int err = 0, ret = BE_SUCCESS; + int i, num_tmp_lines = 0, num_lines = 0; + + if (be_name == NULL || be_root_pool == NULL) + return (BE_ERR_INVAL); + + if (boot_pool == NULL) + boot_pool = be_root_pool; + + if ((zhp = zfs_open(g_zfs, be_root_pool, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_append_menu: failed to open " + "pool dataset for %s: %s\n"), be_root_pool, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if ((ret = be_mount_pool(zhp, &ptmp_mntpnt, &orig_mntpnt, + &pool_mounted)) != BE_SUCCESS) { + be_print_err(gettext("be_append_menu: pool dataset " + "(%s) could not be mounted\n"), be_root_pool); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(zhp, &pool_mntpnt)) { + be_print_err(gettext("be_append_menu: pool " + "dataset (%s) is not mounted. Can't set " + "the default BE in the grub menu.\n"), be_root_pool); + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + /* + * Check to see if this system supports grub + */ + if (be_has_grub()) { + (void) snprintf(menu_file, sizeof (menu_file), + "%s%s", pool_mntpnt, BE_GRUB_MENU); + } else { + (void) snprintf(menu_file, sizeof (menu_file), + "%s%s", pool_mntpnt, BE_SPARC_MENU); + } + + be_make_root_ds(be_root_pool, be_name, be_root_ds, sizeof (be_root_ds)); + + /* + * Iterate through menu first to make sure the BE doesn't already + * have an entry in the menu. + * + * Additionally while iterating through the menu, if we have an + * original root dataset for a BE we're cloning from, we need to keep + * track of that BE's menu entry. We will then use the lines from + * that entry to create the entry for the new BE. + */ + if ((ret = be_open_menu(be_root_pool, pool_mntpnt, menu_file, + &menu_fp, "r", B_TRUE)) != BE_SUCCESS) { + goto cleanup; + } else if (menu_fp == NULL) { + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + free(pool_mntpnt); + pool_mntpnt = NULL; + + while (fgets(line, BUFSIZ, menu_fp)) { + char *tok = NULL; + + (void) strlcpy(temp_line, line, BUFSIZ); + tok = strtok(line, BE_WHITE_SPACE); + + if (tok == NULL || tok[0] == '#') { + continue; + } else if (strcmp(tok, "title") == 0) { + collect_lines = B_FALSE; + if ((tok = strtok(NULL, "\n")) == NULL) + (void) strlcpy(title, "", sizeof (title)); + else + (void) strlcpy(title, tok, sizeof (title)); + found_title = B_TRUE; + + if (num_tmp_lines != 0) { + for (i = 0; i < num_tmp_lines; i++) { + free(tmp_entries[i]); + tmp_entries[i] = NULL; + } + num_tmp_lines = 0; + } + } else if (strcmp(tok, "bootfs") == 0) { + char *bootfs = strtok(NULL, BE_WHITE_SPACE); + found_title = B_FALSE; + if (bootfs == NULL) + continue; + + if (strcmp(bootfs, be_root_ds) == 0) { + found_be = B_TRUE; + break; + } + + if (be_orig_root_ds != NULL && + strcmp(bootfs, be_orig_root_ds) == 0 && + !found_orig_be) { + char str[BUFSIZ]; + found_orig_be = B_TRUE; + num_lines = 0; + /* + * Store the new title line + */ + (void) snprintf(str, BUFSIZ, "title %s\n", + description ? description : be_name); + entries[num_lines] = strdup(str); + num_lines++; + /* + * If there are any lines between the title + * and the bootfs line store these. Also + * free the temporary lines. + */ + for (i = 0; i < num_tmp_lines; i++) { + entries[num_lines] = tmp_entries[i]; + tmp_entries[i] = NULL; + num_lines++; + } + num_tmp_lines = 0; + /* + * Store the new bootfs line. + */ + (void) snprintf(str, BUFSIZ, "bootfs %s\n", + be_root_ds); + entries[num_lines] = strdup(str); + num_lines++; + collect_lines = B_TRUE; + } + } else if (found_orig_be && collect_lines) { + /* + * get the rest of the lines for the original BE and + * store them. + */ + if (strstr(line, BE_GRUB_COMMENT) != NULL || + strstr(line, "BOOTADM") != NULL) + continue; + entries[num_lines] = strdup(temp_line); + num_lines++; + } else if (found_title && !found_orig_be) { + tmp_entries[num_tmp_lines] = strdup(temp_line); + num_tmp_lines++; + } + } + + (void) fclose(menu_fp); + + if (found_be) { + /* + * If an entry for this BE was already in the menu, then if + * that entry's title matches what we would have put in + * return success. Otherwise return failure. + */ + char *new_title = description ? description : be_name; + + if (strcmp(title, new_title) == 0) { + ret = BE_SUCCESS; + goto cleanup; + } else { + if (be_remove_menu(be_name, be_root_pool, + boot_pool) != BE_SUCCESS) { + be_print_err(gettext("be_append_menu: " + "Failed to remove existing unusable " + "entry '%s' in boot menu.\n"), be_name); + ret = BE_ERR_BE_EXISTS; + goto cleanup; + } + } + } + + /* Append BE entry to the end of the file */ + menu_fp = fopen(menu_file, "a+"); + err = errno; + if (menu_fp == NULL) { + be_print_err(gettext("be_append_menu: failed " + "to open menu.lst file %s\n"), menu_file); + ret = errno_to_be_err(err); + goto cleanup; + } + + if (found_orig_be) { + /* + * write out all the stored lines + */ + for (i = 0; i < num_lines; i++) { + (void) fprintf(menu_fp, "%s", entries[i]); + free(entries[i]); + } + num_lines = 0; + + /* + * Check to see if this system supports grub + */ + if (be_has_grub()) + (void) fprintf(menu_fp, "%s\n", BE_GRUB_COMMENT); + ret = BE_SUCCESS; + } else { + (void) fprintf(menu_fp, "title %s\n", + description ? description : be_name); + (void) fprintf(menu_fp, "bootfs %s\n", be_root_ds); + + /* + * Check to see if this system supports grub + */ + if (be_has_grub()) { + (void) fprintf(menu_fp, "kernel$ " + "/platform/i86pc/kernel/$ISADIR/unix -B " + "$ZFS-BOOTFS\n"); + (void) fprintf(menu_fp, "module$ " + "/platform/i86pc/$ISADIR/boot_archive\n"); + (void) fprintf(menu_fp, "%s\n", BE_GRUB_COMMENT); + } + ret = BE_SUCCESS; + } + (void) fclose(menu_fp); +cleanup: + if (pool_mounted) { + int err = BE_SUCCESS; + err = be_unmount_pool(zhp, ptmp_mntpnt, orig_mntpnt); + if (ret == BE_SUCCESS) + ret = err; + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(zhp); + if (num_tmp_lines > 0) { + for (i = 0; i < num_tmp_lines; i++) { + free(tmp_entries[i]); + tmp_entries[i] = NULL; + } + } + if (num_lines > 0) { + for (i = 0; i < num_lines; i++) { + free(entries[i]); + entries[i] = NULL; + } + } + return (ret); +} + +/* + * Function: be_remove_menu + * Description: Removes a BE's entry from a menu.lst file. + * Parameters: + * be_name - the name of BE whose entry is to be removed from + * the menu.lst file. + * be_root_pool - the pool that be_name lives in. + * boot_pool - the pool where the BE is, if different than + * the pool containing the boot menu. If this is + * NULL it will be set to be_root_pool. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_remove_menu(char *be_name, char *be_root_pool, char *boot_pool) +{ + zfs_handle_t *zhp = NULL; + char be_root_ds[MAXPATHLEN]; + char **buffer = NULL; + char menu_buf[BUFSIZ]; + char menu[MAXPATHLEN]; + char *pool_mntpnt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + char *tmp_menu = NULL; + FILE *menu_fp = NULL; + FILE *tmp_menu_fp = NULL; + struct stat sb; + int ret = BE_SUCCESS; + int i; + int fd; + int err = 0; + int nlines = 0; + int default_entry = 0; + int entry_cnt = 0; + int entry_del = 0; + int num_entry_del = 0; + int tmp_menu_len = 0; + boolean_t write = B_TRUE; + boolean_t do_buffer = B_FALSE; + boolean_t pool_mounted = B_FALSE; + + if (boot_pool == NULL) + boot_pool = be_root_pool; + + /* Get name of BE's root dataset */ + be_make_root_ds(be_root_pool, be_name, be_root_ds, sizeof (be_root_ds)); + + /* Get handle to pool dataset */ + if ((zhp = zfs_open(g_zfs, be_root_pool, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_remove_menu: " + "failed to open pool dataset for %s: %s"), + be_root_pool, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if ((ret = be_mount_pool(zhp, &ptmp_mntpnt, &orig_mntpnt, + &pool_mounted)) != BE_SUCCESS) { + be_print_err(gettext("be_remove_menu: pool dataset " + "(%s) could not be mounted\n"), be_root_pool); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(zhp, &pool_mntpnt)) { + be_print_err(gettext("be_remove_menu: pool " + "dataset (%s) is not mounted. Can't set " + "the default BE in the grub menu.\n"), be_root_pool); + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + /* Get path to boot menu */ + (void) strlcpy(menu, pool_mntpnt, sizeof (menu)); + + /* + * Check to see if this system supports grub + */ + if (be_has_grub()) + (void) strlcat(menu, BE_GRUB_MENU, sizeof (menu)); + else + (void) strlcat(menu, BE_SPARC_MENU, sizeof (menu)); + + /* Get handle to boot menu file */ + if ((ret = be_open_menu(be_root_pool, pool_mntpnt, menu, &menu_fp, "r", + B_TRUE)) != BE_SUCCESS) { + goto cleanup; + } else if (menu_fp == NULL) { + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + free(pool_mntpnt); + pool_mntpnt = NULL; + + /* Grab the stats of the original menu file */ + if (stat(menu, &sb) != 0) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "failed to stat file %s: %s\n"), menu, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + /* Create a tmp file for the modified menu.lst */ + tmp_menu_len = strlen(menu) + 7; + if ((tmp_menu = (char *)malloc(tmp_menu_len)) == NULL) { + be_print_err(gettext("be_remove_menu: malloc failed\n")); + ret = BE_ERR_NOMEM; + goto cleanup; + } + (void) memset(tmp_menu, 0, tmp_menu_len); + (void) strlcpy(tmp_menu, menu, tmp_menu_len); + (void) strlcat(tmp_menu, "XXXXXX", tmp_menu_len); + if ((fd = mkstemp(tmp_menu)) == -1) { + err = errno; + be_print_err(gettext("be_remove_menu: mkstemp failed\n")); + ret = errno_to_be_err(err); + free(tmp_menu); + tmp_menu = NULL; + goto cleanup; + } + if ((tmp_menu_fp = fdopen(fd, "w")) == NULL) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "could not open tmp file for write: %s\n"), strerror(err)); + (void) close(fd); + ret = errno_to_be_err(err); + goto cleanup; + } + + while (fgets(menu_buf, BUFSIZ, menu_fp)) { + char tline [BUFSIZ]; + char *tok = NULL; + + (void) strlcpy(tline, menu_buf, sizeof (tline)); + + /* Tokenize line */ + tok = strtok(tline, BE_WHITE_SPACE); + + if (tok == NULL || tok[0] == '#') { + /* Found empty line or comment line */ + if (do_buffer) { + /* Buffer this line */ + if ((buffer = (char **)realloc(buffer, + sizeof (char *)*(nlines + 1))) == NULL) { + ret = BE_ERR_NOMEM; + goto cleanup; + } + if ((buffer[nlines++] = strdup(menu_buf)) + == NULL) { + ret = BE_ERR_NOMEM; + goto cleanup; + } + + } else if (write || strncmp(menu_buf, BE_GRUB_COMMENT, + strlen(BE_GRUB_COMMENT)) != 0) { + /* Write this line out */ + (void) fputs(menu_buf, tmp_menu_fp); + } + } else if (strcmp(tok, "default") == 0) { + /* + * Record what 'default' is set to because we might + * need to adjust this upon deleting an entry. + */ + tok = strtok(NULL, BE_WHITE_SPACE); + + if (tok != NULL) { + default_entry = atoi(tok); + } + + (void) fputs(menu_buf, tmp_menu_fp); + } else if (strcmp(tok, "title") == 0) { + /* + * If we've reached a 'title' line and do_buffer is + * is true, that means we've just buffered an entire + * entry without finding a 'bootfs' directive. We + * need to write that entry out and keep searching. + */ + if (do_buffer) { + for (i = 0; i < nlines; i++) { + (void) fputs(buffer[i], tmp_menu_fp); + free(buffer[i]); + } + free(buffer); + buffer = NULL; + nlines = 0; + } + + /* + * Turn writing off and buffering on, and increment + * our entry counter. + */ + write = B_FALSE; + do_buffer = B_TRUE; + entry_cnt++; + + /* Buffer this 'title' line */ + if ((buffer = (char **)realloc(buffer, + sizeof (char *)*(nlines + 1))) == NULL) { + ret = BE_ERR_NOMEM; + goto cleanup; + } + if ((buffer[nlines++] = strdup(menu_buf)) == NULL) { + ret = BE_ERR_NOMEM; + goto cleanup; + } + + } else if (strcmp(tok, "bootfs") == 0) { + char *bootfs = NULL; + + /* + * Found a 'bootfs' line. See if it matches the + * BE we're looking for. + */ + if ((bootfs = strtok(NULL, BE_WHITE_SPACE)) == NULL || + strcmp(bootfs, be_root_ds) != 0) { + /* + * Either there's nothing after the 'bootfs' + * or this is not the BE we're looking for, + * write out the line(s) we've buffered since + * finding the title. + */ + for (i = 0; i < nlines; i++) { + (void) fputs(buffer[i], tmp_menu_fp); + free(buffer[i]); + } + free(buffer); + buffer = NULL; + nlines = 0; + + /* + * Turn writing back on, and turn off buffering + * since this isn't the entry we're looking + * for. + */ + write = B_TRUE; + do_buffer = B_FALSE; + + /* Write this 'bootfs' line out. */ + (void) fputs(menu_buf, tmp_menu_fp); + } else { + /* + * Found the entry we're looking for. + * Record its entry number, increment the + * number of entries we've deleted, and turn + * writing off. Also, throw away the lines + * we've buffered for this entry so far, we + * don't need them. + */ + entry_del = entry_cnt - 1; + num_entry_del++; + write = B_FALSE; + do_buffer = B_FALSE; + + for (i = 0; i < nlines; i++) { + free(buffer[i]); + } + free(buffer); + buffer = NULL; + nlines = 0; + } + } else { + if (do_buffer) { + /* Buffer this line */ + if ((buffer = (char **)realloc(buffer, + sizeof (char *)*(nlines + 1))) == NULL) { + ret = BE_ERR_NOMEM; + goto cleanup; + } + if ((buffer[nlines++] = strdup(menu_buf)) + == NULL) { + ret = BE_ERR_NOMEM; + goto cleanup; + } + } else if (write) { + /* Write this line out */ + (void) fputs(menu_buf, tmp_menu_fp); + } + } + } + + (void) fclose(menu_fp); + menu_fp = NULL; + (void) fclose(tmp_menu_fp); + tmp_menu_fp = NULL; + + /* Copy the modified menu.lst into place */ + if (rename(tmp_menu, menu) != 0) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "failed to rename file %s to %s: %s\n"), + tmp_menu, menu, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + free(tmp_menu); + tmp_menu = NULL; + + /* + * If we've removed an entry, see if we need to + * adjust the default value in the menu.lst. If the + * entry we've deleted comes before the default entry + * we need to adjust the default value accordingly. + * + * be_has_grub is used here to check to see if this system + * supports grub. + */ + if (be_has_grub() && num_entry_del > 0) { + if (entry_del <= default_entry) { + default_entry = default_entry - num_entry_del; + if (default_entry < 0) + default_entry = 0; + + /* + * Adjust the default value by rewriting the + * menu.lst file. This may be overkill, but to + * preserve the location of the 'default' entry + * in the file, we need to do this. + */ + + /* Get handle to boot menu file */ + if ((menu_fp = fopen(menu, "r")) == NULL) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "failed to open menu.lst (%s): %s\n"), + menu, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + /* Create a tmp file for the modified menu.lst */ + tmp_menu_len = strlen(menu) + 7; + if ((tmp_menu = (char *)malloc(tmp_menu_len)) + == NULL) { + be_print_err(gettext("be_remove_menu: " + "malloc failed\n")); + ret = BE_ERR_NOMEM; + goto cleanup; + } + (void) memset(tmp_menu, 0, tmp_menu_len); + (void) strlcpy(tmp_menu, menu, tmp_menu_len); + (void) strlcat(tmp_menu, "XXXXXX", tmp_menu_len); + if ((fd = mkstemp(tmp_menu)) == -1) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "mkstemp failed: %s\n"), strerror(err)); + ret = errno_to_be_err(err); + free(tmp_menu); + tmp_menu = NULL; + goto cleanup; + } + if ((tmp_menu_fp = fdopen(fd, "w")) == NULL) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "could not open tmp file for write: %s\n"), + strerror(err)); + (void) close(fd); + ret = errno_to_be_err(err); + goto cleanup; + } + + while (fgets(menu_buf, BUFSIZ, menu_fp)) { + char tline [BUFSIZ]; + char *tok = NULL; + + (void) strlcpy(tline, menu_buf, sizeof (tline)); + + /* Tokenize line */ + tok = strtok(tline, BE_WHITE_SPACE); + + if (tok == NULL) { + /* Found empty line, write it out */ + (void) fputs(menu_buf, tmp_menu_fp); + } else if (strcmp(tok, "default") == 0) { + /* Found the default line, adjust it */ + (void) snprintf(tline, sizeof (tline), + "default %d\n", default_entry); + + (void) fputs(tline, tmp_menu_fp); + } else { + /* Pass through all other lines */ + (void) fputs(menu_buf, tmp_menu_fp); + } + } + + (void) fclose(menu_fp); + menu_fp = NULL; + (void) fclose(tmp_menu_fp); + tmp_menu_fp = NULL; + + /* Copy the modified menu.lst into place */ + if (rename(tmp_menu, menu) != 0) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "failed to rename file %s to %s: %s\n"), + tmp_menu, menu, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + free(tmp_menu); + tmp_menu = NULL; + } + } + + /* Set the perms and ownership of the updated file */ + if (chmod(menu, sb.st_mode) != 0) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "failed to chmod %s: %s\n"), menu, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + if (chown(menu, sb.st_uid, sb.st_gid) != 0) { + err = errno; + be_print_err(gettext("be_remove_menu: " + "failed to chown %s: %s\n"), menu, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + +cleanup: + if (pool_mounted) { + int err = BE_SUCCESS; + err = be_unmount_pool(zhp, ptmp_mntpnt, orig_mntpnt); + if (ret == BE_SUCCESS) + ret = err; + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(zhp); + + free(buffer); + if (menu_fp != NULL) + (void) fclose(menu_fp); + if (tmp_menu_fp != NULL) + (void) fclose(tmp_menu_fp); + if (tmp_menu != NULL) { + (void) unlink(tmp_menu); + free(tmp_menu); + } + + return (ret); +} + +/* + * Function: be_default_grub_bootfs + * Description: This function returns the dataset in the default entry of + * the grub menu. If no default entry is found with a valid bootfs + * entry NULL is returned. + * Parameters: + * be_root_pool - This is the name of the root pool where the + * grub menu can be found. + * def_bootfs - This is used to pass back the bootfs string. On + * error NULL is returned here. + * Returns: + * Success - BE_SUCCESS is returned. + * Failure - a be_errno_t is returned. + * Scope: + * Semi-private (library wide use only) + */ +int +be_default_grub_bootfs(const char *be_root_pool, char **def_bootfs) +{ + zfs_handle_t *zhp = NULL; + char grub_file[MAXPATHLEN]; + FILE *menu_fp; + char line[BUFSIZ]; + char *pool_mntpnt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + int default_entry = 0, entries = 0; + int found_default = 0; + int ret = BE_SUCCESS; + boolean_t pool_mounted = B_FALSE; + + errno = 0; + + /* + * Check to see if this system supports grub + */ + if (!be_has_grub()) { + be_print_err(gettext("be_default_grub_bootfs: operation " + "not supported on this architecture\n")); + return (BE_ERR_NOTSUP); + } + + *def_bootfs = NULL; + + /* Get handle to pool dataset */ + if ((zhp = zfs_open(g_zfs, be_root_pool, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_default_grub_bootfs: " + "failed to open pool dataset for %s: %s"), + be_root_pool, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if ((ret = be_mount_pool(zhp, &ptmp_mntpnt, &orig_mntpnt, + &pool_mounted)) != BE_SUCCESS) { + be_print_err(gettext("be_default_grub_bootfs: pool dataset " + "(%s) could not be mounted\n"), be_root_pool); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(zhp, &pool_mntpnt)) { + be_print_err(gettext("be_default_grub_bootfs: failed " + "to get mount point for the root pool. Can't set " + "the default BE in the grub menu.\n")); + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + (void) snprintf(grub_file, MAXPATHLEN, "%s%s", + pool_mntpnt, BE_GRUB_MENU); + + if ((ret = be_open_menu((char *)be_root_pool, pool_mntpnt, grub_file, + &menu_fp, "r", B_FALSE)) != BE_SUCCESS) { + goto cleanup; + } else if (menu_fp == NULL) { + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + free(pool_mntpnt); + pool_mntpnt = NULL; + + while (fgets(line, BUFSIZ, menu_fp)) { + char *tok = strtok(line, BE_WHITE_SPACE); + + if (tok != NULL && tok[0] != '#') { + if (!found_default) { + if (strcmp(tok, "default") == 0) { + tok = strtok(NULL, BE_WHITE_SPACE); + if (tok != NULL) { + default_entry = atoi(tok); + rewind(menu_fp); + found_default = 1; + } + } + continue; + } + if (strcmp(tok, "title") == 0) { + entries++; + } else if (default_entry == entries - 1) { + if (strcmp(tok, "bootfs") == 0) { + tok = strtok(NULL, BE_WHITE_SPACE); + (void) fclose(menu_fp); + + if (tok == NULL) { + ret = BE_SUCCESS; + goto cleanup; + } + + if ((*def_bootfs = strdup(tok)) != + NULL) { + ret = BE_SUCCESS; + goto cleanup; + } + be_print_err(gettext( + "be_default_grub_bootfs: " + "memory allocation failed\n")); + ret = BE_ERR_NOMEM; + goto cleanup; + } + } else if (default_entry < entries - 1) { + /* + * no bootfs entry for the default entry. + */ + break; + } + } + } + (void) fclose(menu_fp); + +cleanup: + if (pool_mounted) { + int err = BE_SUCCESS; + err = be_unmount_pool(zhp, ptmp_mntpnt, orig_mntpnt); + if (ret == BE_SUCCESS) + ret = err; + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_change_grub_default + * Description: This function takes two parameters. These are the name of + * the BE we want to have as the default booted in the grub + * menu and the root pool where the path to the grub menu exists. + * The code takes this and finds the BE's entry in the grub menu + * and changes the default entry to point to that entry in the + * list. + * Parameters: + * be_name - This is the name of the BE wanted as the default + * for the next boot. + * be_root_pool - This is the name of the root pool where the + * grub menu can be found. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_change_grub_default(char *be_name, char *be_root_pool) +{ + zfs_handle_t *zhp = NULL; + char grub_file[MAXPATHLEN]; + char *temp_grub; + char *pool_mntpnt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + char line[BUFSIZ]; + char temp_line[BUFSIZ]; + char be_root_ds[MAXPATHLEN]; + FILE *grub_fp = NULL; + FILE *temp_fp = NULL; + struct stat sb; + int temp_grub_len = 0; + int fd, entries = 0; + int err = 0; + int ret = BE_SUCCESS; + boolean_t found_default = B_FALSE; + boolean_t pool_mounted = B_FALSE; + + errno = 0; + + /* + * Check to see if this system supports grub + */ + if (!be_has_grub()) { + be_print_err(gettext("be_change_grub_default: operation " + "not supported on this architecture\n")); + return (BE_ERR_NOTSUP); + } + + /* Generate string for BE's root dataset */ + be_make_root_ds(be_root_pool, be_name, be_root_ds, sizeof (be_root_ds)); + + /* Get handle to pool dataset */ + if ((zhp = zfs_open(g_zfs, be_root_pool, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_change_grub_default: " + "failed to open pool dataset for %s: %s"), + be_root_pool, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if ((ret = be_mount_pool(zhp, &ptmp_mntpnt, &orig_mntpnt, + &pool_mounted)) != BE_SUCCESS) { + be_print_err(gettext("be_change_grub_default: pool dataset " + "(%s) could not be mounted\n"), be_root_pool); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(zhp, &pool_mntpnt)) { + be_print_err(gettext("be_change_grub_default: pool " + "dataset (%s) is not mounted. Can't set " + "the default BE in the grub menu.\n"), be_root_pool); + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + (void) snprintf(grub_file, MAXPATHLEN, "%s%s", + pool_mntpnt, BE_GRUB_MENU); + + if ((ret = be_open_menu(be_root_pool, pool_mntpnt, grub_file, + &grub_fp, "r+", B_TRUE)) != BE_SUCCESS) { + goto cleanup; + } else if (grub_fp == NULL) { + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + free(pool_mntpnt); + pool_mntpnt = NULL; + + /* Grab the stats of the original menu file */ + if (stat(grub_file, &sb) != 0) { + err = errno; + be_print_err(gettext("be_change_grub_default: " + "failed to stat file %s: %s\n"), grub_file, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + /* Create a tmp file for the modified menu.lst */ + temp_grub_len = strlen(grub_file) + 7; + if ((temp_grub = (char *)malloc(temp_grub_len)) == NULL) { + be_print_err(gettext("be_change_grub_default: " + "malloc failed\n")); + ret = BE_ERR_NOMEM; + goto cleanup; + } + (void) memset(temp_grub, 0, temp_grub_len); + (void) strlcpy(temp_grub, grub_file, temp_grub_len); + (void) strlcat(temp_grub, "XXXXXX", temp_grub_len); + if ((fd = mkstemp(temp_grub)) == -1) { + err = errno; + be_print_err(gettext("be_change_grub_default: " + "mkstemp failed: %s\n"), strerror(err)); + ret = errno_to_be_err(err); + free(temp_grub); + temp_grub = NULL; + goto cleanup; + } + if ((temp_fp = fdopen(fd, "w")) == NULL) { + err = errno; + be_print_err(gettext("be_change_grub_default: " + "failed to open %s file: %s\n"), + temp_grub, strerror(err)); + (void) close(fd); + ret = errno_to_be_err(err); + goto cleanup; + } + + while (fgets(line, BUFSIZ, grub_fp)) { + char *tok = strtok(line, BE_WHITE_SPACE); + + if (tok == NULL || tok[0] == '#') { + continue; + } else if (strcmp(tok, "title") == 0) { + entries++; + continue; + } else if (strcmp(tok, "bootfs") == 0) { + char *bootfs = strtok(NULL, BE_WHITE_SPACE); + if (bootfs == NULL) + continue; + + if (strcmp(bootfs, be_root_ds) == 0) { + found_default = B_TRUE; + break; + } + } + } + + if (!found_default) { + be_print_err(gettext("be_change_grub_default: failed " + "to find entry for %s in the grub menu\n"), + be_name); + ret = BE_ERR_BE_NOENT; + goto cleanup; + } + + rewind(grub_fp); + + while (fgets(line, BUFSIZ, grub_fp)) { + char *tok = NULL; + + (void) strncpy(temp_line, line, BUFSIZ); + + if ((tok = strtok(temp_line, BE_WHITE_SPACE)) != NULL && + strcmp(tok, "default") == 0) { + (void) snprintf(temp_line, BUFSIZ, "default %d\n", + entries - 1 >= 0 ? entries - 1 : 0); + (void) fputs(temp_line, temp_fp); + } else { + (void) fputs(line, temp_fp); + } + } + + (void) fclose(grub_fp); + grub_fp = NULL; + (void) fclose(temp_fp); + temp_fp = NULL; + + if (rename(temp_grub, grub_file) != 0) { + err = errno; + be_print_err(gettext("be_change_grub_default: " + "failed to rename file %s to %s: %s\n"), + temp_grub, grub_file, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + free(temp_grub); + temp_grub = NULL; + + /* Set the perms and ownership of the updated file */ + if (chmod(grub_file, sb.st_mode) != 0) { + err = errno; + be_print_err(gettext("be_change_grub_default: " + "failed to chmod %s: %s\n"), grub_file, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + if (chown(grub_file, sb.st_uid, sb.st_gid) != 0) { + err = errno; + be_print_err(gettext("be_change_grub_default: " + "failed to chown %s: %s\n"), grub_file, strerror(err)); + ret = errno_to_be_err(err); + } + +cleanup: + if (pool_mounted) { + int err = BE_SUCCESS; + err = be_unmount_pool(zhp, ptmp_mntpnt, orig_mntpnt); + if (ret == BE_SUCCESS) + ret = err; + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(zhp); + if (grub_fp != NULL) + (void) fclose(grub_fp); + if (temp_fp != NULL) + (void) fclose(temp_fp); + if (temp_grub != NULL) { + (void) unlink(temp_grub); + free(temp_grub); + } + + return (ret); +} + +/* + * Function: be_update_menu + * Description: This function is used by be_rename to change the BE name in + * an existing entry in the grub menu to the new name of the BE. + * Parameters: + * be_orig_name - the original name of the BE + * be_new_name - the new name the BE is being renameed to. + * be_root_pool - The pool which contains the grub menu + * boot_pool - the pool where the BE is, if different than + * the pool containing the boot menu. If this is + * NULL it will be set to be_root_pool. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_update_menu(char *be_orig_name, char *be_new_name, char *be_root_pool, + char *boot_pool) +{ + zfs_handle_t *zhp = NULL; + char menu_file[MAXPATHLEN]; + char be_root_ds[MAXPATHLEN]; + char be_new_root_ds[MAXPATHLEN]; + char line[BUFSIZ]; + char *pool_mntpnt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + char *temp_menu = NULL; + FILE *menu_fp = NULL; + FILE *new_fp = NULL; + struct stat sb; + int temp_menu_len = 0; + int tmp_fd; + int ret = BE_SUCCESS; + int err = 0; + boolean_t pool_mounted = B_FALSE; + + errno = 0; + + if (boot_pool == NULL) + boot_pool = be_root_pool; + + if ((zhp = zfs_open(g_zfs, be_root_pool, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_update_menu: failed to open " + "pool dataset for %s: %s\n"), be_root_pool, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if ((ret = be_mount_pool(zhp, &ptmp_mntpnt, &orig_mntpnt, + &pool_mounted)) != BE_SUCCESS) { + be_print_err(gettext("be_update_menu: pool dataset " + "(%s) could not be mounted\n"), be_root_pool); + ZFS_CLOSE(zhp); + return (ret); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(zhp, &pool_mntpnt)) { + be_print_err(gettext("be_update_menu: failed " + "to get mount point for the root pool. Can't set " + "the default BE in the grub menu.\n")); + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + /* + * Check to see if this system supports grub + */ + if (be_has_grub()) { + (void) snprintf(menu_file, sizeof (menu_file), + "%s%s", pool_mntpnt, BE_GRUB_MENU); + } else { + (void) snprintf(menu_file, sizeof (menu_file), + "%s%s", pool_mntpnt, BE_SPARC_MENU); + } + + be_make_root_ds(be_root_pool, be_orig_name, be_root_ds, + sizeof (be_root_ds)); + be_make_root_ds(be_root_pool, be_new_name, be_new_root_ds, + sizeof (be_new_root_ds)); + + if ((ret = be_open_menu(be_root_pool, pool_mntpnt, menu_file, + &menu_fp, "r", B_TRUE)) != BE_SUCCESS) { + goto cleanup; + } else if (menu_fp == NULL) { + ret = BE_ERR_NO_MENU; + goto cleanup; + } + + free(pool_mntpnt); + pool_mntpnt = NULL; + + /* Grab the stat of the original menu file */ + if (stat(menu_file, &sb) != 0) { + err = errno; + be_print_err(gettext("be_update_menu: " + "failed to stat file %s: %s\n"), menu_file, strerror(err)); + (void) fclose(menu_fp); + ret = errno_to_be_err(err); + goto cleanup; + } + + /* Create tmp file for modified menu.lst */ + temp_menu_len = strlen(menu_file) + 7; + if ((temp_menu = (char *)malloc(temp_menu_len)) + == NULL) { + be_print_err(gettext("be_update_menu: " + "malloc failed\n")); + (void) fclose(menu_fp); + ret = BE_ERR_NOMEM; + goto cleanup; + } + (void) memset(temp_menu, 0, temp_menu_len); + (void) strlcpy(temp_menu, menu_file, temp_menu_len); + (void) strlcat(temp_menu, "XXXXXX", temp_menu_len); + if ((tmp_fd = mkstemp(temp_menu)) == -1) { + err = errno; + be_print_err(gettext("be_update_menu: " + "mkstemp failed: %s\n"), strerror(err)); + (void) fclose(menu_fp); + free(temp_menu); + ret = errno_to_be_err(err); + goto cleanup; + } + if ((new_fp = fdopen(tmp_fd, "w")) == NULL) { + err = errno; + be_print_err(gettext("be_update_menu: " + "fdopen failed: %s\n"), strerror(err)); + (void) close(tmp_fd); + (void) fclose(menu_fp); + free(temp_menu); + ret = errno_to_be_err(err); + goto cleanup; + } + + while (fgets(line, BUFSIZ, menu_fp)) { + char tline[BUFSIZ]; + char new_line[BUFSIZ]; + char *c = NULL; + + (void) strlcpy(tline, line, sizeof (tline)); + + /* Tokenize line */ + c = strtok(tline, BE_WHITE_SPACE); + + if (c == NULL) { + /* Found empty line, write it out. */ + (void) fputs(line, new_fp); + } else if (c[0] == '#') { + /* Found a comment line, write it out. */ + (void) fputs(line, new_fp); + } else if (strcmp(c, "title") == 0) { + char *name = NULL; + char *desc = NULL; + + /* + * Found a 'title' line, parse out BE name or + * the description. + */ + name = strtok(NULL, BE_WHITE_SPACE); + + if (name == NULL) { + /* + * Nothing after 'title', just push + * this line through + */ + (void) fputs(line, new_fp); + } else { + /* + * Grab the remainder of the title which + * could be a multi worded description + */ + desc = strtok(NULL, "\n"); + + if (strcmp(name, be_orig_name) == 0) { + /* + * The first token of the title is + * the old BE name, replace it with + * the new one, and write it out + * along with the remainder of + * description if there is one. + */ + if (desc) { + (void) snprintf(new_line, + sizeof (new_line), + "title %s %s\n", + be_new_name, desc); + } else { + (void) snprintf(new_line, + sizeof (new_line), + "title %s\n", be_new_name); + } + + (void) fputs(new_line, new_fp); + } else { + (void) fputs(line, new_fp); + } + } + } else if (strcmp(c, "bootfs") == 0) { + /* + * Found a 'bootfs' line, parse out the BE root + * dataset value. + */ + char *root_ds = strtok(NULL, BE_WHITE_SPACE); + + if (root_ds == NULL) { + /* + * Nothing after 'bootfs', just push + * this line through + */ + (void) fputs(line, new_fp); + } else { + /* + * If this bootfs is the one we're renaming, + * write out the new root dataset value + */ + if (strcmp(root_ds, be_root_ds) == 0) { + (void) snprintf(new_line, + sizeof (new_line), "bootfs %s\n", + be_new_root_ds); + + (void) fputs(new_line, new_fp); + } else { + (void) fputs(line, new_fp); + } + } + } else { + /* + * Found some other line we don't care + * about, write it out. + */ + (void) fputs(line, new_fp); + } + } + + (void) fclose(menu_fp); + (void) fclose(new_fp); + (void) close(tmp_fd); + + if (rename(temp_menu, menu_file) != 0) { + err = errno; + be_print_err(gettext("be_update_menu: " + "failed to rename file %s to %s: %s\n"), + temp_menu, menu_file, strerror(err)); + ret = errno_to_be_err(err); + } + free(temp_menu); + + /* Set the perms and ownership of the updated file */ + if (chmod(menu_file, sb.st_mode) != 0) { + err = errno; + be_print_err(gettext("be_update_menu: " + "failed to chmod %s: %s\n"), menu_file, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + if (chown(menu_file, sb.st_uid, sb.st_gid) != 0) { + err = errno; + be_print_err(gettext("be_update_menu: " + "failed to chown %s: %s\n"), menu_file, strerror(err)); + ret = errno_to_be_err(err); + } + +cleanup: + if (pool_mounted) { + int err = BE_SUCCESS; + err = be_unmount_pool(zhp, ptmp_mntpnt, orig_mntpnt); + if (ret == BE_SUCCESS) + ret = err; + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_has_menu_entry + * Description: Checks to see if the BEs root dataset has an entry in the grub + * menu. + * Parameters: + * be_dataset - The root dataset of the BE + * be_root_pool - The pool which contains the boot menu + * entry - A pointer the the entry number of the BE if found. + * Returns: + * B_TRUE - Success + * B_FALSE - Failure + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +be_has_menu_entry(char *be_dataset, char *be_root_pool, int *entry) +{ + zfs_handle_t *zhp = NULL; + char menu_file[MAXPATHLEN]; + FILE *menu_fp; + char line[BUFSIZ]; + char *last; + char *rpool_mntpnt = NULL; + char *ptmp_mntpnt = NULL; + char *orig_mntpnt = NULL; + int ent_num = 0; + boolean_t ret = 0; + boolean_t pool_mounted = B_FALSE; + + + /* + * Check to see if this system supports grub + */ + if ((zhp = zfs_open(g_zfs, be_root_pool, ZFS_TYPE_DATASET)) == NULL) { + be_print_err(gettext("be_has_menu_entry: failed to open " + "pool dataset for %s: %s\n"), be_root_pool, + libzfs_error_description(g_zfs)); + return (B_FALSE); + } + + /* + * Check to see if the pool's dataset is mounted. If it isn't we'll + * attempt to mount it. + */ + if (be_mount_pool(zhp, &ptmp_mntpnt, &orig_mntpnt, + &pool_mounted) != 0) { + be_print_err(gettext("be_has_menu_entry: pool dataset " + "(%s) could not be mounted\n"), be_root_pool); + ZFS_CLOSE(zhp); + return (B_FALSE); + } + + /* + * Get the mountpoint for the root pool dataset. + */ + if (!zfs_is_mounted(zhp, &rpool_mntpnt)) { + be_print_err(gettext("be_has_menu_entry: pool " + "dataset (%s) is not mounted. Can't set " + "the default BE in the grub menu.\n"), be_root_pool); + ret = B_FALSE; + goto cleanup; + } + + if (be_has_grub()) { + (void) snprintf(menu_file, MAXPATHLEN, "/%s%s", + rpool_mntpnt, BE_GRUB_MENU); + } else { + (void) snprintf(menu_file, MAXPATHLEN, "/%s%s", + rpool_mntpnt, BE_SPARC_MENU); + } + + if (be_open_menu(be_root_pool, rpool_mntpnt, menu_file, &menu_fp, "r", + B_FALSE) != 0) { + ret = B_FALSE; + goto cleanup; + } else if (menu_fp == NULL) { + ret = B_FALSE; + goto cleanup; + } + + free(rpool_mntpnt); + rpool_mntpnt = NULL; + + while (fgets(line, BUFSIZ, menu_fp)) { + char *tok = strtok_r(line, BE_WHITE_SPACE, &last); + + if (tok != NULL && tok[0] != '#') { + if (strcmp(tok, "bootfs") == 0) { + tok = strtok_r(last, BE_WHITE_SPACE, &last); + if (tok != NULL && strcmp(tok, + be_dataset) == 0) { + (void) fclose(menu_fp); + /* + * The entry number needs to be + * decremented here because the title + * will always be the first line for + * an entry. Because of this we'll + * always be off by one entry when we + * check for bootfs. + */ + *entry = ent_num - 1; + ret = B_TRUE; + goto cleanup; + } + } else if (strcmp(tok, "title") == 0) + ent_num++; + } + } + +cleanup: + if (pool_mounted) { + (void) be_unmount_pool(zhp, ptmp_mntpnt, orig_mntpnt); + free(orig_mntpnt); + free(ptmp_mntpnt); + } + ZFS_CLOSE(zhp); + (void) fclose(menu_fp); + return (ret); +} + +/* + * Function: be_update_vfstab + * Description: This function digs into a BE's vfstab and updates all + * entries with file systems listed in be_fs_list_data_t. + * The entry's root container dataset and be_name will be + * updated with the parameters passed in. + * Parameters: + * be_name - name of BE to update + * old_rc_loc - dataset under which the root container dataset + * of the old BE resides in. + * new_rc_loc - dataset under which the root container dataset + * of the new BE resides in. + * fld - be_fs_list_data_t pointer providing the list of + * file systems to look for in vfstab. + * mountpoint - directory of where BE is currently mounted. + * If NULL, then BE is not currently mounted. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_update_vfstab(char *be_name, char *old_rc_loc, char *new_rc_loc, + be_fs_list_data_t *fld, char *mountpoint) +{ + char *tmp_mountpoint = NULL; + char alt_vfstab[MAXPATHLEN]; + int ret = BE_SUCCESS, err = BE_SUCCESS; + + if (fld == NULL || fld->fs_list == NULL || fld->fs_num == 0) + return (BE_SUCCESS); + + /* If BE not already mounted, mount the BE */ + if (mountpoint == NULL) { + if ((ret = _be_mount(be_name, &tmp_mountpoint, + BE_MOUNT_FLAG_NO_ZONES)) != BE_SUCCESS) { + be_print_err(gettext("be_update_vfstab: " + "failed to mount BE (%s)\n"), be_name); + return (ret); + } + } else { + tmp_mountpoint = mountpoint; + } + + /* Get string for vfstab in the mounted BE. */ + (void) snprintf(alt_vfstab, sizeof (alt_vfstab), "%s/etc/vfstab", + tmp_mountpoint); + + /* Update the vfstab */ + ret = _update_vfstab(alt_vfstab, be_name, old_rc_loc, new_rc_loc, + fld); + + /* Unmount BE if we mounted it */ + if (mountpoint == NULL) { + if ((err = _be_unmount(be_name, 0)) == BE_SUCCESS) { + /* Remove temporary mountpoint */ + (void) rmdir(tmp_mountpoint); + } else { + be_print_err(gettext("be_update_vfstab: " + "failed to unmount BE %s mounted at %s\n"), + be_name, tmp_mountpoint); + if (ret == BE_SUCCESS) + ret = err; + } + + free(tmp_mountpoint); + } + + return (ret); +} + +/* + * Function: be_update_zone_vfstab + * Description: This function digs into a zone BE's vfstab and updates all + * entries with file systems listed in be_fs_list_data_t. + * The entry's root container dataset and be_name will be + * updated with the parameters passed in. + * Parameters: + * zhp - zfs_handle_t pointer to zone root dataset. + * be_name - name of zone BE to update + * old_rc_loc - dataset under which the root container dataset + * of the old zone BE resides in. + * new_rc_loc - dataset under which the root container dataset + * of the new zone BE resides in. + * fld - be_fs_list_data_t pointer providing the list of + * file systems to look for in vfstab. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_update_zone_vfstab(zfs_handle_t *zhp, char *be_name, char *old_rc_loc, + char *new_rc_loc, be_fs_list_data_t *fld) +{ + be_mount_data_t md = { 0 }; + be_unmount_data_t ud = { 0 }; + char alt_vfstab[MAXPATHLEN]; + boolean_t mounted_here = B_FALSE; + int ret = BE_SUCCESS; + + /* + * If zone root not already mounted, mount it at a + * temporary location. + */ + if (!zfs_is_mounted(zhp, &md.altroot)) { + /* Generate temporary mountpoint to mount zone root */ + if ((ret = be_make_tmp_mountpoint(&md.altroot)) != BE_SUCCESS) { + be_print_err(gettext("be_update_zone_vfstab: " + "failed to make temporary mountpoint to " + "mount zone root\n")); + return (ret); + } + + if (be_mount_zone_root(zhp, &md) != BE_SUCCESS) { + be_print_err(gettext("be_update_zone_vfstab: " + "failed to mount zone root %s\n"), + zfs_get_name(zhp)); + free(md.altroot); + return (BE_ERR_MOUNT_ZONEROOT); + } + mounted_here = B_TRUE; + } + + /* Get string from vfstab in the mounted zone BE */ + (void) snprintf(alt_vfstab, sizeof (alt_vfstab), "%s/etc/vfstab", + md.altroot); + + /* Update the vfstab */ + ret = _update_vfstab(alt_vfstab, be_name, old_rc_loc, new_rc_loc, + fld); + + /* Unmount zone root if we mounted it */ + if (mounted_here) { + ud.force = B_TRUE; + + if (be_unmount_zone_root(zhp, &ud) == BE_SUCCESS) { + /* Remove the temporary mountpoint */ + (void) rmdir(md.altroot); + } else { + be_print_err(gettext("be_update_zone_vfstab: " + "failed to unmount zone root %s from %s\n"), + zfs_get_name(zhp), md.altroot); + if (ret == 0) + ret = BE_ERR_UMOUNT_ZONEROOT; + } + } + + free(md.altroot); + return (ret); +} + +/* + * Function: be_auto_snap_name + * Description: Generate an auto snapshot name constructed based on the + * current date and time. The auto snapshot name is of the form: + * + * <date>-<time> + * + * where <date> is in ISO standard format, so the resultant name + * is of the form: + * + * %Y-%m-%d-%H:%M:%S + * + * Parameters: + * None + * Returns: + * Success - pointer to auto generated snapshot name. The name + * is allocated in heap storage so the caller is + * responsible for free'ing the name. + * Failure - NULL + * Scope: + * Semi-private (library wide use only) + */ +char * +be_auto_snap_name(void) +{ + time_t utc_tm = NULL; + struct tm *gmt_tm = NULL; + char gmt_time_str[64]; + char *auto_snap_name = NULL; + + if (time(&utc_tm) == -1) { + be_print_err(gettext("be_auto_snap_name: time() failed\n")); + return (NULL); + } + + if ((gmt_tm = gmtime(&utc_tm)) == NULL) { + be_print_err(gettext("be_auto_snap_name: gmtime() failed\n")); + return (NULL); + } + + (void) strftime(gmt_time_str, sizeof (gmt_time_str), "%F-%T", gmt_tm); + + if ((auto_snap_name = strdup(gmt_time_str)) == NULL) { + be_print_err(gettext("be_auto_snap_name: " + "memory allocation failed\n")); + return (NULL); + } + + return (auto_snap_name); +} + +/* + * Function: be_auto_be_name + * Description: Generate an auto BE name constructed based on the BE name + * of the original BE being cloned. + * Parameters: + * obe_name - name of the original BE being cloned. + * Returns: + * Success - pointer to auto generated BE name. The name + * is allocated in heap storage so the caller is + * responsible for free'ing the name. + * Failure - NULL + * Scope: + * Semi-private (library wide use only) + */ +char * +be_auto_be_name(char *obe_name) +{ + return (be_get_auto_name(obe_name, NULL, B_FALSE)); +} + +/* + * Function: be_auto_zone_be_name + * Description: Generate an auto BE name for a zone constructed based on + * the BE name of the original zone BE being cloned. + * Parameters: + * container_ds - container dataset for the zone. + * zbe_name - name of the original zone BE being cloned. + * Returns: + * Success - pointer to auto generated BE name. The name + * is allocated in heap storage so the caller is + * responsible for free'ing the name. + * Failure - NULL + * Scope: + * Semi-private (library wide use only) + */ +char * +be_auto_zone_be_name(char *container_ds, char *zbe_name) +{ + return (be_get_auto_name(zbe_name, container_ds, B_TRUE)); +} + +/* + * Function: be_valid_be_name + * Description: Validates a BE name. + * Parameters: + * be_name - name of BE to validate + * Returns: + * B_TRUE - be_name is valid + * B_FALSE - be_name is invalid + * Scope: + * Semi-private (library wide use only) + */ + +boolean_t +be_valid_be_name(const char *be_name) +{ + const char *c = NULL; + + if (be_name == NULL) + return (B_FALSE); + + /* + * A BE name must not be a multi-level dataset name. We also check + * that it does not contain the ' ' and '%' characters. The ' ' is + * a valid character for datasets, however we don't allow that in a + * BE name. The '%' is invalid, but zfs_name_valid() allows it for + * internal reasons, so we explicitly check for it here. + */ + c = be_name; + while (*c != '\0' && *c != '/' && *c != ' ' && *c != '%') + c++; + + if (*c != '\0') + return (B_FALSE); + + /* + * The BE name must comply with a zfs dataset filesystem. We also + * verify its length to be < BE_NAME_MAX_LEN. + */ + if (!zfs_name_valid(be_name, ZFS_TYPE_FILESYSTEM) || + strlen(be_name) > BE_NAME_MAX_LEN) + return (B_FALSE); + + return (B_TRUE); +} + +/* + * Function: be_valid_auto_snap_name + * Description: This function checks that a snapshot name is a valid auto + * generated snapshot name. A valid auto generated snapshot + * name is of the form: + * + * %Y-%m-%d-%H:%M:%S + * + * An older form of the auto generated snapshot name also + * included the snapshot's BE cleanup policy and a reserved + * field. Those names will also be verified by this function. + * + * Examples of valid auto snapshot names are: + * + * 2008-03-31-18:41:30 + * 2008-03-31-22:17:24 + * <policy>:-:2008:04-05-09:12:55 + * <policy>:-:2008:04-06-15:34:12 + * + * Parameters: + * name - name of the snapshot to be validated. + * Returns: + * B_TRUE - the name is a valid auto snapshot name. + * B_FALSE - the name is not a valid auto snapshot name. + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +be_valid_auto_snap_name(char *name) +{ + struct tm gmt_tm; + + char *policy = NULL; + char *reserved = NULL; + char *date = NULL; + char *c = NULL; + + /* Validate the snapshot name by converting it into utc time */ + if (strptime(name, "%Y-%m-%d-%T", &gmt_tm) != NULL && + (mktime(&gmt_tm) != -1)) { + return (B_TRUE); + } + + /* + * Validate the snapshot name against the older form of an + * auto generated snapshot name. + */ + policy = strdup(name); + + /* + * Get the first field from the snapshot name, + * which is the BE policy + */ + c = strchr(policy, ':'); + if (c == NULL) { + free(policy); + return (B_FALSE); + } + c[0] = '\0'; + + /* Validate the policy name */ + if (!valid_be_policy(policy)) { + free(policy); + return (B_FALSE); + } + + /* Get the next field, which is the reserved field. */ + if (c[1] == NULL || c[1] == '\0') { + free(policy); + return (B_FALSE); + } + reserved = c+1; + c = strchr(reserved, ':'); + if (c == NULL) { + free(policy); + return (B_FALSE); + } + c[0] = '\0'; + + /* Validate the reserved field */ + if (strcmp(reserved, "-") != 0) { + free(policy); + return (B_FALSE); + } + + /* The remaining string should be the date field */ + if (c[1] == NULL || c[1] == '\0') { + free(policy); + return (B_FALSE); + } + date = c+1; + + /* Validate the date string by converting it into utc time */ + if (strptime(date, "%Y-%m-%d-%T", &gmt_tm) == NULL || + (mktime(&gmt_tm) == -1)) { + be_print_err(gettext("be_valid_auto_snap_name: " + "invalid auto snapshot name\n")); + free(policy); + return (B_FALSE); + } + + free(policy); + return (B_TRUE); +} + +/* + * Function: be_default_policy + * Description: Temporary hardcoded policy support. This function returns + * the default policy type to be used to create a BE or a BE + * snapshot. + * Parameters: + * None + * Returns: + * Name of default BE policy. + * Scope: + * Semi-private (library wide use only) + */ +char * +be_default_policy(void) +{ + return (BE_PLCY_STATIC); +} + +/* + * Function: valid_be_policy + * Description: Temporary hardcoded policy support. This function valids + * whether a policy is a valid known policy or not. + * Paramters: + * policy - name of policy to validate. + * Returns: + * B_TRUE - policy is a valid. + * B_FALSE - policy is invalid. + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +valid_be_policy(char *policy) +{ + if (policy == NULL) + return (B_FALSE); + + if (strcmp(policy, BE_PLCY_STATIC) == 0 || + strcmp(policy, BE_PLCY_VOLATILE) == 0) { + return (B_TRUE); + } + + return (B_FALSE); +} + +/* + * Function: be_print_err + * Description: This function prints out error messages if do_print is + * set to B_TRUE or if the BE_PRINT_ERR environment variable + * is set to true. + * Paramters: + * prnt_str - the string we wish to print and any arguments + * for the format of that string. + * Returns: + * void + * Scope: + * Semi-private (library wide use only) + */ +void +be_print_err(char *prnt_str, ...) +{ + va_list ap; + char buf[BUFSIZ]; + char *env_buf; + static boolean_t env_checked = B_FALSE; + + if (!env_checked) { + if ((env_buf = getenv("BE_PRINT_ERR")) != NULL) { + if (strcasecmp(env_buf, "true") == 0) { + do_print = B_TRUE; + } + } + env_checked = B_TRUE; + } + + if (do_print) { + va_start(ap, prnt_str); + /* LINTED variable format specifier */ + (void) vsnprintf(buf, BUFSIZ, prnt_str, ap); + (void) fputs(buf, stderr); + va_end(ap); + } +} + +/* + * Function: be_find_current_be + * Description: Find the currently "active" BE. Fill in the + * passed in be_transaction_data_t reference with the + * active BE's data. + * Paramters: + * none + * Returns: + * BE_SUCCESS - Success + * be_errnot_t - Failure + * Scope: + * Semi-private (library wide use only) + * Notes: + * The caller is responsible for initializing the libzfs handle + * and freeing the memory used by the active be_name. + */ +int +be_find_current_be(be_transaction_data_t *bt) +{ + int zret; + + if ((zret = zpool_iter(g_zfs, be_zpool_find_current_be_callback, + bt)) == 0) { + be_print_err(gettext("be_find_current_be: failed to " + "find current BE name\n")); + return (BE_ERR_BE_NOENT); + } else if (zret < 0) { + be_print_err(gettext("be_find_current_be: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + return (BE_SUCCESS); +} + +/* + * Function: be_zpool_find_current_be_callback + * Description: Callback function used to iterate through all existing pools + * to find the BE that is the currently booted BE. + * Parameters: + * zlp - zpool_handle_t pointer to the current pool being + * looked at. + * data - be_transaction_data_t pointer. + * Upon successfully finding the current BE, the + * obe_zpool member of this parameter is set to the + * pool it is found in. + * Return: + * 1 - Found current BE in this pool. + * 0 - Did not find current BE in this pool. + * Scope: + * Semi-private (library wide use only) + */ +int +be_zpool_find_current_be_callback(zpool_handle_t *zlp, void *data) +{ + be_transaction_data_t *bt = data; + zfs_handle_t *zhp = NULL; + const char *zpool = zpool_get_name(zlp); + char be_container_ds[MAXPATHLEN]; + + /* + * Generate string for BE container dataset + */ + be_make_container_ds(zpool, be_container_ds, sizeof (be_container_ds)); + + /* + * Check if a BE container dataset exists in this pool. + */ + if (!zfs_dataset_exists(g_zfs, be_container_ds, ZFS_TYPE_FILESYSTEM)) { + zpool_close(zlp); + return (0); + } + + /* + * Get handle to this zpool's BE container dataset. + */ + if ((zhp = zfs_open(g_zfs, be_container_ds, ZFS_TYPE_FILESYSTEM)) == + NULL) { + be_print_err(gettext("be_zpool_find_current_be_callback: " + "failed to open BE container dataset (%s)\n"), + be_container_ds); + zpool_close(zlp); + return (0); + } + + /* + * Iterate through all potential BEs in this zpool + */ + if (zfs_iter_filesystems(zhp, be_zfs_find_current_be_callback, bt)) { + /* + * Found current BE dataset; set obe_zpool + */ + if ((bt->obe_zpool = strdup(zpool)) == NULL) { + be_print_err(gettext( + "be_zpool_find_current_be_callback: " + "memory allocation failed\n")); + ZFS_CLOSE(zhp); + zpool_close(zlp); + return (0); + } + + ZFS_CLOSE(zhp); + zpool_close(zlp); + return (1); + } + + ZFS_CLOSE(zhp); + zpool_close(zlp); + + return (0); +} + +/* + * Function: be_zfs_find_current_be_callback + * Description: Callback function used to iterate through all BEs in a + * pool to find the BE that is the currently booted BE. + * Parameters: + * zhp - zfs_handle_t pointer to current filesystem being checked. + * data - be_transaction-data_t pointer + * Upon successfully finding the current BE, the + * obe_name and obe_root_ds members of this parameter + * are set to the BE name and BE's root dataset + * respectively. + * Return: + * 1 - Found current BE. + * 0 - Did not find current BE. + * Scope: + * Semi-private (library wide use only) + */ +int +be_zfs_find_current_be_callback(zfs_handle_t *zhp, void *data) +{ + be_transaction_data_t *bt = data; + char *mp = NULL; + + /* + * Check if dataset is mounted, and if so where. + */ + if (zfs_is_mounted(zhp, &mp)) { + /* + * If mounted at root, set obe_root_ds and obe_name + */ + if (mp != NULL && strcmp(mp, "/") == 0) { + free(mp); + + if ((bt->obe_root_ds = strdup(zfs_get_name(zhp))) + == NULL) { + be_print_err(gettext( + "be_zfs_find_current_be_callback: " + "memory allocation failed\n")); + ZFS_CLOSE(zhp); + return (0); + } + if ((bt->obe_name = strdup(basename(bt->obe_root_ds))) + == NULL) { + be_print_err(gettext( + "be_zfs_find_current_be_callback: " + "memory allocation failed\n")); + ZFS_CLOSE(zhp); + return (0); + } + + ZFS_CLOSE(zhp); + return (1); + } + + free(mp); + } + ZFS_CLOSE(zhp); + + return (0); +} + +/* + * Function: be_check_be_roots_callback + * Description: This function checks whether or not the dataset name passed + * is hierachically located under the BE root container dataset + * for this pool. + * Parameters: + * zlp - zpool_handle_t pointer to current pool being processed. + * data - name of dataset to check + * Returns: + * 0 - dataset is not in this pool's BE root container dataset + * 1 - dataset is in this pool's BE root container dataset + * Scope: + * Semi-private (library wide use only) + */ +int +be_check_be_roots_callback(zpool_handle_t *zlp, void *data) +{ + const char *zpool = zpool_get_name(zlp); + char *ds = data; + char be_container_ds[MAXPATHLEN]; + + /* Generate string for this pool's BE root container dataset */ + be_make_container_ds(zpool, be_container_ds, sizeof (be_container_ds)); + + /* + * If dataset lives under the BE root container dataset + * of this pool, return failure. + */ + if (strncmp(be_container_ds, ds, strlen(be_container_ds)) == 0 && + ds[strlen(be_container_ds)] == '/') { + zpool_close(zlp); + return (1); + } + + zpool_close(zlp); + return (0); +} + +/* + * Function: zfs_err_to_be_err + * Description: This function takes the error stored in the libzfs handle + * and maps it to an be_errno_t. If there are no matching + * be_errno_t's then BE_ERR_ZFS is returned. + * Paramters: + * zfsh - The libzfs handle containing the error we're looking up. + * Returns: + * be_errno_t + * Scope: + * Semi-private (library wide use only) + */ +int +zfs_err_to_be_err(libzfs_handle_t *zfsh) +{ + int err = libzfs_errno(zfsh); + + switch (err) { + case 0: + return (BE_SUCCESS); + case EZFS_PERM: + return (BE_ERR_PERM); + case EZFS_INTR: + return (BE_ERR_INTR); + case EZFS_NOENT: + return (BE_ERR_NOENT); + case EZFS_NOSPC: + return (BE_ERR_NOSPC); + case EZFS_MOUNTFAILED: + return (BE_ERR_MOUNT); + case EZFS_UMOUNTFAILED: + return (BE_ERR_UMOUNT); + case EZFS_EXISTS: + return (BE_ERR_BE_EXISTS); + case EZFS_BUSY: + return (BE_ERR_DEV_BUSY); + case EZFS_PERMRDONLY: + return (BE_ERR_ROFS); + case EZFS_NAMETOOLONG: + return (BE_ERR_NAMETOOLONG); + case EZFS_NODEVICE: + return (BE_ERR_NODEV); + case EZFS_POOL_INVALARG: + return (BE_ERR_INVAL); + case EZFS_PROPTYPE: + return (BE_ERR_INVALPROP); + case EZFS_BADTYPE: + return (BE_ERR_DSTYPE); + case EZFS_PROPNONINHERIT: + return (BE_ERR_NONINHERIT); + case EZFS_PROPREADONLY: + return (BE_ERR_READONLYPROP); + case EZFS_RESILVERING: + case EZFS_POOLUNAVAIL: + return (BE_ERR_UNAVAIL); + case EZFS_DSREADONLY: + return (BE_ERR_READONLYDS); + default: + return (BE_ERR_ZFS); + } +} + +/* + * Function: errno_to_be_err + * Description: This function takes an errno and maps it to an be_errno_t. + * If there are no matching be_errno_t's then BE_ERR_UNKNOWN is + * returned. + * Paramters: + * err - The errno we're compairing against. + * Returns: + * be_errno_t + * Scope: + * Semi-private (library wide use only) + */ +int +errno_to_be_err(int err) +{ + switch (err) { + case EPERM: + return (BE_ERR_PERM); + case EACCES: + return (BE_ERR_ACCESS); + case ECANCELED: + return (BE_ERR_CANCELED); + case EINTR: + return (BE_ERR_INTR); + case ENOENT: + return (BE_ERR_NOENT); + case ENOSPC: + case EDQUOT: + return (BE_ERR_NOSPC); + case EEXIST: + return (BE_ERR_BE_EXISTS); + case EBUSY: + return (BE_ERR_BUSY); + case EROFS: + return (BE_ERR_ROFS); + case ENAMETOOLONG: + return (BE_ERR_NAMETOOLONG); + case ENXIO: + return (BE_ERR_NXIO); + case EINVAL: + return (BE_ERR_INVAL); + case EFAULT: + return (BE_ERR_FAULT); + default: + return (BE_ERR_UNKNOWN); + } +} + +/* + * Function: be_err_to_str + * Description: This function takes a be_errno_t and maps it to a message. + * If there are no matching be_errno_t's then NULL is returned. + * Paramters: + * be_errno_t - The be_errno_t we're mapping. + * Returns: + * string or NULL if the error code is not known. + * Scope: + * Semi-private (library wide use only) + */ +char * +be_err_to_str(int err) +{ + switch (err) { + case BE_ERR_ACCESS: + return (gettext("Permission denied.")); + case BE_ERR_ACTIVATE_CURR: + return (gettext("Activation of current BE failed.")); + case BE_ERR_AUTONAME: + return (gettext("Auto naming failed.")); + case BE_ERR_BE_NOENT: + return (gettext("No such BE.")); + case BE_ERR_BUSY: + return (gettext("Mount busy.")); + case BE_ERR_DEV_BUSY: + return (gettext("Device busy.")); + case BE_ERR_CANCELED: + return (gettext("Operation canceled.")); + case BE_ERR_CLONE: + return (gettext("BE clone failed.")); + case BE_ERR_COPY: + return (gettext("BE copy failed.")); + case BE_ERR_CREATDS: + return (gettext("Dataset creation failed.")); + case BE_ERR_CURR_BE_NOT_FOUND: + return (gettext("Can't find current BE.")); + case BE_ERR_DESTROY: + return (gettext("Failed to destroy BE or snapshot.")); + case BE_ERR_DESTROY_CURR_BE: + return (gettext("Cannot destroy current BE.")); + case BE_ERR_DEMOTE: + return (gettext("BE demotion failed.")); + case BE_ERR_DSTYPE: + return (gettext("Invalid dataset type.")); + case BE_ERR_BE_EXISTS: + return (gettext("BE exists.")); + case BE_ERR_INIT: + return (gettext("be_zfs_init failed.")); + case BE_ERR_INTR: + return (gettext("Interupted system call.")); + case BE_ERR_INVAL: + return (gettext("Invalid argument.")); + case BE_ERR_INVALPROP: + return (gettext("Invalid property for dataset.")); + case BE_ERR_INVALMOUNTPOINT: + return (gettext("Unexpected mountpoint.")); + case BE_ERR_MOUNT: + return (gettext("Mount failed.")); + case BE_ERR_MOUNTED: + return (gettext("Already mounted.")); + case BE_ERR_NAMETOOLONG: + return (gettext("name > BUFSIZ.")); + case BE_ERR_NOENT: + return (gettext("Doesn't exist.")); + case BE_ERR_POOL_NOENT: + return (gettext("No such pool.")); + case BE_ERR_NODEV: + return (gettext("No such device.")); + case BE_ERR_NOTMOUNTED: + return (gettext("File system not mounted.")); + case BE_ERR_NOMEM: + return (gettext("Not enough memory.")); + case BE_ERR_NONINHERIT: + return (gettext( + "Property is not inheritable for the BE dataset.")); + case BE_ERR_NXIO: + return (gettext("No such device or address.")); + case BE_ERR_NOSPC: + return (gettext("No space on device.")); + case BE_ERR_NOTSUP: + return (gettext("Operation not supported.")); + case BE_ERR_OPEN: + return (gettext("Open failed.")); + case BE_ERR_PERM: + return (gettext("Not owner.")); + case BE_ERR_UNAVAIL: + return (gettext("The BE is currently unavailable.")); + case BE_ERR_PROMOTE: + return (gettext("BE promotion failed.")); + case BE_ERR_ROFS: + return (gettext("Read only file system.")); + case BE_ERR_READONLYDS: + return (gettext("Read only dataset.")); + case BE_ERR_READONLYPROP: + return (gettext("Read only property.")); + case BE_ERR_RENAME_ACTIVE: + return (gettext("Renaming the active BE is not supported.")); + case BE_ERR_SS_EXISTS: + return (gettext("Snapshot exists.")); + case BE_ERR_SS_NOENT: + return (gettext("No such snapshot.")); + case BE_ERR_UMOUNT: + return (gettext("Unmount failed.")); + case BE_ERR_UMOUNT_CURR_BE: + return (gettext("Can't unmount the current BE.")); + case BE_ERR_UMOUNT_SHARED: + return (gettext("Unmount of a shared File System failed.")); + case BE_ERR_FAULT: + return (gettext("Bad address.")); + case BE_ERR_UNKNOWN: + return (gettext("Unknown error.")); + case BE_ERR_ZFS: + return (gettext("ZFS returned an error.")); + case BE_ERR_GEN_UUID: + return (gettext("Failed to generate uuid.")); + case BE_ERR_PARSE_UUID: + return (gettext("Failed to parse uuid.")); + case BE_ERR_NO_UUID: + return (gettext("No uuid")); + case BE_ERR_ZONE_NO_PARENTBE: + return (gettext("No parent uuid")); + case BE_ERR_ZONE_MULTIPLE_ACTIVE: + return (gettext("Multiple active zone roots")); + case BE_ERR_ZONE_NO_ACTIVE_ROOT: + return (gettext("No active zone root")); + case BE_ERR_ZONE_ROOT_NOT_LEGACY: + return (gettext("Zone root not legacy")); + case BE_ERR_MOUNT_ZONEROOT: + return (gettext("Failed to mount a zone root.")); + case BE_ERR_UMOUNT_ZONEROOT: + return (gettext("Failed to unmount a zone root.")); + case BE_ERR_NO_MOUNTED_ZONE: + return (gettext("Zone is not mounted")); + case BE_ERR_ZONES_UNMOUNT: + return (gettext("Unable to unmount a zone BE.")); + case BE_ERR_NO_MENU: + return (gettext("Missing boot menu file.")); + case BE_ERR_BAD_MENU_PATH: + return (gettext("Invalid path for menu.lst file")); + case BE_ERR_ZONE_SS_EXISTS: + return (gettext("Zone snapshot exists.")); + case BE_ERR_ADD_SPLASH_ICT: + return (gettext("Add_spash_image ICT failed.")); + case BE_ERR_BOOTFILE_INST: + return (gettext("Error installing boot files.")); + case BE_ERR_EXTCMD: + return (gettext("Error running an external command.")); + default: + return (NULL); + } +} + +/* + * Function: be_has_grub + * Description: Boolean function indicating whether the current system + * uses grub. + * Return: B_FALSE - the system does not have grub + * B_TRUE - the system does have grub. + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +be_has_grub(void) +{ + /* + * TODO: This will need to be expanded to check for the existence of + * grub if and when there is grub support for SPARC. + */ + return (be_is_isa("i386")); +} + +/* + * Function: be_is_isa + * Description: Boolean function indicating whether the instruction set + * architecture of the executing system matches the name provided. + * The string must match a system defined architecture (e.g. + * "i386", "sparc") and is case sensitive. + * Parameters: name - string representing the name of instruction set + * architecture being tested + * Returns: B_FALSE - the system instruction set architecture is different + * from the one specified + * B_TRUE - the system instruction set architecture is the same + * as the one specified + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +be_is_isa(char *name) +{ + return ((strcmp((char *)be_get_default_isa(), name) == 0)); +} + +/* + * Function: be_get_default_isa + * Description: + * Returns the default instruction set architecture of the + * machine it is executed on. (eg. sparc, i386, ...) + * NOTE: SYS_INST environment variable may override default + * return value + * Parameters: + * none + * Returns: + * NULL - the architecture returned by sysinfo() was too + * long for local variables + * char * - pointer to a string containing the default + * implementation + * Scope: + * Semi-private (library wide use only) + */ +char * +be_get_default_isa(void) +{ + int i; + char *envp; + static char default_inst[ARCH_LENGTH] = ""; + + if (default_inst[0] == '\0') { + if ((envp = getenv("SYS_INST")) != NULL) { + if ((int)strlen(envp) >= ARCH_LENGTH) + return (NULL); + else + (void) strcpy(default_inst, envp); + } else { + i = sysinfo(SI_ARCHITECTURE, default_inst, ARCH_LENGTH); + if (i < 0 || i > ARCH_LENGTH) + return (NULL); + } + } + return (default_inst); +} + +/* + * Function: be_run_cmd + * Description: + * Runs a command in a separate subprocess. Splits out stdout from stderr + * and sends each to its own buffer. Buffers must be pre-allocated and + * passed in as arguments. Buffer sizes are also passed in as arguments. + * + * Notes / caveats: + * - Command being run is assumed to not have any stdout or stderr + * redirection. + * - Commands which emit total stderr output of greater than PIPE_BUF + * bytes can hang. For such commands, a different implementation + * which uses poll(2) must be used. + * - stdout_buf can be NULL. In this case, stdout_bufsize is ignored, and + * the stream which would have gone to it is sent to the bit + * bucket. + * - stderr_buf cannot be NULL. + * - Only subprocess errors are appended to the stderr_buf. Errors + * running the command are reported through be_print_err(). + * - Data which would overflow its respective buffer is sent to the bit + * bucket. + * + * Parameters: + * command: command to run. Assumed not to have embedded stdout + * or stderr redirection. May have stdin redirection, + * however. + * stderr_buf: buffer returning subprocess stderr data. Errors + * reported by this function are reported through + * be_print_err(). + * stderr_bufsize: size of stderr_buf + * stdout_buf: buffer returning subprocess stdout data. + * stdout_bufsize: size of stdout_buf + * Returns: + * BE_SUCCESS - The command ran successfully without returning + * errors. + * BE_ERR_EXTCMD + * - The command could not be run. + * - The command terminated with error status. + * - There were errors extracting or returning subprocess + * data. + * BE_ERR_NOMEM - The command exceeds the command buffer size. + * BE_ERR_INVAL - An invalid argument was specified. + * Scope: + * Semi-private (library wide use only) + */ +int +be_run_cmd(char *command, char *stderr_buf, int stderr_bufsize, + char *stdout_buf, int stdout_bufsize) +{ + char *temp_filename = strdup(tmpnam(NULL)); + FILE *stdout_str = NULL; + FILE *stderr_str = NULL; + char cmdline[BUFSIZ]; + char oneline[BUFSIZ]; + int exit_status; + int rval = BE_SUCCESS; + + if ((command == NULL) || (stderr_buf == NULL) || + (stderr_bufsize <= 0) || (stdout_bufsize < 0) || + ((stdout_buf != NULL) ^ (stdout_bufsize != 0))) { + return (BE_ERR_INVAL); +} + + /* Set up command so popen returns stderr, not stdout */ + if (snprintf(cmdline, BUFSIZ, "%s 2> %s", command, + temp_filename) >= BUFSIZ) { + rval = BE_ERR_NOMEM; + goto cleanup; + } + + /* Set up the fifo that will make stderr available. */ + if (mkfifo(temp_filename, 0600) != 0) { + (void) be_print_err(gettext("be_run_cmd: mkfifo: %s\n"), + strerror(errno)); + rval = BE_ERR_EXTCMD; + goto cleanup; + } + + if ((stdout_str = popen(cmdline, "r")) == NULL) { + (void) be_print_err(gettext("be_run_cmd: popen: %s\n"), + strerror(errno)); + rval = BE_ERR_EXTCMD; + goto cleanup; + } + + if ((stderr_str = fopen(temp_filename, "r")) == NULL) { + (void) be_print_err(gettext("be_run_cmd: fopen: %s\n"), + strerror(errno)); + (void) pclose(stdout_str); + rval = BE_ERR_EXTCMD; + goto cleanup; + } + + /* Read stdout first, as it usually outputs more than stderr. */ + oneline[BUFSIZ-1] = '\0'; + while (fgets(oneline, BUFSIZ-1, stdout_str) != NULL) { + if (stdout_str != NULL) { + (void) strlcat(stdout_buf, oneline, stdout_bufsize); + } + } + + while (fgets(oneline, BUFSIZ-1, stderr_str) != NULL) { + (void) strlcat(stderr_buf, oneline, stderr_bufsize); + } + + /* Close pipe, get exit status. */ + if ((exit_status = pclose(stdout_str)) == -1) { + (void) be_print_err(gettext("be_run_cmd: pclose: %s\n"), + strerror(errno)); + rval = BE_ERR_EXTCMD; + } else if (WIFEXITED(exit_status)) { + exit_status = (int)((char)WEXITSTATUS(exit_status)); + if (exit_status != 0) { + (void) snprintf(oneline, BUFSIZ, gettext("be_run_cmd: " + "command terminated with error status: %d\n"), + exit_status); + (void) strlcat(stderr_buf, oneline, stderr_bufsize); + rval = BE_ERR_EXTCMD; + } + } else { + (void) snprintf(oneline, BUFSIZ, gettext("be_run_cmd: command " + "terminated on signal: %s\n"), + strsignal(WTERMSIG(exit_status))); + (void) strlcat(stderr_buf, oneline, stderr_bufsize); + rval = BE_ERR_EXTCMD; + } + +cleanup: + (void) unlink(temp_filename); + (void) free(temp_filename); + + return (rval); +} + +/* ******************************************************************** */ +/* Private Functions */ +/* ******************************************************************** */ + +/* + * Function: update_dataset + * Description: This function takes a dataset name and replaces the zpool + * and be_name components of the dataset with the new be_name + * zpool passed in. + * Parameters: + * dataset - name of dataset + * dataset_len - lenth of buffer in which dataset is passed in. + * be_name - name of new BE name to update to. + * old_rc_loc - dataset under which the root container dataset + * for the old BE lives. + * new_rc_loc - dataset under which the root container dataset + * for the new BE lives. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +update_dataset(char *dataset, int dataset_len, char *be_name, + char *old_rc_loc, char *new_rc_loc) +{ + char *ds = NULL; + char *sub_ds = NULL; + + /* Tear off the BE container dataset */ + if ((ds = be_make_name_from_ds(dataset, old_rc_loc)) == NULL) { + return (BE_ERR_INVAL); + } + + /* Get dataset name relative to BE root, if there is one */ + sub_ds = strchr(ds, '/'); + + /* Generate the BE root dataset name */ + be_make_root_ds(new_rc_loc, be_name, dataset, dataset_len); + + /* If a subordinate dataset name was found, append it */ + if (sub_ds != NULL) + (void) strlcat(dataset, sub_ds, dataset_len); + + free(ds); + return (BE_SUCCESS); +} + +/* + * Function: _update_vfstab + * Description: This function updates a vfstab file to reflect the new + * root container dataset location and be_name for all + * entries listed in the be_fs_list_data_t structure passed in. + * Parameters: + * vfstab - vfstab file to modify + * be_name - name of BE to update. + * old_rc_loc - dataset under which the root container dataset + * of the old BE resides in. + * new_rc_loc - dataset under which the root container dataset + * of the new BE resides in. + * fld - be_fs_list_data_t pointer providing the list of + * file systems to look for in vfstab. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +_update_vfstab(char *vfstab, char *be_name, char *old_rc_loc, + char *new_rc_loc, be_fs_list_data_t *fld) +{ + struct vfstab vp; + char *tmp_vfstab = NULL; + char comments_buf[BUFSIZ]; + FILE *comments = NULL; + FILE *vfs_ents = NULL; + FILE *tfile = NULL; + struct stat sb; + char dev[MAXPATHLEN]; + char *c; + int fd; + int ret = BE_SUCCESS, err = 0; + int i; + int tmp_vfstab_len = 0; + + errno = 0; + + /* + * Open vfstab for reading twice. First is for comments, + * second is for actual entries. + */ + if ((comments = fopen(vfstab, "r")) == NULL || + (vfs_ents = fopen(vfstab, "r")) == NULL) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "failed to open vfstab (%s): %s\n"), vfstab, + strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + /* Grab the stats of the original vfstab file */ + if (stat(vfstab, &sb) != 0) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "failed to stat file %s: %s\n"), vfstab, + strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + /* Create tmp file for modified vfstab */ + if ((tmp_vfstab = (char *)malloc(strlen(vfstab) + 7)) + == NULL) { + be_print_err(gettext("_update_vfstab: " + "malloc failed\n")); + ret = BE_ERR_NOMEM; + goto cleanup; + } + tmp_vfstab_len = strlen(vfstab) + 7; + (void) memset(tmp_vfstab, 0, tmp_vfstab_len); + (void) strlcpy(tmp_vfstab, vfstab, tmp_vfstab_len); + (void) strlcat(tmp_vfstab, "XXXXXX", tmp_vfstab_len); + if ((fd = mkstemp(tmp_vfstab)) == -1) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "mkstemp failed: %s\n"), strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + if ((tfile = fdopen(fd, "w")) == NULL) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "could not open file for write\n")); + (void) close(fd); + ret = errno_to_be_err(err); + goto cleanup; + } + + while (fgets(comments_buf, BUFSIZ, comments)) { + for (c = comments_buf; *c != '\0' && isspace(*c); c++) + ; + if (*c == '\0') { + continue; + } else if (*c == '#') { + /* + * If line is a comment line, just put + * it through to the tmp vfstab. + */ + (void) fputs(comments_buf, tfile); + } else { + /* + * Else line is a vfstab entry, grab it + * into a vfstab struct. + */ + if (getvfsent(vfs_ents, &vp) != 0) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "getvfsent failed: %s\n"), strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + if (vp.vfs_special == NULL || vp.vfs_mountp == NULL) { + (void) putvfsent(tfile, &vp); + continue; + } + + /* + * If the entry is one of the entries in the list + * of file systems to update, modify it's device + * field to be correct for this BE. + */ + for (i = 0; i < fld->fs_num; i++) { + if (strcmp(vp.vfs_special, fld->fs_list[i]) + == 0) { + /* + * Found entry that needs an update. + * Replace the root container dataset + * location and be_name in the + * entry's device. + */ + (void) strlcpy(dev, vp.vfs_special, + sizeof (dev)); + + if ((ret = update_dataset(dev, + sizeof (dev), be_name, old_rc_loc, + new_rc_loc)) != 0) { + be_print_err( + gettext("_update_vfstab: " + "Failed to update device " + "field for vfstab entry " + "%s\n"), fld->fs_list[i]); + goto cleanup; + } + + vp.vfs_special = dev; + break; + } + } + + /* Put entry through to tmp vfstab */ + (void) putvfsent(tfile, &vp); + } + } + + (void) fclose(comments); + comments = NULL; + (void) fclose(vfs_ents); + vfs_ents = NULL; + (void) fclose(tfile); + tfile = NULL; + + /* Copy tmp vfstab into place */ + if (rename(tmp_vfstab, vfstab) != 0) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "failed to rename file %s to %s: %s\n"), tmp_vfstab, + vfstab, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + + /* Set the perms and ownership of the updated file */ + if (chmod(vfstab, sb.st_mode) != 0) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "failed to chmod %s: %s\n"), vfstab, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + if (chown(vfstab, sb.st_uid, sb.st_gid) != 0) { + err = errno; + be_print_err(gettext("_update_vfstab: " + "failed to chown %s: %s\n"), vfstab, strerror(err)); + ret = errno_to_be_err(err); + goto cleanup; + } + +cleanup: + if (comments != NULL) + (void) fclose(comments); + if (vfs_ents != NULL) + (void) fclose(vfs_ents); + (void) unlink(tmp_vfstab); + (void) free(tmp_vfstab); + if (tfile != NULL) + (void) fclose(tfile); + + return (ret); +} + + +/* + * Function: be_get_auto_name + * Description: Generate an auto name constructed based on the BE name + * of the original BE or zone BE being cloned. + * Parameters: + * obe_name - name of the original BE or zone BE being cloned. + * container_ds - container dataset for the zone. + * Note: if zone_be is false this should be + * NULL. + * zone_be - flag that indicates if we are operating on a zone BE. + * Returns: + * Success - pointer to auto generated BE name. The name + * is allocated in heap storage so the caller is + * responsible for free'ing the name. + * Failure - NULL + * Scope: + * Private + */ +static char * +be_get_auto_name(char *obe_name, char *be_container_ds, boolean_t zone_be) +{ + be_node_list_t *be_nodes = NULL; + be_node_list_t *cur_be = NULL; + char auto_be_name[MAXPATHLEN]; + char base_be_name[MAXPATHLEN]; + char cur_be_name[MAXPATHLEN]; + char *num_str = NULL; + char *c = NULL; + int num = 0; + int cur_num = 0; + + errno = 0; + + /* + * Check if obe_name is already in an auto BE name format. + * If it is, then strip off the increment number to get the + * base name. + */ + (void) strlcpy(base_be_name, obe_name, sizeof (base_be_name)); + + if ((num_str = strrchr(base_be_name, BE_AUTO_NAME_DELIM)) + != NULL) { + /* Make sure remaining string is all digits */ + c = num_str + 1; + while (c[0] != '\0' && isdigit(c[0])) + c++; + /* + * If we're now at the end of the string strip off the + * increment number. + */ + if (c[0] == '\0') + num_str[0] = '\0'; + } + + if (zone_be) { + if (be_container_ds == NULL) + return (NULL); + if (be_get_zone_be_list(obe_name, be_container_ds, + &be_nodes) != BE_SUCCESS) { + be_print_err(gettext("be_get_auto_name: " + "be_get_zone_be_list failed\n")); + return (NULL); + } + } else if (_be_list(NULL, &be_nodes) != BE_SUCCESS) { + be_print_err(gettext("be_get_auto_name: be_list failed\n")); + return (NULL); + } + + for (cur_be = be_nodes; cur_be != NULL; cur_be = cur_be->be_next_node) { + (void) strlcpy(cur_be_name, cur_be->be_node_name, + sizeof (cur_be_name)); + + /* If cur_be_name doesn't match at least base be name, skip. */ + if (strncmp(cur_be_name, base_be_name, strlen(base_be_name)) + != 0) + continue; + + /* Get the string following the base be name */ + num_str = cur_be_name + strlen(base_be_name); + + /* + * If nothing follows the base be name, this cur_be_name + * is the BE named with the base be name, skip. + */ + if (num_str == NULL || num_str[0] == '\0') + continue; + + /* + * Remove the name delimiter. If its not there, + * cur_be_name isn't part of this BE name stream, skip. + */ + if (num_str[0] == BE_AUTO_NAME_DELIM) + num_str++; + else + continue; + + /* Make sure remaining string is all digits */ + c = num_str; + while (c[0] != '\0' && isdigit(c[0])) + c++; + if (c[0] != '\0') + continue; + + /* Convert the number string to an int */ + cur_num = atoi(num_str); + + /* + * If failed to convert the string, skip it. If its too + * long to be converted to an int, we wouldn't auto generate + * this number anyway so there couldn't be a conflict. + * We treat it as a manually created BE name. + */ + if (cur_num == 0 && errno == EINVAL) + continue; + + /* + * Compare current number to current max number, + * take higher of the two. + */ + if (cur_num > num) + num = cur_num; + } + + /* + * Store off a copy of 'num' incase we need it later. If incrementing + * 'num' causes it to roll over, this means 'num' is the largest + * positive int possible; we'll need it later in the loop to determine + * if we've exhausted all possible increment numbers. We store it in + * 'cur_num'. + */ + cur_num = num; + + /* Increment 'num' to get new auto BE name number */ + if (++num <= 0) { + int ret = 0; + + /* + * Since incrementing 'num' caused it to rollover, start + * over at 0 and find the first available number. + */ + for (num = 0; num < cur_num; num++) { + + (void) snprintf(cur_be_name, sizeof (cur_be_name), + "%s%c%d", base_be_name, BE_AUTO_NAME_DELIM, num); + + ret = zpool_iter(g_zfs, be_exists_callback, + cur_be_name); + + if (ret == 0) { + /* + * BE name doesn't exist, break out + * to use 'num'. + */ + break; + } else if (ret == 1) { + /* BE name exists, continue looking */ + continue; + } else { + be_print_err(gettext("be_get_auto_name: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + be_free_list(be_nodes); + return (NULL); + } + } + + /* + * If 'num' equals 'cur_num', we've exhausted all possible + * auto BE names for this base BE name. + */ + if (num == cur_num) { + be_print_err(gettext("be_get_auto_name: " + "No more available auto BE names for base " + "BE name %s\n"), base_be_name); + be_free_list(be_nodes); + return (NULL); + } + } + + be_free_list(be_nodes); + + /* + * Generate string for auto BE name. + */ + (void) snprintf(auto_be_name, sizeof (auto_be_name), "%s%c%d", + base_be_name, BE_AUTO_NAME_DELIM, num); + + if ((c = strdup(auto_be_name)) == NULL) { + be_print_err(gettext("be_get_auto_name: " + "memory allocation failed\n")); + return (NULL); + } + + return (c); +} + +/* + * Function: be_create_menu + * Description: + * This function is used if no menu.lst file exists. In + * this case a new file is created and if needed default + * lines are added to the file. + * Parameters: + * pool - The name of the pool the menu.lst file is on + * pool_mntpt - The mountpoint for the pool we're using. + * menu_file - The name of the file we're creating. + * menu_fp - A pointer to the file pointer of the file we + * created. This is also used to pass back the file + * pointer to the newly created file. + * mode - the original mode used for the failed attempt to + * non-existent file. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_create_menu( + char *pool, + char *pool_mntpt, + char *menu_file, + FILE **menu_fp, + char *mode) +{ + be_node_list_t *be_nodes = NULL; + char add_default_cmd[BUFSIZ]; + char *menu_path = NULL; + char *be_rpool = NULL; + char *be_name = NULL; + + errno = 0; + + if (menu_file == NULL || menu_fp == NULL || mode == NULL) + return (BE_ERR_INVAL); + + menu_path = strdup(menu_file); + if (menu_path == NULL) + return (BE_ERR_NOMEM); + + (void) dirname(menu_path); + if (*menu_path == '.') { + free(menu_path); + return (BE_ERR_BAD_MENU_PATH); + } + if (mkdirp(menu_path, + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1 && + errno != EEXIST) { + free(menu_path); + be_print_err(gettext("be_create_menu: Failed to create the %s " + "directory: %s\n"), menu_path, strerror(errno)); + return (errno_to_be_err(errno)); + } + free(menu_path); + + /* + * Check to see if this system supports grub + */ + if (be_has_grub()) { + char be_run_cmd_errbuf[BUFSIZ]; + /* + * The grub menu is missing so we need to create it + * and fill in the first few lines. + */ + (void) snprintf(add_default_cmd, sizeof (add_default_cmd), + "%s add_splash_image_to_grub_menu %s", + INST_ICT, pool_mntpt); + if (be_run_cmd(add_default_cmd, be_run_cmd_errbuf, BUFSIZ, + NULL, 0) != BE_SUCCESS) { + be_print_err(gettext("be_create_menu: " + "add_splash_image_to_grub_menu ICT failed.\n")); + be_print_err(gettext(" Command: \"%s\"\n"), + add_default_cmd); + be_print_err(be_run_cmd_errbuf); + return (BE_ERR_ADD_SPLASH_ICT); + } + } else { + /* + * The menu file doesn't exist so we need to create a + * blank file. + */ + FILE *temp_fp = fopen(menu_file, "w+"); + if (temp_fp == NULL) { + *menu_fp = NULL; + return (errno_to_be_err(errno)); + } + (void) fclose(temp_fp); + } + + /* + * Now we need to add all the BE's back into the the file. + */ + if (_be_list(NULL, &be_nodes) == BE_SUCCESS) { + while (be_nodes != NULL) { + if (strcmp(pool, be_nodes->be_rpool) == 0) { + (void) be_append_menu(be_nodes->be_node_name, + be_nodes->be_rpool, NULL, NULL, NULL); + } + if (be_nodes->be_active_on_boot) { + be_rpool = strdup(be_nodes->be_rpool); + be_name = strdup(be_nodes->be_node_name); + } + + be_nodes = be_nodes->be_next_node; + } + } + be_free_list(be_nodes); + + /* + * Check to see if this system supports grub + */ + if (be_has_grub()) { + int err = be_change_grub_default(be_name, be_rpool); + if (err != BE_SUCCESS) + return (err); + } + *menu_fp = fopen(menu_file, mode); + if (*menu_fp == NULL) + return (errno_to_be_err(errno)); + + return (BE_SUCCESS); +} + +/* + * Function: be_open_menu + * Description: + * This function is used it open the menu.lst file. If this + * file does not exist be_create_menu is called to create it + * and the open file pointer is returned. If the file does + * exist it is simply opened using the mode passed in. + * Parameters: + * pool - The name of the pool the menu.lst file is on + * pool_mntpt - The mountpoint for the pool we're using. + * The mountpoint is used since the mountpoint + * name can differ from the pool name. + * menu_file - The name of the file we're opening. + * menu_fp - A pointer to the file pointer of the file we're + * opening. This is also used to pass back the file + * pointer. + * mode - the original mode to be used for opening the menu.lst + * file. + * create_menu - If this is true and the menu.lst file does not + * exist we will attempt to re-create it. However + * if it's false the error returned from the fopen + * will be returned. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +static int +be_open_menu( + char *pool, + char *pool_mntpt, + char *menu_file, + FILE **menu_fp, + char *mode, + boolean_t create_menu) +{ + int err = 0; + boolean_t set_print = B_FALSE; + + *menu_fp = fopen(menu_file, mode); + err = errno; + if (*menu_fp == NULL) { + if (err == ENOENT && create_menu) { + be_print_err(gettext("be_open_menu: menu.lst " + "file %s does not exist,\n"), menu_file); + if (!do_print) { + set_print = B_TRUE; + do_print = B_TRUE; + } + be_print_err(gettext("WARNING: menu.lst " + "file %s does not exist,\n generating " + "a new menu.lst file\n"), menu_file); + if (set_print) + do_print = B_FALSE; + err = 0; + if ((err = be_create_menu(pool, pool_mntpt, menu_file, + menu_fp, mode)) == ENOENT) + return (BE_ERR_NO_MENU); + else if (err != BE_SUCCESS) + return (err); + else if (*menu_fp == NULL) + return (BE_ERR_NO_MENU); + } else { + be_print_err(gettext("be_open_menu: failed " + "to open menu.lst file %s\n"), menu_file); + if (err == ENOENT) + return (BE_ERR_NO_MENU); + else + return (errno_to_be_err(err)); + } + } + return (BE_SUCCESS); +} diff --git a/usr/src/lib/libbe/common/be_zones.c b/usr/src/lib/libbe/common/be_zones.c new file mode 100644 index 0000000000..886b0dacad --- /dev/null +++ b/usr/src/lib/libbe/common/be_zones.c @@ -0,0 +1,518 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * System includes + */ +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <libnvpair.h> +#include <libzfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mntent.h> +#include <sys/mnttab.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/vfstab.h> +#include <unistd.h> + +#include <libbe.h> +#include <libbe_priv.h> + +typedef struct active_zone_root_data { + uuid_t parent_uuid; + char *zoneroot_ds; +} active_zone_root_data_t; + +typedef struct mounted_zone_root_data { + char *zone_altroot; + char *zoneroot_ds; +} mounted_zone_root_data_t; + +/* Private function prototypes */ +static int be_find_active_zone_root_callback(zfs_handle_t *, void *); +static int be_find_mounted_zone_root_callback(zfs_handle_t *, void *); +static boolean_t be_zone_get_active(zfs_handle_t *); + + +/* ******************************************************************** */ +/* Semi-Private Functions */ +/* ******************************************************************** */ + +/* + * Function: be_make_zoneroot + * Description: Generate a string for a zone's zoneroot given the + * zone's zonepath. + * Parameters: + * zonepath - pointer to zonepath + * zoneroot - pointer to buffer to retrn zoneroot in. + * zoneroot_size - size of zoneroot + * Returns: + * None + * Scope: + * Semi-private (library wise use only) + */ +void +be_make_zoneroot(char *zonepath, char *zoneroot, int zoneroot_size) +{ + (void) snprintf(zoneroot, zoneroot_size, "%s/root", zonepath); +} + +/* + * Function: be_find_active_zone_root + * Description: This function will find the active zone root of a zone for + * a given global BE. It will iterate all of the zone roots + * under a zonepath, find the zone roots that belong to the + * specified global BE, and return the one that is active. + * Parameters: + * be_zhp - zfs handle to global BE root dataset. + * zonepath_ds - pointer to zone's zonepath dataset. + * zoneroot_ds - pointer to a buffer to store the dataset name of + * the zone's zoneroot that's currently active for this + * given global BE.. + * zoneroot-ds_size - size of zoneroot_ds. + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_find_active_zone_root(zfs_handle_t *be_zhp, char *zonepath_ds, + char *zoneroot_ds, int zoneroot_ds_size) +{ + active_zone_root_data_t azr_data = { 0 }; + zfs_handle_t *zhp; + char zone_container_ds[MAXPATHLEN]; + int ret = BE_SUCCESS; + + /* Get the uuid of the parent global BE */ + if ((ret = be_get_uuid(zfs_get_name(be_zhp), &azr_data.parent_uuid)) + != BE_SUCCESS) { + be_print_err(gettext("be_find_active_zone_root: failed to " + "get uuid for BE root dataset %s\n"), zfs_get_name(be_zhp)); + return (ret); + } + + /* Generate string for the root container dataset for this zone. */ + be_make_container_ds(zonepath_ds, zone_container_ds, + sizeof (zone_container_ds)); + + /* Get handle to this zone's root container dataset */ + if ((zhp = zfs_open(g_zfs, zone_container_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_find_active_zone_root: failed to " + "open zone root container dataset (%s): %s\n"), + zone_container_ds, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* + * Iterate through all of this zone's BEs, looking for ones + * that belong to the parent global BE, and finding the one + * that is marked active. + */ + if ((ret = zfs_iter_filesystems(zhp, be_find_active_zone_root_callback, + &azr_data)) != 0) { + be_print_err(gettext("be_find_active_zone_root: failed to " + "find active zone root in zonepath dataset %s: %s\n"), + zonepath_ds, be_err_to_str(ret)); + goto done; + } + + if (azr_data.zoneroot_ds != NULL) { + (void) strlcpy(zoneroot_ds, azr_data.zoneroot_ds, + zoneroot_ds_size); + free(azr_data.zoneroot_ds); + } else { + be_print_err(gettext("be_find_active_zone_root: failed to " + "find active zone root in zonepath dataset %s\n"), + zonepath_ds); + ret = BE_ERR_ZONE_NO_ACTIVE_ROOT; + } + +done: + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_find_mounted_zone_root + * Description: This function will find the dataset mounted as the zoneroot + * of a zone for a given mounted global BE. + * Parameters: + * zone_altroot - path of zoneroot wrt the mounted global BE. + * zonepath_ds - dataset of the zone's zonepath. + * zoneroot_ds - pointer to a buffer to store the dataset of + * the zoneroot that currently mounted for this zone + * in the mounted global BE. + * zoneroot_ds_size - size of zoneroot_ds + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Semi-private (library wide use only) + */ +int +be_find_mounted_zone_root(char *zone_altroot, char *zonepath_ds, + char *zoneroot_ds, int zoneroot_ds_size) +{ + mounted_zone_root_data_t mzr_data = { 0 }; + zfs_handle_t *zhp = NULL; + char zone_container_ds[MAXPATHLEN]; + int ret = BE_SUCCESS; + int zret = 0; + + /* Generate string for the root container dataset for this zone. */ + be_make_container_ds(zonepath_ds, zone_container_ds, + sizeof (zone_container_ds)); + + /* Get handle to this zone's root container dataset. */ + if ((zhp = zfs_open(g_zfs, zone_container_ds, ZFS_TYPE_FILESYSTEM)) + == NULL) { + be_print_err(gettext("be_find_mounted_zone_root: failed to " + "open zone root container dataset (%s): %s\n"), + zone_container_ds, libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + mzr_data.zone_altroot = zone_altroot; + + /* + * Iterate through all of the zone's BEs, looking for the one + * that is currently mounted at the zone altroot in the mounted + * global BE. + */ + if ((zret = zfs_iter_filesystems(zhp, + be_find_mounted_zone_root_callback, &mzr_data)) == 0) { + be_print_err(gettext("be_find_mounted_zone_root: did not " + "find mounted zone under altroot zonepath %s\n"), + zonepath_ds); + ret = BE_ERR_NO_MOUNTED_ZONE; + goto done; + } else if (zret < 0) { + be_print_err(gettext("be_find_mounted_zone_root: " + "zfs_iter_filesystems failed: %s\n"), + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + if (mzr_data.zoneroot_ds != NULL) { + (void) strlcpy(zoneroot_ds, mzr_data.zoneroot_ds, + zoneroot_ds_size); + free(mzr_data.zoneroot_ds); + } + +done: + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_zone_supported + * Description: This function will determine if a zone is supported + * based on its zonepath dataset. The zonepath dataset + * must: + * - not be under any global BE root dataset. + * - have a root container dataset underneath it. + * + * Parameters: + * zonepath_ds - name of dataset of the zonepath of the + * zone to check. + * Returns: + * B_TRUE - zone is supported + * B_FALSE - zone is not supported + * Scope: + * Semi-private (library wide use only) + */ +boolean_t +be_zone_supported(char *zonepath_ds) +{ + char zone_container_ds[MAXPATHLEN]; + int ret = 0; + + /* + * Make sure the dataset for the zonepath is not hierarchically + * under any reserved BE root container dataset of any pool. + */ + if ((ret = zpool_iter(g_zfs, be_check_be_roots_callback, + zonepath_ds)) > 0) { + be_print_err(gettext("be_zone_supported: " + "zonepath dataset %s not supported\n"), zonepath_ds); + return (B_FALSE); + } else if (ret < 0) { + be_print_err(gettext("be_zone_supported: " + "zpool_iter failed: %s\n"), + libzfs_error_description(g_zfs)); + return (B_FALSE); + } + + /* + * Make sure the zonepath has a zone root container dataset + * underneath it. + */ + be_make_container_ds(zonepath_ds, zone_container_ds, + sizeof (zone_container_ds)); + + if (!zfs_dataset_exists(g_zfs, zone_container_ds, + ZFS_TYPE_FILESYSTEM)) { + be_print_err(gettext("be_zone_supported: " + "zonepath dataset (%s) does not have a zone root container " + "dataset, zone is not supported, skipping ...\n"), + zonepath_ds); + return (B_FALSE); + } + + return (B_TRUE); +} + +/* + * Function: be_get_supported_brandlist + * Desciption: This functions retuns a list of supported brands in + * a zoneBrandList_t object. + * Parameters: + * None + * Returns: + * Failure - NULL if no supported brands found. + * Success - pointer to zoneBrandList structure. + * Scope: + * Semi-private (library wide use only) + */ +zoneBrandList_t * +be_get_supported_brandlist(void) +{ + return (z_make_brand_list(BE_ZONE_SUPPORTED_BRANDS, + BE_ZONE_SUPPORTED_BRANDS_DELIM)); +} + +/* + * Function: be_zone_get_parent_uuid + * Description: This function gets the parentbe property of a zone root + * dataset, parsed it into internal uuid format, and returns + * it in the uuid_t reference pointer passed in. + * Parameters: + * root_ds - dataset name of a zone root dataset + * uu - pointer to a uuid_t to return the parentbe uuid in + * Returns: + * BE_SUCCESS - Success + * be_errno_t - Failure + * Scope: + * Private + */ +int +be_zone_get_parent_uuid(const char *root_ds, uuid_t *uu) +{ + zfs_handle_t *zhp = NULL; + nvlist_t *userprops = NULL; + nvlist_t *propname = NULL; + char *uu_string = NULL; + int ret = BE_SUCCESS; + + /* Get handle to zone root dataset */ + if ((zhp = zfs_open(g_zfs, root_ds, ZFS_TYPE_FILESYSTEM)) == NULL) { + be_print_err(gettext("be_zone_get_parent_uuid: failed to " + "open zone root dataset (%s): %s\n"), root_ds, + libzfs_error_description(g_zfs)); + return (zfs_err_to_be_err(g_zfs)); + } + + /* Get user properties for zone root dataset */ + if ((userprops = zfs_get_user_props(zhp)) == NULL) { + be_print_err(gettext("be_zone_get_parent_uuid: " + "failed to get user properties for zone root " + "dataset (%s): %s\n"), root_ds, + libzfs_error_description(g_zfs)); + ret = zfs_err_to_be_err(g_zfs); + goto done; + } + + /* Get UUID string from zone's root dataset user properties */ + if (nvlist_lookup_nvlist(userprops, BE_ZONE_PARENTBE_PROPERTY, + &propname) != 0 || nvlist_lookup_string(propname, ZPROP_VALUE, + &uu_string) != 0) { + be_print_err(gettext("be_zone_get_parent_uuid: failed to " + "get parent uuid property from zone root dataset user " + "properties.\n")); + ret = BE_ERR_ZONE_NO_PARENTBE; + goto done; + } + + /* Parse the uuid string into internal format */ + if (uuid_parse(uu_string, *uu) != 0 || uuid_is_null(*uu)) { + be_print_err(gettext("be_zone_get_parent_uuid: failed to " + "parse parentuuid\n")); + ret = BE_ERR_PARSE_UUID; + } + +done: + ZFS_CLOSE(zhp); + return (ret); +} + +/* ******************************************************************** */ +/* Private Functions */ +/* ******************************************************************** */ + +/* + * Function: be_find_active_zone_root_callback + * Description: This function is used as a callback to iterate over all of + * a zone's root datasets, finding the one that is marked active + * for the parent BE specified in the data passed in. The name + * of the zone's active root dataset is returned in heap storage + * in the active_zone_root_data_t structure passed in, so the + * caller is responsible for freeing it. + * Parameters: + * zhp - zfs_handle_t pointer to current dataset being processed + * data - active_zone_root_data_t pointer + * Returns: + * 0 - Success + * >0 - Failure + * Scope: + * Private + */ +static int +be_find_active_zone_root_callback(zfs_handle_t *zhp, void *data) +{ + active_zone_root_data_t *azr_data = data; + uuid_t parent_uuid = { 0 }; + int iret = 0; + int ret = 0; + + if ((iret = be_zone_get_parent_uuid(zfs_get_name(zhp), &parent_uuid)) + != BE_SUCCESS) { + be_print_err(gettext("be_find_active_zone_root_callback: " + "skipping zone root dataset (%s): %s\n"), + zfs_get_name(zhp), be_err_to_str(iret)); + goto done; + } + + if (uuid_compare(azr_data->parent_uuid, parent_uuid) == 0) { + /* + * Found a zone root dataset belonging to the right parent, + * check if its active. + */ + if (be_zone_get_active(zhp)) { + /* + * Found active zone root dataset, if its already + * set in the callback data, that means this + * is the second one we've found. Return error. + */ + if (azr_data->zoneroot_ds != NULL) { + ret = BE_ERR_ZONE_MULTIPLE_ACTIVE; + goto done; + } + + azr_data->zoneroot_ds = strdup(zfs_get_name(zhp)); + if (azr_data->zoneroot_ds == NULL) { + ret = BE_ERR_NOMEM; + } + } + } + +done: + ZFS_CLOSE(zhp); + return (ret); +} + +/* + * Function: be_find_mounted_zone_root_callback + * Description: This function is used as a callback to iterate over all of + * a zone's root datasets, find the one that is currently + * mounted for the parent BE specified in the data passed in. + * The name of the zone's mounted root dataset is returned in + * heap storage the mounted_zone_data_t structure passed in, + * so the caller is responsible for freeing it. + * Parameters: + * zhp - zfs_handle_t pointer to the current dataset being + * processed + * data - mounted_zone_data_t pointer + * Returns: + * 0 - not mounted as zone's root + * 1 - this dataset is mounted as zone's root + * Scope: + * Private + */ +static int +be_find_mounted_zone_root_callback(zfs_handle_t *zhp, void *data) +{ + mounted_zone_root_data_t *mzr_data = data; + char *mp = NULL; + + if (zfs_is_mounted(zhp, &mp) && mp != NULL && + strcmp(mp, mzr_data->zone_altroot) == 0) { + mzr_data->zoneroot_ds = strdup(zfs_get_name(zhp)); + free(mp); + return (1); + } + + free(mp); + return (0); +} + +/* + * Function: be_zone_get_active + * Description: This function gets the active property of a zone root + * dataset, and returns true if active property is on. + * Parameters: + * zfs - zfs_handle_t pointer to zone root dataset to check + * Returns: + * B_TRUE - zone root dataset is active + * B_FALSE - zone root dataset is not active + * Scope: + * Private + */ +static boolean_t +be_zone_get_active(zfs_handle_t *zhp) +{ + nvlist_t *userprops = NULL; + nvlist_t *propname = NULL; + char *active_str = NULL; + + /* Get user properties for the zone root dataset */ + if ((userprops = zfs_get_user_props(zhp)) == NULL) { + be_print_err(gettext("be_zone_get_active: " + "failed to get user properties for zone root " + "dataset (%s): %s\n"), zfs_get_name(zhp), + libzfs_error_description(g_zfs)); + return (B_FALSE); + } + + /* Get active property from the zone root dataset user properties */ + if (nvlist_lookup_nvlist(userprops, BE_ZONE_ACTIVE_PROPERTY, &propname) + != 0 || nvlist_lookup_string(propname, ZPROP_VALUE, &active_str) + != 0) { + return (B_FALSE); + } + + if (strcmp(active_str, "on") == 0) + return (B_TRUE); + + return (B_FALSE); +} diff --git a/usr/src/lib/libbe/common/libbe.h b/usr/src/lib/libbe/common/libbe.h new file mode 100644 index 0000000000..27baecab89 --- /dev/null +++ b/usr/src/lib/libbe/common/libbe.h @@ -0,0 +1,234 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#ifndef _LIBBE_H +#define _LIBBE_H + +#include <libnvpair.h> +#include <uuid/uuid.h> +#include <libzfs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define BE_ATTR_ORIG_BE_NAME "orig_be_name" +#define BE_ATTR_ORIG_BE_POOL "orig_be_pool" +#define BE_ATTR_SNAP_NAME "snap_name" + +#define BE_ATTR_NEW_BE_NAME "new_be_name" +#define BE_ATTR_NEW_BE_POOL "new_be_pool" +#define BE_ATTR_NEW_BE_DESC "new_be_desc" +#define BE_ATTR_POLICY "policy" +#define BE_ATTR_ZFS_PROPERTIES "zfs_properties" + +#define BE_ATTR_FS_NAMES "fs_names" +#define BE_ATTR_FS_NUM "fs_num" +#define BE_ATTR_SHARED_FS_NAMES "shared_fs_names" +#define BE_ATTR_SHARED_FS_NUM "shared_fs_num" + +#define BE_ATTR_MOUNTPOINT "mountpoint" +#define BE_ATTR_MOUNT_FLAGS "mount_flags" +#define BE_ATTR_UNMOUNT_FLAGS "unmount_flags" +#define BE_ATTR_DESTROY_FLAGS "destroy_flags" +#define BE_ATTR_ROOT_DS "root_ds" +#define BE_ATTR_UUID_STR "uuid_str" + +#define BE_ATTR_ACTIVE "active" +#define BE_ATTR_ACTIVE_ON_BOOT "active_boot" +#define BE_ATTR_SPACE "space_used" +#define BE_ATTR_DATASET "dataset" +#define BE_ATTR_STATUS "status" +#define BE_ATTR_DATE "date" +#define BE_ATTR_MOUNTED "mounted" + +/* + * libbe error codes + * + * NOTE: there is a copy of this enum in beadm/messages.py. To keep these + * in sync please make sure to add any new error messages at the end + * of this enumeration. + */ +enum { + BE_SUCCESS = 0, + BE_ERR_ACCESS = 4000, /* permission denied */ + BE_ERR_ACTIVATE_CURR, /* Activation of current BE failed */ + BE_ERR_AUTONAME, /* auto naming failed */ + BE_ERR_BE_NOENT, /* No such BE */ + BE_ERR_BUSY, /* mount busy */ + BE_ERR_CANCELED, /* operation canceled */ + BE_ERR_CLONE, /* BE clone failed */ + BE_ERR_COPY, /* BE copy failed */ + BE_ERR_CREATDS, /* dataset creation failed */ + BE_ERR_CURR_BE_NOT_FOUND, /* Can't find current BE */ + BE_ERR_DESTROY, /* failed to destroy BE or snapshot */ + BE_ERR_DEMOTE, /* BE demotion failed */ + BE_ERR_DSTYPE, /* invalid dataset type */ + BE_ERR_BE_EXISTS, /* BE exists */ + BE_ERR_INIT, /* be_zfs_init failed */ + BE_ERR_INTR, /* interupted system call */ + BE_ERR_INVAL, /* invalid argument */ + BE_ERR_INVALPROP, /* invalid property for dataset */ + BE_ERR_INVALMOUNTPOINT, /* Unexpected mountpoint */ + BE_ERR_MOUNT, /* mount failed */ + BE_ERR_MOUNTED, /* already mounted */ + BE_ERR_NAMETOOLONG, /* name > BUFSIZ */ + BE_ERR_NOENT, /* Doesn't exist */ + BE_ERR_POOL_NOENT, /* No such pool */ + BE_ERR_NODEV, /* No such device */ + BE_ERR_NOTMOUNTED, /* File system not mounted */ + BE_ERR_NOMEM, /* not enough memory */ + BE_ERR_NONINHERIT, /* property is not inheritable for BE dataset */ + BE_ERR_NXIO, /* No such device or address */ + BE_ERR_NOSPC, /* No space on device */ + BE_ERR_NOTSUP, /* Operation not supported */ + BE_ERR_OPEN, /* open failed */ + BE_ERR_PERM, /* Not owner */ + BE_ERR_UNAVAIL, /* The BE is currently unavailable */ + BE_ERR_PROMOTE, /* BE promotion failed */ + BE_ERR_ROFS, /* read only file system */ + BE_ERR_READONLYDS, /* read only dataset */ + BE_ERR_READONLYPROP, /* read only property */ + BE_ERR_SS_EXISTS, /* snapshot exists */ + BE_ERR_SS_NOENT, /* No such snapshot */ + BE_ERR_UMOUNT, /* unmount failed */ + BE_ERR_UMOUNT_CURR_BE, /* Can't unmount current BE */ + BE_ERR_UMOUNT_SHARED, /* unmount of shared File System failed */ + BE_ERR_UNKNOWN, /* Unknown error */ + BE_ERR_ZFS, /* ZFS returned an error */ + BE_ERR_DESTROY_CURR_BE, /* Cannot destroy current BE */ + BE_ERR_GEN_UUID, /* Failed to generate uuid */ + BE_ERR_PARSE_UUID, /* Failed to parse uuid */ + BE_ERR_NO_UUID, /* BE has no uuid */ + BE_ERR_ZONE_NO_PARENTBE, /* Zone root dataset has no parent uuid */ + BE_ERR_ZONE_MULTIPLE_ACTIVE, /* Zone has multiple active roots */ + BE_ERR_ZONE_NO_ACTIVE_ROOT, /* Zone has no active root for this BE */ + BE_ERR_ZONE_ROOT_NOT_LEGACY, /* Zone root dataset mntpt is not legacy */ + BE_ERR_NO_MOUNTED_ZONE, /* Zone not mounted in alternate BE */ + BE_ERR_MOUNT_ZONEROOT, /* Failed to mount a zone root */ + BE_ERR_UMOUNT_ZONEROOT, /* Failed to unmount a zone root */ + BE_ERR_ZONES_UNMOUNT, /* Unable to unmount a zone. */ + BE_ERR_FAULT, /* Bad Address */ + BE_ERR_RENAME_ACTIVE, /* Renaming the active BE is not supported */ + BE_ERR_NO_MENU, /* Missing boot menu file */ + BE_ERR_DEV_BUSY, /* Device is Busy */ + BE_ERR_BAD_MENU_PATH, /* Invalid path for menu.lst file */ + BE_ERR_ZONE_SS_EXISTS, /* zone snapshot already exists */ + BE_ERR_ADD_SPLASH_ICT, /* Add_splash_image ICT failed */ + BE_ERR_BOOTFILE_INST, /* Error installing boot files */ + BE_ERR_EXTCMD /* External command error */ +} be_errno_t; + +/* + * Data structures used to return the listing and information of BEs. + */ +typedef struct be_dataset_list { + uint64_t be_ds_space_used; + boolean_t be_ds_mounted; + char *be_dataset_name; + time_t be_ds_creation; /* Date/time stamp when created */ + char *be_ds_mntpt; + char *be_ds_plcy_type; /* cleanup policy type */ + struct be_dataset_list *be_next_dataset; +} be_dataset_list_t; + +typedef struct be_snapshot_list { + uint64_t be_snapshot_space_used; /* bytes of disk space used */ + char *be_snapshot_name; + time_t be_snapshot_creation; /* Date/time stamp when created */ + char *be_snapshot_type; /* cleanup policy type */ + struct be_snapshot_list *be_next_snapshot; +} be_snapshot_list_t; + +typedef struct be_node_list { + boolean_t be_mounted; /* is BE currently mounted */ + boolean_t be_active_on_boot; /* is this BE active on boot */ + boolean_t be_active; /* is this BE active currently */ + uint64_t be_space_used; + char *be_node_name; + char *be_rpool; + char *be_root_ds; + char *be_mntpt; + char *be_policy_type; /* cleanup policy type */ + char *be_uuid_str; /* string representation of uuid */ + time_t be_node_creation; /* Date/time stamp when created */ + struct be_dataset_list *be_node_datasets; + uint_t be_node_num_datasets; + struct be_snapshot_list *be_node_snapshots; + uint_t be_node_num_snapshots; + struct be_node_list *be_next_node; +} be_node_list_t; + +/* Flags used with mounting a BE */ +#define BE_MOUNT_FLAG_NULL 0x00000000 +#define BE_MOUNT_FLAG_SHARED_FS 0x00000001 +#define BE_MOUNT_FLAG_SHARED_RW 0x00000002 +#define BE_MOUNT_FLAG_NO_ZONES 0x00000004 + +/* Flags used with unmounting a BE */ +#define BE_UNMOUNT_FLAG_NULL 0x00000000 +#define BE_UNMOUNT_FLAG_FORCE 0x00000001 + +/* Flags used with destroying a BE */ +#define BE_DESTROY_FLAG_NULL 0x00000000 +#define BE_DESTROY_FLAG_SNAPSHOTS 0x00000001 +#define BE_DESTROY_FLAG_FORCE_UNMOUNT 0x00000002 + +/* + * BE functions + */ +int be_init(nvlist_t *); +int be_destroy(nvlist_t *); +int be_copy(nvlist_t *); + +int be_mount(nvlist_t *); +int be_unmount(nvlist_t *); + +int be_rename(nvlist_t *); + +int be_activate(nvlist_t *); + +int be_create_snapshot(nvlist_t *); +int be_destroy_snapshot(nvlist_t *); +int be_rollback(nvlist_t *); + +/* + * Functions for listing and getting information about existing BEs. + */ +int be_list(char *, be_node_list_t **); +void be_free_list(be_node_list_t *); +int be_max_avail(char *, uint64_t *); +char *be_err_to_str(int); + +/* + * Library functions + */ +void libbe_print_errors(boolean_t); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBBE_H */ diff --git a/usr/src/lib/libbe/common/libbe_priv.h b/usr/src/lib/libbe/common/libbe_priv.h new file mode 100644 index 0000000000..941fe6d565 --- /dev/null +++ b/usr/src/lib/libbe/common/libbe_priv.h @@ -0,0 +1,209 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#ifndef _LIBBE_PRIV_H +#define _LIBBE_PRIV_H + +#include <libnvpair.h> +#include <libzfs.h> +#include <instzones_api.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define ARCH_LENGTH MAXNAMELEN +#define BE_AUTO_NAME_MAX_TRY 3 +#define BE_AUTO_NAME_DELIM '-' +#define BE_CONTAINER_DS_NAME "ROOT" +#define BE_POLICY_PROPERTY "org.opensolaris.libbe:policy" +#define BE_UUID_PROPERTY "org.opensolaris.libbe:uuid" +#define BE_PLCY_STATIC "static" +#define BE_PLCY_VOLATILE "volatile" +#define BE_GRUB_MENU "/boot/grub/menu.lst" +#define BE_SPARC_MENU "/boot/menu.lst" +#define BE_GRUB_COMMENT "#============ End of LIBBE entry =============" +#define BE_WHITE_SPACE " \t\r\n" +#define BE_CAP_FILE "/boot/grub/capability" +#define BE_INSTALL_GRUB "/sbin/installgrub" +#define BE_STAGE_1 "/boot/grub/stage1" +#define BE_STAGE_2 "/boot/grub/stage2" +#define ZFS_CLOSE(_zhp) \ + if (_zhp) { \ + zfs_close(_zhp); \ + _zhp = NULL; \ + } + +#define BE_ZONE_PARENTBE_PROPERTY "org.opensolaris.libbe:parentbe" +#define BE_ZONE_ACTIVE_PROPERTY "org.opensolaris.libbe:active" +#define BE_ZONE_SUPPORTED_BRANDS "ipkg labeled" +#define BE_ZONE_SUPPORTED_BRANDS_DELIM " " + +/* Maximum length for the BE name. */ +#define BE_NAME_MAX_LEN 64 + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +typedef struct be_transaction_data { + char *obe_name; /* Original BE name */ + char *obe_root_ds; /* Original BE root dataset */ + char *obe_zpool; /* Original BE pool */ + char *obe_snap_name; /* Original BE snapshot name */ + char *obe_altroot; /* Original BE altroot */ + char *nbe_name; /* New BE name */ + char *nbe_root_ds; /* New BE root dataset */ + char *nbe_zpool; /* New BE pool */ + char *nbe_desc; /* New BE description */ + nvlist_t *nbe_zfs_props; /* New BE dataset properties */ + char *policy; /* BE policy type */ +} be_transaction_data_t; + +typedef struct be_mount_data { + char *altroot; /* Location of where to mount BE */ + boolean_t shared_fs; /* Mount shared file sytsems */ + boolean_t shared_rw; /* Mount shared file systems rw */ +} be_mount_data_t; + +typedef struct be_unmount_data { + char *altroot; /* Location of where BE is mounted */ + boolean_t force; /* Forcibly unmount */ +} be_unmount_data_t; + +typedef struct be_destroy_data { + boolean_t destroy_snaps; /* Destroy snapshots of BE */ + boolean_t force_unmount; /* Forcibly unmount BE if mounted */ + uuid_t gz_be_uuid; /* UUID of the global zone BE */ +} be_destroy_data_t; + +typedef struct be_demote_data { + zfs_handle_t *clone_zhp; /* clone dataset to promote */ + time_t origin_creation; /* snapshot creation time of clone */ + const char *snapshot; /* snapshot of dataset being demoted */ + boolean_t find_in_BE; /* flag noting to find clone in BE */ +} be_demote_data_t; + +typedef struct be_fs_list_data { + char *altroot; + char **fs_list; + int fs_num; +} be_fs_list_data_t; + +typedef struct be_plcy_list { + char *be_plcy_name; + int be_num_max; + int be_num_min; + time_t be_age_max; + int be_usage_pcnt; + struct be_plcy_list *be_next_plcy; +}be_plcy_list_t; + +/* Library globals */ +extern libzfs_handle_t *g_zfs; +extern boolean_t do_print; + +/* be_create.c */ +int be_set_uuid(char *); +int be_get_uuid(const char *, uuid_t *); + +/* be_list.c */ +int _be_list(char *, be_node_list_t **); +int be_get_zone_be_list(char *, char *, be_node_list_t **); + +/* be_mount.c */ +int _be_mount(char *, char **, int); +int _be_unmount(char *, int); +int be_mount_pool(zfs_handle_t *, char **, char **, boolean_t *); +int be_unmount_pool(zfs_handle_t *, char *, char *); +int be_mount_zone_root(zfs_handle_t *, be_mount_data_t *); +int be_unmount_zone_root(zfs_handle_t *, be_unmount_data_t *); +int be_get_legacy_fs(char *, char *, char *, char *, be_fs_list_data_t *); +void be_free_fs_list(be_fs_list_data_t *); +char *be_get_ds_from_dir(char *); +int be_make_tmp_mountpoint(char **); + +/* be_snapshot.c */ +int _be_create_snapshot(char *, char **, char *); +int _be_destroy_snapshot(char *, char *); + +/* be_utils.c */ +boolean_t be_zfs_init(void); +void be_zfs_fini(void); +void be_make_root_ds(const char *, const char *, char *, int); +void be_make_container_ds(const char *, char *, int); +char *be_make_name_from_ds(const char *, char *); +int be_append_menu(char *, char *, char *, char *, char *); +int be_remove_menu(char *, char *, char *); +int be_update_menu(char *, char *, char *, char *); +int be_default_grub_bootfs(const char *, char **); +boolean_t be_has_menu_entry(char *, char *, int *); +int be_run_cmd(char *, char *, int, char *, int); +int be_change_grub_default(char *, char *); +int be_update_vfstab(char *, char *, char *, be_fs_list_data_t *, char *); +int be_update_zone_vfstab(zfs_handle_t *, char *, char *, char *, + be_fs_list_data_t *); +int be_maxsize_avail(zfs_handle_t *, uint64_t *); +char *be_auto_snap_name(void); +char *be_auto_be_name(char *); +char *be_auto_zone_be_name(char *, char *); +char *be_default_policy(void); +boolean_t valid_be_policy(char *); +boolean_t be_valid_auto_snap_name(char *); +boolean_t be_valid_be_name(const char *); +void be_print_err(char *, ...); +int be_find_current_be(be_transaction_data_t *); +int zfs_err_to_be_err(libzfs_handle_t *); +int errno_to_be_err(int); + +/* be_activate.c */ +int _be_activate(char *); +int be_activate_current_be(void); +boolean_t be_is_active_on_boot(char *); + +/* be_zones.c */ +void be_make_zoneroot(char *, char *, int); +int be_find_active_zone_root(zfs_handle_t *, char *, char *, int); +int be_find_mounted_zone_root(char *, char *, char *, int); +boolean_t be_zone_supported(char *); +zoneBrandList_t *be_get_supported_brandlist(void); +int be_zone_get_parent_uuid(const char *, uuid_t *); + +/* check architecture functions */ +char *be_get_default_isa(void); +boolean_t be_is_isa(char *); +boolean_t be_has_grub(void); + +/* callback functions */ +int be_exists_callback(zpool_handle_t *, void *); +int be_find_zpool_callback(zpool_handle_t *, void *); +int be_zpool_find_current_be_callback(zpool_handle_t *, void *); +int be_zfs_find_current_be_callback(zfs_handle_t *, void *); +int be_check_be_roots_callback(zpool_handle_t *, void *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBBE_PRIV_H */ diff --git a/usr/src/lib/libbe/common/llib-lbe b/usr/src/lib/libbe/common/llib-lbe new file mode 100644 index 0000000000..2330b0a0dd --- /dev/null +++ b/usr/src/lib/libbe/common/llib-lbe @@ -0,0 +1,29 @@ +/* + * 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) 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <libbe.h> +#include <libbe_priv.h> diff --git a/usr/src/lib/libbe/common/mapfile-vers b/usr/src/lib/libbe/common/mapfile-vers new file mode 100644 index 0000000000..358b8a730c --- /dev/null +++ b/usr/src/lib/libbe/common/mapfile-vers @@ -0,0 +1,60 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +# +# MAPFILE HEADER START +# +# WARNING: STOP NOW. DO NOT MODIFY THIS FILE. +# Object versioning must comply with the rules detailed in +# +# usr/src/lib/README.mapfiles +# +# You should not be making modifications here until you've read the most current +# copy of that file. If you need help, contact a gatekeeper for guidance. +# +# MAPFILE HEADER END +# + +$mapfile_version 2 + +SYMBOL_VERSION SUNWprivate_1.1 { + global: + be_activate; + be_copy; + be_create_snapshot; + be_destroy; + be_destroy_snapshot; + be_err_to_str; + be_free_list; + be_init; + be_list; + be_mount; + be_rename; + be_rollback; + be_unmount; + be_valid_be_name; + libbe_print_errors; + local: + *; +}; diff --git a/usr/src/lib/libbe/i386/Makefile b/usr/src/lib/libbe/i386/Makefile new file mode 100644 index 0000000000..c6e75c4787 --- /dev/null +++ b/usr/src/lib/libbe/i386/Makefile @@ -0,0 +1,28 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libbe/sparc/Makefile b/usr/src/lib/libbe/sparc/Makefile new file mode 100644 index 0000000000..c6e75c4787 --- /dev/null +++ b/usr/src/lib/libbe/sparc/Makefile @@ -0,0 +1,28 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libbe/tbeadm/Makefile b/usr/src/lib/libbe/tbeadm/Makefile new file mode 100644 index 0000000000..2c9dc95daf --- /dev/null +++ b/usr/src/lib/libbe/tbeadm/Makefile @@ -0,0 +1,69 @@ +# +# 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. +# + +ARCH = $(TARGET_ARCH:-%=%) + +PROG = tbeadm + + +OBJS = tbeadm.o +SRCS = $(OBJS:%.o=../%.c) + +include ../../Makefile.lib + +#LIBS= $(DYNLIB) + +INCLUDE = -I../common + +STATICDEPLIBS = ../$(ARCH)/libbe.a +STATICLDLIBS += -linstzones -L/lib -L../$(ARCH) -lzfs -lnvpair \ + -luuid -lgen -Bstatic -lbe -Bdynamic + +DEPLIBS = ../$(ARCH)/libbe.so.1 +LDLIBS += -L/lib -L../pics/$(ARCH) -lzfs -lnvpair -lbe + +CPPFLAGS += -D_LARGEFILE64_SOURCE=1 -D_REENTRANT ${INCLUDE} +CFLAGS += -g -DDEBUG + + +$(PROG): $(OBJS) $(STATICDEPLIBS) + $(LINK.c) -o $@ $@.c $(OBJS) $(STATICLDLIBS) + $(POST_PROCESS) + +all: $(PROG) + +install_h: + +install: all + +lint: + $(LINT.c) $(SRCS) $(LDLIBS) + +clobber: clean + $(RM) tbeadm.o + +clean: + $(RM) tbeadm + diff --git a/usr/src/lib/libbe/tbeadm/tbeadm.c b/usr/src/lib/libbe/tbeadm/tbeadm.c new file mode 100644 index 0000000000..f0de6a50ef --- /dev/null +++ b/usr/src/lib/libbe/tbeadm/tbeadm.c @@ -0,0 +1,837 @@ +/* + * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * System includes + */ + +#include <stdio.h> +#include <strings.h> +#include <libzfs.h> + +#include "libbe.h" + +static int be_do_create(int argc, char **argv); +static int be_do_destroy(int argc, char **argv); +static int be_do_list(int argc, char **argv); +static int be_do_mount(int argc, char **argv); +static int be_do_unmount(int argc, char **argv); +static int be_do_rename(int argc, char **argv); +static int be_do_activate(int argc, char **argv); +static int be_do_create_snapshot(int argc, char **argv); +static int be_do_destroy_snapshot(int argc, char **argv); +static int be_do_rollback(int argc, char **argv); +static void usage(void); + +typedef struct be_command { + const char *name; + int (*func)(int argc, char **argv); +} be_command_t; + +static be_command_t command_table[] = { + { "create", be_do_create }, + { "destroy", be_do_destroy }, + { "list", be_do_list }, + { "mount", be_do_mount }, + { "unmount", be_do_unmount }, + { "rename", be_do_rename }, + { "activate", be_do_activate }, + { "create_snap", be_do_create_snapshot }, + { "destroy_snap", be_do_destroy_snapshot }, +}; + +static int fs_num = 2; +static int shared_fs_num = 2; +static char *fs_names[2] = {"/", "/opt"}; +static char *shared_fs_names[4] = {"/export", "/export/home"}; + +static void +usage(void) +{ + (void) printf("usage:\n" + "\ttbeadm\n" + "\ttbeadm create [-d BE_desc] [-e nonActiveBe | -i] \n" + "\t\t[-o property=value] ... [-p zpool] [beName]\n" + "\ttbeadm destroy [-fs] beName\n" + "\ttbeadm create_snap [-p policy] beName [snapshot]\n" + "\ttbeadm destroy_snap beName snapshot\n" + "\ttbeadm list [-s] [beName]\n" + "\ttbeadm mount [-s ro|rw] beName mountpoint\n" + "\ttbeadm unmount [-f] beName\n" + "\ttbeadm rename origBeName newBeName\n" + "\ttbeadm activate beName\n" + "\ttbeadm rollback beName snapshot\n"); +} + +int +main(int argc, char **argv) { + + if (argc < 2) { + usage(); + return (1); + } + + /* Turn error printing on */ + libbe_print_errors(B_TRUE); + + if (strcmp(argv[1], "create") == 0) { + return (be_do_create(argc - 1, argv + 1)); + } else if (strcmp(argv[1], "destroy") == 0) { + return (be_do_destroy(argc - 1, argv + 1)); + } else if (strcmp(argv[1], "list") == 0) { + return (be_do_list(argc - 1, argv + 1)); + } else if (strcmp(argv[1], "mount") == 0) { + return (be_do_mount(argc - 1, argv + 1)); + } else if (strcmp(argv[1], "unmount") == 0) { + return (be_do_unmount(argc - 1, argv + 1)); + } else if (strcmp(argv[1], "rename") == 0) { + return (be_do_rename(argc - 2, argv + 2)); + } else if (strcmp(argv[1], "activate") == 0) { + return (be_do_activate(argc - 2, argv + 2)); + } else if (strcmp(argv[1], "create_snap") == 0) { + return (be_do_create_snapshot(argc - 1, argv + 1)); + } else if (strcmp(argv[1], "destroy_snap") == 0) { + return (be_do_destroy_snapshot(argc - 2, argv + 2)); + } else if (strcmp(argv[1], "rollback") == 0) { + return (be_do_rollback(argc - 2, argv + 2)); + } else { + usage(); + return (1); + } + + /* NOTREACHED */ +} + +static int +be_do_create(int argc, char **argv) +{ + nvlist_t *be_attrs; + char *obe_name = NULL; + char *snap_name = NULL; + char *nbe_zpool = NULL; + char *nbe_name = NULL; + char *nbe_desc = NULL; + nvlist_t *zfs_props = NULL; + char *propname = NULL; + char *propval = NULL; + char *strval = NULL; + boolean_t init = B_FALSE; + int c; + int ret = BE_SUCCESS; + + if (nvlist_alloc(&zfs_props, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + while ((c = getopt(argc, argv, "d:e:io:p:")) != -1) { + switch (c) { + case 'd': + nbe_desc = optarg; + break; + case 'e': + obe_name = optarg; + break; + case 'i': + /* Special option to test be_init() function */ + init = B_TRUE; + break; + case 'o': + if (zfs_props == NULL) { + if (nvlist_alloc(&zfs_props, NV_UNIQUE_NAME, + 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + } + + propname = optarg; + if ((propval = strchr(propname, '=')) == NULL) { + (void) fprintf(stderr, "missing " + "'=' for -o option\n"); + return (1); + } + *propval = '\0'; + propval++; + if (nvlist_lookup_string(zfs_props, propname, + &strval) == 0) { + (void) fprintf(stderr, "property '%s' " + "specified multiple times\n", propname); + return (1); + } + if (nvlist_add_string(zfs_props, propname, propval) + != 0) { + (void) fprintf(stderr, "internal " + "error: out of memory\n"); + return (1); + } + break; + case 'p': + nbe_zpool = optarg; + break; + default: + usage(); + return (1); + } + } + + if (init && obe_name) { + printf("ERROR: -e and -i are exclusive options\n"); + usage(); + return (1); + } + + argc -= optind; + argv += optind; + + if (argc == 1) { + nbe_name = argv[0]; + } else if (argc > 1) { + usage(); + return (1); + } + + if (obe_name) { + /* + * Check if obe_name is really a snapshot name. + * If so, split it out. + */ + char *cp = NULL; + + cp = strrchr(obe_name, '@'); + if (cp != NULL) { + cp[0] = '\0'; + if (cp[1] != NULL && cp[1] != '\0') { + snap_name = cp+1; + } + } + } + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (zfs_props) { + if (nvlist_add_nvlist(be_attrs, BE_ATTR_ZFS_PROPERTIES, + zfs_props) != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ZFS_PROPERTES (%s).\n", zfs_props); + return (1); + } + } + + if (obe_name != NULL) { + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + } + + if (snap_name != NULL) { + if (nvlist_add_string(be_attrs, BE_ATTR_SNAP_NAME, snap_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_SNAP_NANE (%s).\n", snap_name); + return (1); + } + } + + if (nbe_zpool != NULL) { + if (nvlist_add_string(be_attrs, BE_ATTR_NEW_BE_POOL, nbe_zpool) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_NEW_BE_POOL (%s).\n", nbe_zpool); + return (1); + } + } + + if (nbe_name) { + if (nvlist_add_string(be_attrs, BE_ATTR_NEW_BE_NAME, nbe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_NEW_BE_NAME (%s).\n", nbe_name); + return (1); + } + } + + if (nbe_desc) { + if (nvlist_add_string(be_attrs, BE_ATTR_NEW_BE_DESC, nbe_desc) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_NEW_BE_DESC (%s)\n", nbe_desc); + return (1); + } + } + + if (init) { + /* + * Add the default file system test values to test + * creating an initial BE. + */ + if (nvlist_add_uint16(be_attrs, BE_ATTR_FS_NUM, fs_num) != 0) { + printf("nvlist_add_uint16 failed for BE_ATTR_FS_NUM " + "(%d).\n", fs_num); + return (1); + } + + if (nvlist_add_string_array(be_attrs, BE_ATTR_FS_NAMES, + fs_names, fs_num) != 0) { + printf("nvlist_add_string_array failed for " + "BE_ATTR_FS_NAMES\n"); + return (1); + } + + if (nvlist_add_uint16(be_attrs, BE_ATTR_SHARED_FS_NUM, + shared_fs_num) != 0) { + printf("nvlist_add_uint16 failed for " + "BE_ATTR_SHARED_FS_NUM (%d).\n", shared_fs_num); + return (1); + } + + if (nvlist_add_string_array(be_attrs, BE_ATTR_SHARED_FS_NAMES, + shared_fs_names, shared_fs_num) != 0) { + printf("nvlist_add_string_array failed for " + "BE_ATTR_SHARED_FS_NAMES\n"); + return (1); + } + + return (be_init(be_attrs)); + } + + ret = be_copy(be_attrs); + + if (!nbe_name & ret == BE_SUCCESS) { + /* + * We requested an auto named BE; find out the + * name of the BE that was created for us and + * the auto snapshot created from the original BE. + */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_NEW_BE_NAME, + &nbe_name) != 0) { + printf("failed to get BE_ATTR_NEW_BE_NAME attribute\n"); + ret = 1; + } else { + printf("Auto named BE: %s\n", nbe_name); + } + + if (nvlist_lookup_string(be_attrs, BE_ATTR_SNAP_NAME, + &snap_name) != 0) { + printf("failed to get BE_ATTR_SNAP_NAME attribute\n"); + ret = 1; + } else { + printf("Auto named snapshot: %s\n", snap_name); + } + } + + return (ret); +} + +static int +be_do_destroy(int argc, char **argv) +{ + nvlist_t *be_attrs; + int c; + int destroy_flags = 0; + char *be_name; + + while ((c = getopt(argc, argv, "fs")) != -1) { + switch (c) { + case 'f': + destroy_flags |= BE_DESTROY_FLAG_FORCE_UNMOUNT; + break; + case 's': + destroy_flags |= BE_DESTROY_FLAG_SNAPSHOTS; + break; + default: + usage(); + return (1); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(); + return (1); + } + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, argv[0]) != 0) { + printf("nvlist_add_string failed for BE_ATTR_NEW_BE_NAME " + "(%s).\n", argv[0]); + return (1); + } + + if (nvlist_add_uint16(be_attrs, BE_ATTR_DESTROY_FLAGS, destroy_flags) + != 0) { + printf("nvlist_add_uint16 failed for " + "BE_ATTR_DESTROY_FLAGS.\n"); + return (1); + } + + return (be_destroy(be_attrs)); +} + +static int +be_do_list(int argc, char **argv) +{ + int err = BE_SUCCESS; + be_node_list_t *be_nodes; + be_node_list_t *cur_be; + boolean_t snaps = B_FALSE; + int c = 0; + + while ((c = getopt(argc, argv, "s")) != -1) { + switch (c) { + case 's': + snaps = B_TRUE; + break; + default: + usage(); + return (1); + } + } + + argc -= optind; + argv += optind; + + + if (argc == 1) { + err = be_list(argv[0], &be_nodes); + } else { + err = be_list(NULL, &be_nodes); + } + + if (err == BE_SUCCESS) { + + printf( + "BE name\t\tActive\tActive \tDataset\t\t\tPolicy\tUUID\n"); + printf( + " \t\t \ton boot\t \t\t\t \t \n"); + printf( + "-------\t\t------\t-------\t-------\t\t\t------\t----\n"); + + for (cur_be = be_nodes; cur_be != NULL; + cur_be = cur_be->be_next_node) { + + int name_len = strlen(cur_be->be_node_name); + int ds_len = strlen(cur_be->be_root_ds); + + printf("%s%s%s\t%s\t%s%s%s\t%s\n", + cur_be->be_node_name, + name_len < 8 ? "\t\t" : "\t", + cur_be->be_active ? "yes" : "no", + cur_be->be_active_on_boot ? "yes" : "no", + cur_be->be_root_ds, + ds_len < 8 ? "\t\t\t" : + (ds_len < 16 ? "\t\t" : "\t"), + cur_be->be_policy_type, + cur_be->be_uuid_str ? cur_be->be_uuid_str : "-"); + if (snaps) { + be_snapshot_list_t *snapshots = NULL; + printf("Snapshot Name\n"); + printf("--------------\n"); + for (snapshots = cur_be->be_node_snapshots; + snapshots != NULL; snapshots = + snapshots->be_next_snapshot) { + printf("%s\n", + snapshots->be_snapshot_name); + } + } + } + } + + be_free_list(be_nodes); + return (err); +} + +static int +be_do_rename(int argc, char **argv) +{ + nvlist_t *be_attrs; + char *obe_name; + char *nbe_name; + + if (argc < 1 || argc > 2) { + usage(); + return (1); + } + + obe_name = argv[0]; + nbe_name = argv[1]; + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_NEW_BE_NAME, nbe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_NEW_BE_NAME (%s).\n", nbe_name); + return (1); + } + + return (be_rename(be_attrs)); + +} + +static int +be_do_create_snapshot(int argc, char **argv) +{ + nvlist_t *be_attrs; + char *obe_name = NULL; + char *snap_name = NULL; + char *policy = NULL; + int c; + int ret = BE_SUCCESS; + + while ((c = getopt(argc, argv, "p:")) != -1) { + switch (c) { + case 'p': + policy = optarg; + break; + default: + usage(); + return (1); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1 || argc > 2) { + usage(); + return (1); + } + + obe_name = argv[0]; + + if (argc > 1) { + /* Snapshot name provided */ + snap_name = argv[1]; + } + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + + if (policy) { + if (nvlist_add_string(be_attrs, BE_ATTR_POLICY, policy) != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_POLICY (%s).\n", policy); + return (1); + } + } + + if (snap_name) { + if (nvlist_add_string(be_attrs, BE_ATTR_SNAP_NAME, snap_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_SNAP_NAME (%s).\n", snap_name); + return (1); + } + } + + ret = be_create_snapshot(be_attrs); + + if (!snap_name && ret == BE_SUCCESS) { + /* + * We requested an auto named snapshot; find out + * the snapshot name that was created for us. + */ + if (nvlist_lookup_string(be_attrs, BE_ATTR_SNAP_NAME, + &snap_name) != 0) { + printf("failed to get BE_ATTR_SNAP_NAME attribute\n"); + ret = 1; + } else { + printf("Auto named snapshot: %s\n", snap_name); + } + } + + return (ret); +} + +static int +be_do_destroy_snapshot(int argc, char **argv) +{ + nvlist_t *be_attrs; + char *obe_name; + char *snap_name; + + if (argc != 2) { + usage(); + return (1); + } + + obe_name = argv[0]; + snap_name = argv[1]; + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_SNAP_NAME, snap_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_SNAP_NAME (%s).\n", snap_name); + return (1); + } + + return (be_destroy_snapshot(be_attrs)); +} + +static int +be_do_rollback(int argc, char **argv) +{ + nvlist_t *be_attrs; + char *obe_name; + char *snap_name; + + if (argc < 1 || argc > 2) { + usage(); + return (1); + } + + obe_name = argv[0]; + snap_name = argv[1]; + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_SNAP_NAME, snap_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_SNAP_NAME (%s).\n", snap_name); + return (1); + } + + return (be_rollback(be_attrs)); +} + +static int +be_do_activate(int argc, char **argv) +{ + nvlist_t *be_attrs; + char *obe_name; + + if (argc < 1 || argc > 2) { + usage(); + return (1); + } + + obe_name = argv[0]; + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + + return (be_activate(be_attrs)); +} + +static int +be_do_mount(int argc, char **argv) +{ + nvlist_t *be_attrs; + int c; + boolean_t shared_fs = B_FALSE; + int mount_flags = 0; + char *obe_name; + char *mountpoint; + + while ((c = getopt(argc, argv, "s:")) != -1) { + switch (c) { + case 's': + shared_fs = B_TRUE; + + mount_flags |= BE_MOUNT_FLAG_SHARED_FS; + + if (strcmp(optarg, "rw") == 0) { + mount_flags |= BE_MOUNT_FLAG_SHARED_RW; + } else if (strcmp(optarg, "ro") != 0) { + printf("The -s flag requires an argument " + "[ rw | ro ]\n"); + usage(); + return (1); + } + + break; + default: + usage(); + return (1); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1 || argc > 2) { + usage(); + return (1); + } + + obe_name = argv[0]; + + if (argc == 2) { + mountpoint = argv[1]; + } else { + /* + * XXX - Need to generate a random mountpoint here; + * right now we're just exitting if one isn't supplied. + */ + usage(); + return (1); + } + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_MOUNTPOINT, mountpoint) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_MOUNTPOINT (%s).\n", mountpoint); + return (1); + } + + if (shared_fs) { + if (nvlist_add_uint16(be_attrs, BE_ATTR_MOUNT_FLAGS, + mount_flags) != 0) { + printf("nvlist_add_uint16 failed for " + "BE_ATTR_MOUNT_FLAGS (%d).\n", mount_flags); + return (1); + } + } + + return (be_mount(be_attrs)); +} + + +static int +be_do_unmount(int argc, char **argv) +{ + nvlist_t *be_attrs; + int c; + int unmount_flags = 0; + char *obe_name; + + while ((c = getopt(argc, argv, "f")) != -1) { + switch (c) { + case 'f': + unmount_flags |= BE_UNMOUNT_FLAG_FORCE; + break; + default: + usage(); + return (1); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(); + return (1); + } + + obe_name = argv[0]; + + if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { + printf("nvlist_alloc failed.\n"); + return (1); + } + + if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, obe_name) + != 0) { + printf("nvlist_add_string failed for " + "BE_ATTR_ORIG_BE_NAME (%s).\n", obe_name); + return (1); + } + + if (nvlist_add_uint16(be_attrs, BE_ATTR_UNMOUNT_FLAGS, unmount_flags) + != 0) { + printf("nvlist_add_uint16 failed for " + "BE_ATTR_UNMOUNT_FLAGS\n"); + return (1); + } + + return (be_unmount(be_attrs)); +} diff --git a/usr/src/lib/libsecdb/exec_attr.txt b/usr/src/lib/libsecdb/exec_attr.txt index 034afd84b6..2b6e991979 100644 --- a/usr/src/lib/libsecdb/exec_attr.txt +++ b/usr/src/lib/libsecdb/exec_attr.txt @@ -269,6 +269,7 @@ Process Management:solaris:cmd:::/usr/sbin/rcapadm:uid=0 Project Management:solaris:cmd:::/usr/sbin/projadd:euid=0 Project Management:solaris:cmd:::/usr/sbin/projmod:euid=0 Project Management:solaris:cmd:::/usr/sbin/projdel:euid=0 +Software Installation:suser:cmd:::/sbin/beadm:uid=0;gid=bin Software Installation:suser:cmd:::/usr/bin/ln:euid=0 Software Installation:suser:cmd:::/usr/bin/pkginfo:uid=0 Software Installation:suser:cmd:::/usr/bin/pkgmk:uid=0 diff --git a/usr/src/lib/libsecdb/prof_attr.txt b/usr/src/lib/libsecdb/prof_attr.txt index 65a651ea1b..85c9a89e9d 100644 --- a/usr/src/lib/libsecdb/prof_attr.txt +++ b/usr/src/lib/libsecdb/prof_attr.txt @@ -79,7 +79,7 @@ Rights Delegation:::Delegate ability to assign rights to users and roles:auths=s Rmvolmgr Management:::Manage Removable Volume Manager SMF service:auths=solaris.smf.manage.rmvolmgr;help=RtRmvolmgrMngmnt.html Service Management:::Manage services:auths=solaris.smf.manage,solaris.smf.modify Service Operator:::Administer services:auths=solaris.smf.manage,solaris.smf.modify.framework -Software Installation:::Add application software to the system:help=RtSoftwareInstall.html +Software Installation:::Add application software to the system:profiles=ZFS File System Management;help=RtSoftwareInstall.html Stop:::Last Profile evaluated, default profiles are not considered:help=RtReservedProfile.html System Administrator:::Can perform most non-security administrative tasks:profiles=Audit Review,Printer Management,Cron Management,Device Management,File System Management,Mail Management,Maintenance and Repair,Media Backup,Media Restore,Name Service Management,Network Management,Object Access Management,Process Management,Software Installation,User Management,Project Management,All;help=RtSysAdmin.html System Event Management:::Manage system events and system event channels:help=RtSysEvMngmnt.html diff --git a/usr/src/lib/pylibbe/Makefile b/usr/src/lib/pylibbe/Makefile new file mode 100644 index 0000000000..64e8106641 --- /dev/null +++ b/usr/src/lib/pylibbe/Makefile @@ -0,0 +1,54 @@ +# +# 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. +# + +include ../Makefile.lib + +SUBDIRS= $(MACH) +XGETTEXT= $(GNUXGETTEXT) +XGETFLAGS= $(GNUXGETFLAGS) + +all := TARGET= all +install := TARGET= install +clean := TARGET= clean +clobber := TARGET= clobber +lint := TARGET= lint + +MSGFILES = `$(FIND) . -name '*.py' -o -name '*.c'` +POFILE = libbe_py.po + +.KEEP_STATE: + +all install clean clobber lint: $(SUBDIRS) + +$(POFILE): pofile_MSGFILES + +_msg: $(MSGDOMAINPOFILE) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include ../../Makefile.msg.targ diff --git a/usr/src/lib/pylibbe/Makefile.com b/usr/src/lib/pylibbe/Makefile.com new file mode 100644 index 0000000000..6fa379ddcc --- /dev/null +++ b/usr/src/lib/pylibbe/Makefile.com @@ -0,0 +1,60 @@ +# +# 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. +# + +LIBRARY = libbe_py.a +VERS = +OBJECTS = libbe_py.o + +include ../../Makefile.lib + +PYTHON = $(PYTHON_26) +LIBLINKS = +SRCDIR = ../common +ROOTLIBDIR= $(ROOT)/usr/lib/python2.6/vendor-packages +PYOBJS= $(PYSRCS:%.py=$(SRCDIR)/%.pyc) +PYFILES= $(PYSRCS) $(PYSRCS:%.py=%.pyc) +ROOTPYBEFILES= $(PYFILES:%=$(ROOTLIBDIR)/%) + +C99MODE= $(C99_ENABLE) + +LIBS = $(DYNLIB) +LDLIBS += -lbe -lnvpair -lc +CFLAGS += $(CCVERBOSE) +CPPFLAGS += -I/usr/include/python2.6 -D_FILE_OFFSET_BITS=64 -I../../libbe/common + +.KEEP_STATE: + +all install := LDLIBS += -lpython2.6 + +all: $(PYOBJS) $(LIBS) + +install: all $(ROOTPYBEFILES) + +$(ROOTLIBDIR)/%: % + $(INS.pyfile) + +lint: lintcheck + +include ../../Makefile.targ diff --git a/usr/src/lib/pylibbe/common/libbe_py.c b/usr/src/lib/pylibbe/common/libbe_py.c new file mode 100644 index 0000000000..b4f6ca0e52 --- /dev/null +++ b/usr/src/lib/pylibbe/common/libbe_py.c @@ -0,0 +1,1094 @@ +/* + * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include <Python.h> +#include <sys/varargs.h> +#include <stdio.h> +#include <libnvpair.h> + +#include <libbe.h> +#include <libbe_priv.h> + +enum { + BE_PY_SUCCESS = 0, + BE_PY_ERR_APPEND = 6000, + BE_PY_ERR_DICT, + BE_PY_ERR_LIST, + BE_PY_ERR_NVLIST, + BE_PY_ERR_PARSETUPLE, + BE_PY_ERR_PRINT_ERR, + BE_PY_ERR_VAR_CONV, +} bePyErr; + +/* + * public libbe functions + */ + +PyObject *beCreateSnapshot(PyObject *, PyObject *); +PyObject *beCopy(PyObject *, PyObject *); +PyObject *beList(PyObject *, PyObject *); +PyObject *beActivate(PyObject *, PyObject *); +PyObject *beDestroy(PyObject *, PyObject *); +PyObject *beDestroySnapshot(PyObject *, PyObject *); +PyObject *beRename(PyObject *, PyObject *); +PyObject *beMount(PyObject *, PyObject *); +PyObject *beUnmount(PyObject *, PyObject *); +PyObject *bePrintErrors(PyObject *, PyObject *); +PyObject *beGetErrDesc(PyObject *, PyObject *); +char *beMapLibbePyErrorToString(int); +void initlibbe_py(); + +static boolean_t convertBEInfoToDictionary(be_node_list_t *be, + PyObject **listDict); +static boolean_t convertDatasetInfoToDictionary(be_dataset_list_t *ds, + PyObject **listDict); +static boolean_t convertSnapshotInfoToDictionary(be_snapshot_list_t *ss, + PyObject **listDict); +static boolean_t convertPyArgsToNvlist(nvlist_t **nvList, int numArgs, ...); + + +/* ~~~~~~~~~~~~~~~ */ +/* Public Funtions */ +/* ~~~~~~~~~~~~~~~ */ + +/* + * Function: beCreateSnapshot + * Description: Convert Python args to nvlist pairs and + * call libbe:be_create_snapshot to create a + * snapshot of all the datasets within a BE + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the BE to create a snapshot of + * snapName - The name of the snapshot to create (optional) + * + * The following public attribute values. defined by libbe.h, + * are used by this function: + * + * Returns a pointer to a python object and an optional snapshot name: + * 0, [snapName] - Success + * 1, [snapName] - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beCreateSnapshot(PyObject *self, PyObject *args) +{ + char *beName = NULL; + char *snapName = NULL; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + PyObject *retVals = NULL; + + if (!PyArg_ParseTuple(args, "z|z", &beName, &snapName)) { + return (Py_BuildValue("[is]", BE_PY_ERR_PARSETUPLE, NULL)); + } + + if (!convertPyArgsToNvlist(&beAttrs, 4, + BE_ATTR_ORIG_BE_NAME, beName, + BE_ATTR_SNAP_NAME, snapName)) { + nvlist_free(beAttrs); + return (Py_BuildValue("[is]", BE_PY_ERR_NVLIST, NULL)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if ((ret = be_create_snapshot(beAttrs)) != 0) { + nvlist_free(beAttrs); + return (Py_BuildValue("[is]", ret, NULL)); + } + if (snapName == NULL) { + if (nvlist_lookup_pairs(beAttrs, NV_FLAG_NOENTOK, + BE_ATTR_SNAP_NAME, DATA_TYPE_STRING, &snapName, + NULL) != 0) { + nvlist_free(beAttrs); + return (Py_BuildValue("[is]", + BE_PY_ERR_NVLIST, NULL)); + } + retVals = Py_BuildValue("[is]", ret, snapName); + nvlist_free(beAttrs); + return (retVals); + } + nvlist_free(beAttrs); + + return (Py_BuildValue("[is]", ret, NULL)); +} + +/* + * Function: beCopy + * Description: Convert Python args to nvlist pairs and call libbe:be_copy + * to create a Boot Environment + * Parameters: + * args - pointer to a python object containing: + * trgtBeName - The name of the BE to create + * srcBeName - The name of the BE used to create trgtBeName (optional) + * rpool - The pool to create the new BE in (optional) + * srcSnapName - The snapshot name (optional) + * beNameProperties - The properties to use when creating + * the BE (optional) + * + * Returns a pointer to a python object. That Python object will consist of + * the return code and optional attributes, trgtBeName and snapshotName + * BE_SUCCESS, [trgtBeName], [trgtSnapName] - Success + * 1, [trgtBeName], [trgtSnapName] - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beCopy(PyObject *self, PyObject *args) +{ + char *trgtBeName = NULL; + char *srcBeName = NULL; + char *srcSnapName = NULL; + char *trgtSnapName = NULL; + char *rpool = NULL; + char *beDescription = NULL; + int pos = 0; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + nvlist_t *beProps = NULL; + PyObject *beNameProperties = NULL; + PyObject *pkey = NULL; + PyObject *pvalue = NULL; + PyObject *retVals = NULL; + + if (!PyArg_ParseTuple(args, "|zzzzOz", &trgtBeName, &srcBeName, + &srcSnapName, &rpool, &beNameProperties, &beDescription)) { + return (Py_BuildValue("[iss]", BE_PY_ERR_PARSETUPLE, + NULL, NULL)); + } + + if (!convertPyArgsToNvlist(&beAttrs, 10, + BE_ATTR_NEW_BE_NAME, trgtBeName, + BE_ATTR_ORIG_BE_NAME, srcBeName, + BE_ATTR_SNAP_NAME, srcSnapName, + BE_ATTR_NEW_BE_POOL, rpool, + BE_ATTR_NEW_BE_DESC, beDescription)) { + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", BE_PY_ERR_NVLIST, NULL, NULL)); + } + + if (beNameProperties != NULL) { + if (nvlist_alloc(&beProps, NV_UNIQUE_NAME, 0) != 0) { + (void) printf("nvlist_alloc failed.\n"); + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", BE_PY_ERR_NVLIST, + NULL, NULL)); + } + while (PyDict_Next(beNameProperties, &pos, &pkey, &pvalue)) { + if (!convertPyArgsToNvlist(&beProps, 2, + PyString_AsString(pkey), + PyString_AsString(pvalue))) { + nvlist_free(beProps); + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", BE_PY_ERR_NVLIST, + NULL, NULL)); + } + } + } + + if (beProps != NULL && beAttrs != NULL && + nvlist_add_nvlist(beAttrs, BE_ATTR_ZFS_PROPERTIES, + beProps) != 0) { + nvlist_free(beProps); + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", BE_PY_ERR_NVLIST, + NULL, NULL)); + } + + if (beProps != NULL) nvlist_free(beProps); + + if (trgtBeName == NULL) { + /* + * Caller wants to get back the BE_ATTR_NEW_BE_NAME and + * BE_ATTR_SNAP_NAME + */ + if ((ret = be_copy(beAttrs)) != BE_SUCCESS) { + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", ret, NULL, NULL)); + } + + /* + * When no trgtBeName is passed to be_copy, be_copy + * returns an auto generated beName and snapshot name. + */ + if (nvlist_lookup_string(beAttrs, BE_ATTR_NEW_BE_NAME, + &trgtBeName) != 0) { + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", BE_PY_ERR_NVLIST, + NULL, NULL)); + } + if (nvlist_lookup_string(beAttrs, BE_ATTR_SNAP_NAME, + &trgtSnapName) != 0) { + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", BE_PY_ERR_NVLIST, + NULL, NULL)); + } + + retVals = Py_BuildValue("[iss]", BE_PY_SUCCESS, + trgtBeName, trgtSnapName); + nvlist_free(beAttrs); + return (retVals); + + } else { + ret = be_copy(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("[iss]", ret, NULL, NULL)); + } +} + +/* + * Function: beList + * Description: Convert Python args to nvlist pairs and call libbe:be_list + * to gather information about Boot Environments + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the BE to list (optional) + * + * Returns a pointer to a python object. That Python object will consist of + * the return code and a list of Dicts or NULL. + * BE_PY_SUCCESS, listOfDicts - Success + * bePyErr or be_errno_t, NULL - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beList(PyObject *self, PyObject *args) +{ + char *beName = NULL; + int ret = BE_PY_SUCCESS; + be_node_list_t *list = NULL; + be_node_list_t *be = NULL; + PyObject *dict = NULL; + PyObject *listOfDicts = NULL; + + if ((listOfDicts = PyList_New(0)) == NULL) { + ret = BE_PY_ERR_DICT; + listOfDicts = Py_None; + goto done; + } + + if (!PyArg_ParseTuple(args, "|z", &beName)) { + ret = BE_PY_ERR_PARSETUPLE; + goto done; + } + + if ((ret = be_list(beName, &list)) != BE_SUCCESS) { + goto done; + } + + for (be = list; be != NULL; be = be->be_next_node) { + be_dataset_list_t *ds = be->be_node_datasets; + be_snapshot_list_t *ss = be->be_node_snapshots; + + if ((dict = PyDict_New()) == NULL) { + ret = BE_PY_ERR_DICT; + goto done; + } + + if (!convertBEInfoToDictionary(be, &dict)) { + /* LINTED */ + Py_DECREF(dict); + ret = BE_PY_ERR_VAR_CONV; + goto done; + } + + if (PyList_Append(listOfDicts, dict) != 0) { + /* LINTED */ + Py_DECREF(dict); + ret = BE_PY_ERR_APPEND; + goto done; + } + + /* LINTED */ + Py_DECREF(dict); + + while (ds != NULL) { + if ((dict = PyDict_New()) == NULL) { + ret = BE_PY_ERR_DICT; + goto done; + } + + if (!convertDatasetInfoToDictionary(ds, &dict)) { + /* LINTED */ + Py_DECREF(dict); + ret = BE_PY_ERR_VAR_CONV; + goto done; + } + + if (PyList_Append(listOfDicts, dict) != 0) { + /* LINTED */ + Py_DECREF(dict); + ret = BE_PY_ERR_APPEND; + goto done; + } + + ds = ds->be_next_dataset; + + /* LINTED */ + Py_DECREF(dict); + } + + + while (ss != NULL) { + if ((dict = PyDict_New()) == NULL) { + /* LINTED */ + Py_DECREF(dict); + ret = BE_PY_ERR_DICT; + goto done; + } + + if (!convertSnapshotInfoToDictionary(ss, &dict)) { + /* LINTED */ + Py_DECREF(dict); + ret = BE_PY_ERR_VAR_CONV; + goto done; + } + + if (PyList_Append(listOfDicts, dict) != 0) { + /* LINTED */ + Py_DECREF(dict); + ret = BE_PY_ERR_APPEND; + goto done; + } + + ss = ss->be_next_snapshot; + + /* LINTED */ + Py_DECREF(dict); + } + } + +done: + if (list != NULL) + be_free_list(list); + return (Py_BuildValue("[iO]", ret, listOfDicts)); +} + +/* + * Function: beActivate + * Description: Convert Python args to nvlist pairs and call libbe:be_activate + * to activate a Boot Environment + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the BE to activate + * + * Returns a pointer to a python object: + * BE_SUCCESS - Success + * bePyErr or be_errno_t - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beActivate(PyObject *self, PyObject *args) +{ + char *beName = NULL; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + + if (!PyArg_ParseTuple(args, "z", &beName)) { + return (Py_BuildValue("i", BE_PY_ERR_PARSETUPLE)); + } + + if (!convertPyArgsToNvlist(&beAttrs, 2, BE_ATTR_ORIG_BE_NAME, beName)) { + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + ret = be_activate(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("i", ret)); +} + +/* + * Function: beDestroy + * Description: Convert Python args to nvlist pairs and call libbe:be_destroy + * to destroy a Boot Environment + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the BE to destroy + * + * Returns a pointer to a python object: + * BE_SUCCESS - Success + * bePyErr or be_errno_t - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beDestroy(PyObject *self, PyObject *args) +{ + char *beName = NULL; + int destroy_snaps = 0; + int force_unmount = 0; + int destroy_flags = 0; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + + if (!PyArg_ParseTuple(args, "z|ii", &beName, &destroy_snaps, + &force_unmount)) { + return (Py_BuildValue("i", BE_PY_ERR_PARSETUPLE)); + } + + if (destroy_snaps == 1) + destroy_flags |= BE_DESTROY_FLAG_SNAPSHOTS; + + if (force_unmount == 1) + destroy_flags |= BE_DESTROY_FLAG_FORCE_UNMOUNT; + + if (!convertPyArgsToNvlist(&beAttrs, 2, BE_ATTR_ORIG_BE_NAME, beName)) { + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (nvlist_add_uint16(beAttrs, BE_ATTR_DESTROY_FLAGS, destroy_flags) + != 0) { + (void) printf("nvlist_add_uint16 failed for " + "BE_ATTR_DESTROY_FLAGS (%d).\n", destroy_flags); + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + ret = be_destroy(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("i", ret)); +} + +/* + * Function: beDestroySnapshot + * Description: Convert Python args to nvlist pairs and call libbe:be_destroy + * to destroy a snapshot of a Boot Environment + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the BE to destroy + * snapName - The name of the snapshot to destroy + * + * Returns a pointer to a python object: + * BE_SUCCESS - Success + * bePyErr or be_errno_t - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beDestroySnapshot(PyObject *self, PyObject *args) +{ + char *beName = NULL; + char *snapName = NULL; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + + if (!PyArg_ParseTuple(args, "zz", &beName, &snapName)) { + return (Py_BuildValue("i", BE_PY_ERR_PARSETUPLE)); + } + + if (!convertPyArgsToNvlist(&beAttrs, 4, + BE_ATTR_ORIG_BE_NAME, beName, + BE_ATTR_SNAP_NAME, snapName)) { + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + ret = be_destroy_snapshot(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("i", ret)); +} + +/* + * Function: beRename + * Description: Convert Python args to nvlist pairs and call libbe:be_rename + * to rename a Boot Environment + * Parameters: + * args - pointer to a python object containing: + * oldBeName - The name of the old Boot Environment + * newBeName - The name of the new Boot Environment + * + * Returns a pointer to a python object: + * BE_SUCCESS - Success + * bePyErr or be_errno_t - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beRename(PyObject *self, PyObject *args) +{ + char *oldBeName = NULL; + char *newBeName = NULL; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + + if (!PyArg_ParseTuple(args, "zz", &oldBeName, &newBeName)) { + return (Py_BuildValue("i", BE_PY_ERR_PARSETUPLE)); + } + + if (!convertPyArgsToNvlist(&beAttrs, 4, + BE_ATTR_ORIG_BE_NAME, oldBeName, + BE_ATTR_NEW_BE_NAME, newBeName)) { + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + ret = be_rename(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("i", ret)); +} + +/* + * Function: beMount + * Description: Convert Python args to nvlist pairs and call libbe:be_mount + * to mount a Boot Environment + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the Boot Environment to mount + * mountpoint - The path of the mountpoint to mount the + * Boot Environment on (optional) + * + * Returns a pointer to a python object: + * BE_SUCCESS - Success + * bePyErr or be_errno_t - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beMount(PyObject *self, PyObject *args) +{ + char *beName = NULL; + char *mountpoint = NULL; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + + if (!PyArg_ParseTuple(args, "zz", &beName, &mountpoint)) { + return (Py_BuildValue("i", BE_PY_ERR_PARSETUPLE)); + } + + if (!convertPyArgsToNvlist(&beAttrs, 4, + BE_ATTR_ORIG_BE_NAME, beName, + BE_ATTR_MOUNTPOINT, mountpoint)) { + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + ret = be_mount(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("i", ret)); +} + +/* + * Function: beUnmount + * Description: Convert Python args to nvlist pairs and call libbe:be_unmount + * to unmount a Boot Environment + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the Boot Environment to unmount + * + * Returns a pointer to a python object: + * BE_SUCCESS - Success + * bePyErr or be_errno_t - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beUnmount(PyObject *self, PyObject *args) +{ + char *beName = NULL; + int force_unmount = 0; + int unmount_flags = 0; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + + if (!PyArg_ParseTuple(args, "z|i", &beName, &force_unmount)) { + return (Py_BuildValue("i", BE_PY_ERR_PARSETUPLE)); + } + + if (force_unmount == 1) + unmount_flags |= BE_UNMOUNT_FLAG_FORCE; + + if (!convertPyArgsToNvlist(&beAttrs, 2, + BE_ATTR_ORIG_BE_NAME, beName)) { + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (nvlist_add_uint16(beAttrs, BE_ATTR_UNMOUNT_FLAGS, unmount_flags) + != 0) { + (void) printf("nvlist_add_uint16 failed for " + "BE_ATTR_UNMOUNT_FLAGS (%d).\n", unmount_flags); + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + ret = be_unmount(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("i", ret)); +} + +/* + * Function: beRollback + * Description: Convert Python args to nvlist pairs and call libbe:be_rollback + * to rollback a Boot Environment to a previously taken + * snapshot. + * Parameters: + * args - pointer to a python object containing: + * beName - The name of the Boot Environment to unmount + * + * Returns a pointer to a python object: + * BE_SUCCESS - Success + * bePyErr or be_errno_t - Failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beRollback(PyObject *self, PyObject *args) +{ + char *beName = NULL; + char *snapName = NULL; + int ret = BE_PY_SUCCESS; + nvlist_t *beAttrs = NULL; + + if (!PyArg_ParseTuple(args, "zz", &beName, &snapName)) { + return (Py_BuildValue("i", BE_PY_ERR_PARSETUPLE)); + } + + if (!convertPyArgsToNvlist(&beAttrs, 4, + BE_ATTR_ORIG_BE_NAME, beName, + BE_ATTR_SNAP_NAME, snapName)) { + nvlist_free(beAttrs); + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + if (beAttrs == NULL) { + return (Py_BuildValue("i", BE_PY_ERR_NVLIST)); + } + + ret = be_rollback(beAttrs); + nvlist_free(beAttrs); + return (Py_BuildValue("i", ret)); +} + +/* + * Function: bePrintErrors + * Description: Convert Python args to boolean and call libbe_print_errors to + * turn on/off error output for the library. + * Parameter: + * args - pointer to a python object containing: + * print_errors - Boolean that turns library error + * printing on or off. + * Parameters: + * args - pointer to a python object containing: + * 0 - do not print errors - Python boolean "False" + * 1 - print errors - Python boolean "True" + * + * Returns 1 on missing or invalid argument, 0 otherwise + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +bePrintErrors(PyObject *self, PyObject *args) +{ + int print_errors; + + if (!PyArg_ParseTuple(args, "i", &print_errors) || + (print_errors != 1 && print_errors != 0)) + return (Py_BuildValue("i", BE_PY_ERR_PRINT_ERR)); + libbe_print_errors(print_errors == 1); + return (Py_BuildValue("i", BE_PY_SUCCESS)); +} + +/* + * Function: beGetErrDesc + * Description: Convert Python args to an int and call be_err_to_str to + * map an error code to an error string. + * Parameter: + * args - pointer to a python object containing: + * errCode - value to map to an error string. + * + * Returns: error string or NULL + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beGetErrDesc(PyObject *self, PyObject *args) +{ + int errCode = 0; + char *beErrStr = NULL; + + if (!PyArg_ParseTuple(args, "i", &errCode)) { + return (Py_BuildValue("s", NULL)); + } + + /* + * First check libbe_py errors. If NULL is returned check error codes + * in libbe. + */ + + if ((beErrStr = beMapLibbePyErrorToString(errCode)) == NULL) { + beErrStr = be_err_to_str(errCode); + } + + return (Py_BuildValue("s", beErrStr)); +} + +/* + * Function: beVerifyBEName + * Description: Call be_valid_be_name() to verify the BE name. + * Parameter: + * args - pointer to a python object containing: + * string - value to map to a string. + * + * Returns: 0 for success or 1 for failure + * Scope: + * Public + */ +/* ARGSUSED */ +PyObject * +beVerifyBEName(PyObject *self, PyObject *args) +{ + char *string = NULL; + + if (!PyArg_ParseTuple(args, "s", &string)) { + return (Py_BuildValue("i", 1)); + } + + if (be_valid_be_name(string)) { + return (Py_BuildValue("i", 0)); + } else { + return (Py_BuildValue("i", 1)); + } +} + +/* ~~~~~~~~~~~~~~~~~ */ +/* Private Functions */ +/* ~~~~~~~~~~~~~~~~~ */ + +static boolean_t +convertBEInfoToDictionary(be_node_list_t *be, PyObject **listDict) +{ + if (be->be_node_name != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_ORIG_BE_NAME, + PyString_FromString(be->be_node_name)) != 0) { + return (B_FALSE); + } + } + + if (be->be_rpool != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_ORIG_BE_POOL, + PyString_FromString(be->be_rpool)) != 0) { + return (B_FALSE); + } + } + + if (be->be_mntpt != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_MOUNTPOINT, + PyString_FromString(be->be_mntpt)) != 0) { + return (B_FALSE); + } + } + + if (PyDict_SetItemString(*listDict, BE_ATTR_MOUNTED, + (be->be_mounted ? Py_True : Py_False)) != 0) { + return (B_FALSE); + } + + if (PyDict_SetItemString(*listDict, BE_ATTR_ACTIVE, + (be->be_active ? Py_True : Py_False)) != 0) { + return (B_FALSE); + } + + if (PyDict_SetItemString(*listDict, BE_ATTR_ACTIVE_ON_BOOT, + (be->be_active_on_boot ? Py_True : Py_False)) != 0) { + return (B_FALSE); + } + + if (be->be_space_used != 0) { + if (PyDict_SetItemString(*listDict, BE_ATTR_SPACE, + PyLong_FromUnsignedLongLong(be->be_space_used)) != 0) { + return (B_FALSE); + } + } + + if (be->be_root_ds != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_ROOT_DS, + PyString_FromString(be->be_root_ds)) != 0) { + return (B_FALSE); + } + } + + if (be->be_node_creation != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_DATE, + PyLong_FromLong(be->be_node_creation)) != 0) { + return (B_FALSE); + } + } + + if (be->be_policy_type != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_POLICY, + PyString_FromString(be->be_policy_type)) != 0) { + return (B_FALSE); + } + } + + if (be->be_uuid_str != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_UUID_STR, + PyString_FromString(be->be_uuid_str)) != 0) { + return (B_FALSE); + } + } + + return (B_TRUE); +} + +static boolean_t +convertDatasetInfoToDictionary(be_dataset_list_t *ds, PyObject **listDict) +{ + if (ds->be_dataset_name != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_DATASET, + PyString_FromString(ds->be_dataset_name)) != 0) { + return (B_FALSE); + } + } + + if (PyDict_SetItemString(*listDict, BE_ATTR_STATUS, + (ds->be_ds_mounted ? Py_True : Py_False)) != 0) { + return (B_FALSE); + } + + if (ds->be_ds_mntpt != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_MOUNTPOINT, + PyString_FromString(ds->be_ds_mntpt)) != 0) { + return (B_FALSE); + } + } + + if (PyDict_SetItemString(*listDict, BE_ATTR_MOUNTED, + (ds->be_ds_mounted ? Py_True : Py_False)) != 0) { + return (B_FALSE); + } + + if (ds->be_ds_space_used != 0) { + if (PyDict_SetItemString(*listDict, BE_ATTR_SPACE, + PyLong_FromUnsignedLongLong(ds->be_ds_space_used)) + != 0) { + return (B_FALSE); + } + } + + if (ds->be_dataset_name != 0) { + if (PyDict_SetItemString(*listDict, BE_ATTR_DATASET, + PyString_FromString(ds->be_dataset_name)) != 0) { + return (B_FALSE); + } + } + + if (ds->be_ds_plcy_type != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_POLICY, + PyString_FromString(ds->be_ds_plcy_type)) != 0) { + return (B_FALSE); + } + } + + if (ds->be_ds_creation != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_DATE, + PyLong_FromLong(ds->be_ds_creation)) != 0) { + return (B_FALSE); + } + } + + return (B_TRUE); +} + +static boolean_t +convertSnapshotInfoToDictionary(be_snapshot_list_t *ss, PyObject **listDict) +{ + if (ss->be_snapshot_name != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_SNAP_NAME, + PyString_FromString(ss->be_snapshot_name)) != 0) { + return (B_FALSE); + } + } + + if (ss->be_snapshot_creation != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_DATE, + PyLong_FromLong(ss->be_snapshot_creation)) != 0) { + return (B_FALSE); + } + } + + if (ss->be_snapshot_type != NULL) { + if (PyDict_SetItemString(*listDict, BE_ATTR_POLICY, + PyString_FromString(ss->be_snapshot_type)) != 0) { + return (B_FALSE); + } + } + + if (ss->be_snapshot_space_used != 0) { + if (PyDict_SetItemString(*listDict, BE_ATTR_SPACE, + PyLong_FromUnsignedLongLong(ss->be_snapshot_space_used)) + != 0) { + return (B_FALSE); + } + } + + return (B_TRUE); +} + +/* + * Convert string arguments to nvlist attributes + */ + +static boolean_t +convertPyArgsToNvlist(nvlist_t **nvList, int numArgs, ...) +{ + char *pt, *pt2; + va_list ap; + int i; + + if (*nvList == NULL) { + if (nvlist_alloc(nvList, NV_UNIQUE_NAME, 0) != 0) { + (void) printf("nvlist_alloc failed.\n"); + return (B_FALSE); + } + } + + va_start(ap, numArgs); + + for (i = 0; i < numArgs; i += 2) { + if ((pt = va_arg(ap, char *)) == NULL || + (pt2 = va_arg(ap, char *)) == NULL) { + continue; + } + if (nvlist_add_string(*nvList, pt, pt2) != 0) { + (void) printf("nvlist_add_string failed for %s (%s).\n", + pt, pt2); + nvlist_free(*nvList); + return (B_FALSE); + } + } + + va_end(ap); + + return (B_TRUE); +} + +/* + * Function: beMapLibbePyErrorToString + * Description: Convert Python args to an int and map an error code to an + * error string. + * Parameter: + * errCode - value to map to an error string. + * + * Returns error string or NULL + * Scope: + * Public + */ + +char * +beMapLibbePyErrorToString(int errCode) +{ + switch (errCode) { + case BE_PY_ERR_APPEND: + return ("Unable to append a dictionary to a list " + "of dictinaries."); + case BE_PY_ERR_DICT: + return ("Creation of a Python dictionary failed."); + case BE_PY_ERR_LIST: + return ("beList() failed."); + case BE_PY_ERR_NVLIST: + return ("An nvlist operation failed."); + case BE_PY_ERR_PARSETUPLE: + return ("PyArg_ParseTuple() failed to convert variable to C."); + case BE_PY_ERR_PRINT_ERR: + return ("bePrintErrors() failed."); + case BE_PY_ERR_VAR_CONV: + return ("Unable to add variables to a Python dictionary."); + default: + return (NULL); + } +} + +/* Private python initialization structure */ + +static struct PyMethodDef libbeMethods[] = { + {"beCopy", (PyCFunction)beCopy, METH_VARARGS, "Create/Copy a BE."}, + {"beCreateSnapshot", (PyCFunction)beCreateSnapshot, METH_VARARGS, + "Create a snapshot."}, + {"beDestroy", (PyCFunction)beDestroy, METH_VARARGS, "Destroy a BE."}, + {"beDestroySnapshot", (PyCFunction)beDestroySnapshot, METH_VARARGS, + "Destroy a snapshot."}, + {"beMount", (PyCFunction)beMount, METH_VARARGS, "Mount a BE."}, + {"beUnmount", (PyCFunction)beUnmount, METH_VARARGS, "Unmount a BE."}, + {"beList", (PyCFunction)beList, METH_VARARGS, "List BE info."}, + {"beRename", (PyCFunction)beRename, METH_VARARGS, "Rename a BE."}, + {"beActivate", (PyCFunction)beActivate, METH_VARARGS, "Activate a BE."}, + {"beRollback", (PyCFunction)beRollback, METH_VARARGS, "Rollback a BE."}, + {"bePrintErrors", (PyCFunction)bePrintErrors, METH_VARARGS, + "Enable/disable error printing."}, + {"beGetErrDesc", (PyCFunction)beGetErrDesc, METH_VARARGS, + "Map Error codes to strings."}, + {"beVerifyBEName", (PyCFunction)beVerifyBEName, METH_VARARGS, + "Verify BE name."}, + {NULL, NULL, 0, NULL} +}; + +void +initlibbe_py() +{ + /* PyMODINIT_FUNC; */ + (void) Py_InitModule("libbe_py", libbeMethods); +} diff --git a/usr/src/lib/pylibbe/common/mapfile-vers b/usr/src/lib/pylibbe/common/mapfile-vers new file mode 100644 index 0000000000..73f2bceaf5 --- /dev/null +++ b/usr/src/lib/pylibbe/common/mapfile-vers @@ -0,0 +1,46 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +# +# MAPFILE HEADER START +# +# WARNING: STOP NOW. DO NOT MODIFY THIS FILE. +# Object versioning must comply with the rules detailed in +# +# usr/src/lib/README.mapfiles +# +# You should not be making modifications here until you've read the most current +# copy of that file. If you need help, contact a gatekeeper for guidance. +# +# MAPFILE HEADER END +# + +$mapfile_version 2 + +SYMBOL_VERSION SUNWprivate { + global: + initlibbe_py; + local: + *; +}; diff --git a/usr/src/lib/pylibbe/i386/Makefile b/usr/src/lib/pylibbe/i386/Makefile new file mode 100644 index 0000000000..5a771c0816 --- /dev/null +++ b/usr/src/lib/pylibbe/i386/Makefile @@ -0,0 +1,28 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) diff --git a/usr/src/lib/pylibbe/sparc/Makefile b/usr/src/lib/pylibbe/sparc/Makefile new file mode 100644 index 0000000000..5a771c0816 --- /dev/null +++ b/usr/src/lib/pylibbe/sparc/Makefile @@ -0,0 +1,28 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) diff --git a/usr/src/pkg/manifests/SUNWbeadm.mf b/usr/src/pkg/manifests/SUNWbeadm.mf new file mode 100644 index 0000000000..5135ad7a36 --- /dev/null +++ b/usr/src/pkg/manifests/SUNWbeadm.mf @@ -0,0 +1,28 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +set name=pkg.fmri value=pkg:/SUNWbeadm@0.5.11,5.11-0.133 +set name=pkg.renamed value=true +set name=variant.arch value=$(ARCH) +depend fmri=pkg:/install/beadm@0.5.11,5.11-0.133 type=require diff --git a/usr/src/pkg/manifests/install-beadm.mf b/usr/src/pkg/manifests/install-beadm.mf new file mode 100644 index 0000000000..b514748b28 --- /dev/null +++ b/usr/src/pkg/manifests/install-beadm.mf @@ -0,0 +1,55 @@ +# +# 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) 2010, Oracle and/or its affiliates. All rights reserved. +# + +set name=pkg.fmri value=pkg:/install/beadm@$(PKGVERS) +set name=pkg.description value="Boot Environment Management Tool" +set name=pkg.summary value="beadm utility" +set name=info.classification value=org.opensolaris.category.2008:System/Core +set name=variant.arch value=$(ARCH) +dir path=sbin group=sys +dir path=usr group=sys +dir path=usr/include +dir path=usr/lib +dir path=usr/lib/python2.6 +dir path=usr/lib/python2.6/vendor-packages +dir path=usr/lib/python2.6/vendor-packages/beadm +dir path=usr/sbin +dir path=usr/share group=sys +file path=sbin/beadm mode=0555 +file path=usr/include/libbe.h +file path=usr/lib/libbe.so.1 +file path=usr/lib/llib-lbe +file path=usr/lib/llib-lbe.ln +file path=usr/lib/python2.6/vendor-packages/beadm/BootEnvironment.py mode=0444 +file path=usr/lib/python2.6/vendor-packages/beadm/BootEnvironment.pyc \ + mode=0444 +file path=usr/lib/python2.6/vendor-packages/beadm/__init__.py mode=0444 +file path=usr/lib/python2.6/vendor-packages/beadm/__init__.pyc mode=0444 +file path=usr/lib/python2.6/vendor-packages/beadm/messages.py mode=0444 +file path=usr/lib/python2.6/vendor-packages/beadm/messages.pyc mode=0444 +file path=usr/lib/python2.6/vendor-packages/libbe_py.so +license cr_Sun license=cr_Sun +license lic_CDDL license=lic_CDDL +link path=usr/lib/libbe.so target=./libbe.so.1 +link path=usr/sbin/beadm target=../../sbin/beadm |
