1# CDDL HEADER START 2# 3# The contents of this file are subject to the terms of the 4# Common Development and Distribution License (the "License"). 5# You may not use this file except in compliance with the License. 6# 7# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 8# or http://www.opensolaris.org/os/licensing. 9# See the License for the specific language governing permissions 10# and limitations under the License. 11# 12# When distributing Covered Code, include this CDDL HEADER in each 13# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 14# If applicable, add the following below this CDDL HEADER, with the 15# fields enclosed by brackets "[]" replaced with your own identifying 16# information: Portions Copyright [yyyy] [name of copyright owner] 17# 18# CDDL HEADER END 19# 20 21# 22# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 23# 24 25"""Boot Environment classes used by beadm.""" 26 27import datetime 28 29class BootEnvironment: 30 """Boot Environment object that is used by beadm to manage command line 31 options, arguments and the log.""" 32 33 def __init__(self): 34 self.trgt_rpool = None 35 self.trgt_be_name_or_snapshot = None 36 self.src_be_name_or_snapshot = None 37 self.properties = {} 38 self.log_id = None 39 self.log = None 40 self.msg_buf = {} 41 self.description = None 42 43class listBootEnvironment: 44 """Base class for beadm list 45 Determine the BE's to display. Prints command output according to option: 46 -d - dataset 47 -s - snapshot 48 -a - all (both dataset and snapshot) 49 <none> - only BE information 50 The -H option produces condensed, parseable output 51 The ';' delimits each field in the output. BEs with multiple 52 datasets will have multiple lines in the output. 53 """ 54 55 def list(self, be_list, ddh, be_name): 56 """ print all output for beadm list command 57 be_list - list of all BEs 58 ddh - if True, Do not Display Headers - just parseable data 59 be_name - user-specified BE, if any 60 61 returns 0 for success 62 side effect: beadm list output printed to stdout 63 """ 64 65 #If we're listing Headers, initialize the array holding the 66 #column widths with the header widths themselves. Later on, 67 #the data in this array will get adjusted as we process actual 68 #row data and find that a piece of data is wider than its 69 #column header. 70 bemaxout = [0 for i in range(len(self.hdr[0]))] 71 if not ddh: 72 #iterate all header rows since their fields may not 73 #be of equal length. 74 for header in self.hdr: 75 icol = 0 76 for hc in header: 77 if len(hc) + 1 > bemaxout[icol]: 78 bemaxout[icol] = len(hc) + 1 79 icol += 1 80 81 #collect BEs 82 beout = {} #matrix of output text [row][attribute] 83 beoutname = {} #list of BE names [row] 84 be_space = {} #space used totals for BE [BE name]['space_used','ibei'] 85 ibe = 0 #BE index 86 spacecol = -1 #to contain column where space used is displayed 87 for be in be_list: 88 if 'orig_be_name' in be: 89 cur_be = be['orig_be_name'] 90 cur_be_obj = be 91 92 #if BE name specified, collect matching BEs 93 if be_name is not None and not self.beMatch(be, be_name): 94 continue 95 attrs = () 96 #identify BE|dataset|snapshot attributes 97 att = '' 98 for att in ('orig_be_name', 'dataset', 'snap_name'): 99 if att in be and att in self.lattrs: 100 attrs = self.lattrs[att] 101 if att == 'orig_be_name': 102 be_space[cur_be] = {} 103 be_space[cur_be]['space_used'] = 0 104 be_space[cur_be]['ibe'] = ibe 105 if not ddh and len(cur_be) + 1 > bemaxout[0]: 106 bemaxout[0] = len(cur_be) + 1 107 break 108 beout[ibe] = {} 109 beoutname[ibe] = cur_be 110 111 icol = 0 #first column 112 for at in attrs: 113 #for option -s, withhold subordinate datasets 114 if self.__class__.__name__ == 'SnapshotList' and \ 115 att == 'snap_name' and 'snap_name' in be and \ 116 '/' in be[att]: 117 break 118 #convert output to readable format and save 119 save = self.getAttr(at, be, ddh, cur_be_obj) 120 beout[ibe][at] = save 121 #maintain maximum column widths 122 if not ddh and len(save) + 1 > bemaxout[icol]: 123 bemaxout[icol] = len(save) + 1 124 #sum all snapshots for BE 125 if at == 'space_used' and 'space_used' in be: 126 spacecol = icol 127 icol += 1 #next column 128 ibe += 1 129 if 'space_used' in be: 130 #sum all snapshots and datasets for BE in 'beadm list' 131 if isinstance(self, BEList): 132 be_space[cur_be]['space_used'] += be.get('space_used') 133 elif cur_be in be_space and \ 134 ('space_used' not in be_space[cur_be] or 135 be_space[cur_be]['space_used'] == 0): 136 #list space used separately for other options 137 be_space[cur_be]['space_used'] = be.get('space_used') 138 139 #output format total lengths for each BE with any snapshots 140 for cur_be in be_space: 141 save = self.getSpaceValue(be_space[cur_be]['space_used'], ddh) 142 ibe = be_space[cur_be]['ibe'] 143 beout[ibe]['space_used'] = save 144 #expand column if widest column entry 145 if (spacecol != -1) and \ 146 (not ddh and len(save) + 1 > bemaxout[spacecol]): 147 bemaxout[spacecol] = len(save) + 1 148 149 #print headers in columns 150 if not ddh: 151 for header in self.hdr: 152 outstr = '' 153 for icol in range(len(header)): 154 outstr += header[icol].ljust(bemaxout[icol]) 155 if outstr != '': 156 print outstr 157 158 #print collected output in columns 159 outstr = '' 160 prev_be = None 161 cur_be = None 162 for ibe in beout: #index output matrix 163 if beoutname[ibe] != None: 164 cur_be = beoutname[ibe] 165 #find attributes for BE type 166 curtype = None 167 for att in ('orig_be_name', 'dataset', 'snap_name'): 168 if att in beout[ibe]: 169 attrs = self.lattrs[att] 170 curtype = att 171 break 172 173 if curtype == None: #default to BE 174 curtype = 'orig_be_name' 175 if 'orig_be_name' in self.lattrs: 176 attrs = self.lattrs['orig_be_name'] 177 else: attrs = () 178 179 if not ddh: 180 if prev_be != cur_be and cur_be != None: 181 #for -d,-s,-a, print BE alone on line 182 if self.__class__.__name__ != 'BEList': 183 print cur_be 184 prev_be = cur_be 185 186 #print for one BE/snapshot/dataset 187 icol = 0 #first column 188 189 #if this is a 'dataset' or 'snap_name', start line with BE 190 #name token 191 if ddh and curtype != 'orig_be_name': 192 outstr = cur_be 193 194 for at in attrs: #for each attribute specified in table 195 if ddh: #add separators for parsing 196 if outstr != '': 197 outstr += ';' #attribute separator 198 if at in beout[ibe] and beout[ibe][at] != '-' and \ 199 beout[ibe][at] != '': 200 outstr += beout[ibe][at] 201 else: #append text justified in column 202 if at in beout[ibe]: 203 outstr += beout[ibe][at].ljust(bemaxout[icol]) 204 icol += 1 #next column 205 206 if outstr != '': 207 print outstr 208 outstr = '' 209 210 return 0 211 212 def beMatch(self, be, be_name): 213 """find match on user-specified BE.""" 214 215 if 'orig_be_name' in be: 216 return be.get('orig_be_name') == be_name 217 if 'dataset' in be: 218 if be.get('dataset') == be_name: 219 return True 220 out = be.get('dataset').split("/") 221 return out[0] == be_name 222 if 'snap_name' in be: 223 if be.get('snap_name') == be_name: 224 return True 225 out = be.get('snap_name').split('@') 226 if out[0] == be_name: 227 return True 228 out = be.get('snap_name').split('/') 229 return out[0] == be_name 230 return False 231 232 def getAttr(self, at, be, ddh, beobj): 233 """ 234 Extract information by attribute and format for printing 235 returns '?' if normally present attribute not found - error. 236 """ 237 if at == 'blank': 238 return ' ' 239 if at == 'dash': 240 return '-' 241 if at == 'orig_be_name': 242 if at not in be: 243 return '-' 244 ret = be[at] 245 if ddh or self.__class__.__name__ == 'BEList': 246 return ret 247 return ' ' + ret #indent 248 if at == 'snap_name': 249 if at not in be: 250 return '-' 251 if self.__class__.__name__ == 'CompleteList': 252 ret = self.prependRootDS(be[at], beobj) 253 else: 254 ret = be[at] 255 if ddh: 256 return ret 257 return ' ' + ret #indent 258 if at == 'dataset': 259 if at not in be: 260 return '-' 261 if self.__class__.__name__ == 'DatasetList' or \ 262 self.__class__.__name__ == 'CompleteList': 263 ret = self.prependRootDS(be[at], beobj) 264 else: 265 ret = be[at] 266 if ddh: 267 return ret 268 return ' ' + ret #indent 269 if at == 'active': 270 if at not in be: 271 return '-' 272 ret = '' 273 if 'active' in be and be['active']: 274 ret += 'N' 275 if 'active_boot' in be and be['active_boot']: 276 ret += 'R' 277 if ret == '': 278 return '-' 279 return ret 280 if at == 'mountpoint': 281 if at not in be: 282 return '-' 283 if 'mounted' not in be or not be['mounted']: 284 return '-' 285 return be[at] 286 if at == 'space_used': 287 if at not in be: 288 return '0' 289 return self.getSpaceValue(be[at], ddh) 290 if at == 'mounted': 291 if at not in be: 292 return '-' 293 return be[at] 294 if at == 'date': 295 if at not in be: 296 return '?' 297 if ddh: 298 return str(be[at]) #timestamp in seconds 299 sec = str(datetime.datetime.fromtimestamp(be[at])) 300 return sec[0:len(sec)-3] #trim seconds 301 if at == 'policy': 302 if at not in be: 303 return '?' 304 return be[at] 305 if at == 'root_ds': 306 if at not in be: 307 return '?' 308 if ddh or self.__class__.__name__ == 'BEList': 309 return be[at] 310 return ' ' + be[at] 311 if at == 'uuid_str': 312 if at not in be: 313 return '-' 314 return be[at] 315 #default case - no match on attribute 316 return be[at] 317 318 def getSpaceValue(self, num, ddh): 319 """Readable formatting for disk space size.""" 320 321 if ddh: 322 return str(num) #return size in bytes as string 323 324 kilo = 1024.0 325 mega = 1048576.0 326 giga = 1073741824.0 327 tera = 1099511627776.0 328 329 if num == None: 330 return '0' 331 if num < kilo: 332 return str(num) + 'B' 333 if num < mega: 334 return str('%.1f' % (num / kilo)) + 'K' 335 if num < giga: 336 return str('%.2f' % (num / mega)) + 'M' 337 if num < tera: 338 return str('%.2f' % (num / giga)) + 'G' 339 return str('%.2f' % (num / tera)) + 'T' 340 341 def prependRootDS(self, val, beobj): 342 """Prepend root dataset name with BE name stripped.""" 343 344 root_ds = beobj.get('root_ds') 345 return root_ds[0:root_ds.rfind('/')+1] + val 346 347 348"""Top level "beadm list" derived classes defined here. 349 Only table definition is done here - all methods are in the base class. 350 Tables driving list: 351 hdr - list of text to output for each column 352 lattrs - dictionary of attributes 353 Each entry specifies either BE, dataset, snapshot with 354 an attribute key: 355 orig_be_name - for BEs 356 dataset - for datasets 357 snap_name - for snapshots 358 Each list item in entry indicates specific datum for 359 column 360 Number of hdr columns must equal number of lattrs entries 361 unless ddh (dontDisplayHeaders) is true. 362""" 363class BEList(listBootEnvironment): 364 """specify header and attribute information for BE-only output""" 365 366 def __init__(self, ddh): 367 """Init function for the class.""" 368 self.hdr = \ 369 ('BE','Active','Mountpoint','Space','Policy','Created'), \ 370 ('--','------','----------','-----','------','-------') 371 if ddh: 372 self.lattrs = {'orig_be_name':('orig_be_name', 'uuid_str', 373 'active', 'mountpoint', 'space_used', 'policy', 374 'date')} 375 else: 376 self.lattrs = {'orig_be_name':('orig_be_name', 'active', 377 'mountpoint', 'space_used', 'policy', 'date')} 378 379class DatasetList(listBootEnvironment): 380 """ 381 specify header and attribute information for dataset output, 382 -d option 383 """ 384 def __init__(self, ddh): 385 """Init function for the class.""" 386 387 self.hdr = \ 388 ('BE/Dataset','Active','Mountpoint','Space','Policy','Created'), \ 389 ('----------','------','----------','-----','------','-------') 390 if ddh: 391 self.lattrs = { \ 392 'orig_be_name':('orig_be_name', 'root_ds', 'active', 393 'mountpoint', 'space_used', 'policy', 'date'), \ 394 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', 395 'policy', 'date')} 396 else: 397 self.lattrs = { \ 398 'orig_be_name':('root_ds', 'active', 'mountpoint', 399 'space_used', 'policy', 'date'), \ 400 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', 401 'policy', 'date')} 402 403class SnapshotList(listBootEnvironment): 404 """ 405 specify header and attribute information for snapshot output, 406 -s option 407 """ 408 def __init__(self, ddh): 409 """Init function for the class.""" 410 411 self.hdr = \ 412 ('BE/Snapshot','Space','Policy','Created'), \ 413 ('-----------','-----','------','-------') 414 self.lattrs = {'snap_name':('snap_name', 'space_used', 'policy', 415 'date')} 416 417class CompleteList(listBootEnvironment): 418 """ 419 specify header and attribute information for BE and/or dataset and/or 420 snapshot output, 421 -a or -ds options 422 """ 423 def __init__(self, ddh): 424 """Init function for the class.""" 425 426 self.hdr = \ 427 ('BE/Dataset/Snapshot','Active','Mountpoint','Space','Policy','Created'), \ 428 ('-------------------','------','----------','-----','------','-------') 429 if ddh: 430 self.lattrs = { \ 431 'orig_be_name':('orig_be_name', 'root_ds', 'active', 432 'mountpoint', 'space_used', 'policy', 'date'), 433 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', 434 'policy', 'date'), 435 'snap_name':('snap_name', 'dash', 'dash', 'space_used', 436 'policy', 'date')} 437 else: 438 self.lattrs = { \ 439 'orig_be_name':('root_ds', 'active', 'mountpoint', 440 'space_used', 'policy', 'date'), \ 441 'dataset':('dataset', 'dash', 'mountpoint', 'space_used', 442 'policy', 'date'), 443 'snap_name':('snap_name', 'dash', 'dash', 'space_used', 444 'policy', 'date')} 445