1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li"""This module gives the mkfs creation options for an existing filesystem. 3*9c5db199SXin Li 4*9c5db199SXin Litune2fs or xfs_growfs is called according to the filesystem. The results, 5*9c5db199SXin Lifilesystem tunables, are parsed and mapped to corresponding mkfs options. 6*9c5db199SXin Li""" 7*9c5db199SXin Lifrom __future__ import absolute_import 8*9c5db199SXin Lifrom __future__ import division 9*9c5db199SXin Lifrom __future__ import print_function 10*9c5db199SXin Li 11*9c5db199SXin Liimport os, re, tempfile 12*9c5db199SXin Li 13*9c5db199SXin Liimport six 14*9c5db199SXin Li 15*9c5db199SXin Liimport common 16*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, utils 17*9c5db199SXin Li 18*9c5db199SXin Li 19*9c5db199SXin Lidef opt_string2dict(opt_string): 20*9c5db199SXin Li """Breaks the mkfs.ext* option string into dictionary.""" 21*9c5db199SXin Li # Example string: '-j -q -i 8192 -b 4096'. There may be extra whitespaces. 22*9c5db199SXin Li opt_dict = {} 23*9c5db199SXin Li 24*9c5db199SXin Li for item in opt_string.split('-'): 25*9c5db199SXin Li item = item.strip() 26*9c5db199SXin Li if ' ' in item: 27*9c5db199SXin Li (opt, value) = item.split(' ', 1) 28*9c5db199SXin Li opt_dict['-%s' % opt] = value 29*9c5db199SXin Li elif item != '': 30*9c5db199SXin Li opt_dict['-%s' % item] = None 31*9c5db199SXin Li # Convert all the digit strings to int. 32*9c5db199SXin Li for key, value in six.iteritems(opt_dict): 33*9c5db199SXin Li if value and value.isdigit(): 34*9c5db199SXin Li opt_dict[key] = int(value) 35*9c5db199SXin Li 36*9c5db199SXin Li return opt_dict 37*9c5db199SXin Li 38*9c5db199SXin Li 39*9c5db199SXin Lidef parse_mke2fs_conf(fs_type, conf_file='/etc/mke2fs.conf'): 40*9c5db199SXin Li """Parses mke2fs config file for default settings.""" 41*9c5db199SXin Li # Please see /ect/mke2fs.conf for an example. 42*9c5db199SXin Li default_opt = {} 43*9c5db199SXin Li fs_opt = {} 44*9c5db199SXin Li current_fs_type = '' 45*9c5db199SXin Li current_section = '' 46*9c5db199SXin Li f = open(conf_file, 'r') 47*9c5db199SXin Li for line in f: 48*9c5db199SXin Li if '[defaults]' == line.strip(): 49*9c5db199SXin Li current_section = '[defaults]' 50*9c5db199SXin Li elif '[fs_types]' == line.strip(): 51*9c5db199SXin Li current_section = '[fs_types]' 52*9c5db199SXin Li elif current_section == '[defaults]': 53*9c5db199SXin Li components = line.split('=', 1) 54*9c5db199SXin Li if len(components) == 2: 55*9c5db199SXin Li default_opt[components[0].strip()] = components[1].strip() 56*9c5db199SXin Li elif current_section == '[fs_types]': 57*9c5db199SXin Li m = re.search('(\w+) = {', line) 58*9c5db199SXin Li if m: 59*9c5db199SXin Li current_fs_type = m.group(1) 60*9c5db199SXin Li else: 61*9c5db199SXin Li components = line.split('=', 1) 62*9c5db199SXin Li if len(components) == 2 and current_fs_type == fs_type: 63*9c5db199SXin Li default_opt[components[0].strip()] = components[1].strip() 64*9c5db199SXin Li f.close() 65*9c5db199SXin Li 66*9c5db199SXin Li # fs_types options override the defaults options 67*9c5db199SXin Li for key, value in six.iteritems(fs_opt): 68*9c5db199SXin Li default_opt[key] = value 69*9c5db199SXin Li 70*9c5db199SXin Li # Convert all the digit strings to int. 71*9c5db199SXin Li for key, value in six.iteritems(default_opt): 72*9c5db199SXin Li if value and value.isdigit(): 73*9c5db199SXin Li default_opt[key] = int(value) 74*9c5db199SXin Li 75*9c5db199SXin Li return default_opt 76*9c5db199SXin Li 77*9c5db199SXin Li 78*9c5db199SXin Lidef convert_conf_opt(default_opt): 79*9c5db199SXin Li conf_opt_mapping = {'blocksize': '-b', 80*9c5db199SXin Li 'inode_ratio': '-i', 81*9c5db199SXin Li 'inode_size': '-I'} 82*9c5db199SXin Li mkfs_opt = {} 83*9c5db199SXin Li 84*9c5db199SXin Li # Here we simply concatenate the feature string while we really need 85*9c5db199SXin Li # to do the better and/or operations. 86*9c5db199SXin Li if 'base_features' in default_opt: 87*9c5db199SXin Li mkfs_opt['-O'] = default_opt['base_features'] 88*9c5db199SXin Li if 'default_features' in default_opt: 89*9c5db199SXin Li mkfs_opt['-O'] += ',%s' % default_opt['default_features'] 90*9c5db199SXin Li if 'features' in default_opt: 91*9c5db199SXin Li mkfs_opt['-O'] += ',%s' % default_opt['features'] 92*9c5db199SXin Li 93*9c5db199SXin Li for key, value in six.iteritems(conf_opt_mapping): 94*9c5db199SXin Li if key in default_opt: 95*9c5db199SXin Li mkfs_opt[value] = default_opt[key] 96*9c5db199SXin Li 97*9c5db199SXin Li if '-O' in mkfs_opt: 98*9c5db199SXin Li mkfs_opt['-O'] = mkfs_opt['-O'].split(',') 99*9c5db199SXin Li 100*9c5db199SXin Li return mkfs_opt 101*9c5db199SXin Li 102*9c5db199SXin Li 103*9c5db199SXin Lidef merge_ext_features(conf_feature, user_feature): 104*9c5db199SXin Li user_feature_list = user_feature.split(',') 105*9c5db199SXin Li 106*9c5db199SXin Li merged_feature = [] 107*9c5db199SXin Li # Removes duplicate entries in conf_list. 108*9c5db199SXin Li for item in conf_feature: 109*9c5db199SXin Li if item not in merged_feature: 110*9c5db199SXin Li merged_feature.append(item) 111*9c5db199SXin Li 112*9c5db199SXin Li # User options override config options. 113*9c5db199SXin Li for item in user_feature_list: 114*9c5db199SXin Li if item[0] == '^': 115*9c5db199SXin Li if item[1:] in merged_feature: 116*9c5db199SXin Li merged_feature.remove(item[1:]) 117*9c5db199SXin Li else: 118*9c5db199SXin Li merged_feature.append(item) 119*9c5db199SXin Li elif item not in merged_feature: 120*9c5db199SXin Li merged_feature.append(item) 121*9c5db199SXin Li return merged_feature 122*9c5db199SXin Li 123*9c5db199SXin Li 124*9c5db199SXin Lidef ext_tunables(dev): 125*9c5db199SXin Li """Call tune2fs -l and parse the result.""" 126*9c5db199SXin Li cmd = 'tune2fs -l %s' % dev 127*9c5db199SXin Li try: 128*9c5db199SXin Li out = utils.system_output(cmd) 129*9c5db199SXin Li except error.CmdError: 130*9c5db199SXin Li tools_dir = os.path.join(os.environ['AUTODIR'], 'tools') 131*9c5db199SXin Li cmd = '%s/tune2fs.ext4dev -l %s' % (tools_dir, dev) 132*9c5db199SXin Li out = utils.system_output(cmd) 133*9c5db199SXin Li # Load option mappings 134*9c5db199SXin Li tune2fs_dict = {} 135*9c5db199SXin Li for line in out.splitlines(): 136*9c5db199SXin Li components = line.split(':', 1) 137*9c5db199SXin Li if len(components) == 2: 138*9c5db199SXin Li value = components[1].strip() 139*9c5db199SXin Li option = components[0] 140*9c5db199SXin Li if value.isdigit(): 141*9c5db199SXin Li tune2fs_dict[option] = int(value) 142*9c5db199SXin Li else: 143*9c5db199SXin Li tune2fs_dict[option] = value 144*9c5db199SXin Li 145*9c5db199SXin Li return tune2fs_dict 146*9c5db199SXin Li 147*9c5db199SXin Li 148*9c5db199SXin Lidef ext_mkfs_options(tune2fs_dict, mkfs_option): 149*9c5db199SXin Li """Map the tune2fs options to mkfs options.""" 150*9c5db199SXin Li 151*9c5db199SXin Li def __inode_count(tune_dict, k): 152*9c5db199SXin Li return (tune_dict['Block count']/tune_dict[k] + 1) * ( 153*9c5db199SXin Li tune_dict['Block size']) 154*9c5db199SXin Li 155*9c5db199SXin Li def __block_count(tune_dict, k): 156*9c5db199SXin Li return int(100*tune_dict[k]/tune_dict['Block count'] + 1) 157*9c5db199SXin Li 158*9c5db199SXin Li def __volume_name(tune_dict, k): 159*9c5db199SXin Li if tune_dict[k] != '<none>': 160*9c5db199SXin Li return tune_dict[k] 161*9c5db199SXin Li else: 162*9c5db199SXin Li return '' 163*9c5db199SXin Li 164*9c5db199SXin Li # mappings between fs features and mkfs options 165*9c5db199SXin Li ext_mapping = {'Blocks per group': '-g', 166*9c5db199SXin Li 'Block size': '-b', 167*9c5db199SXin Li 'Filesystem features': '-O', 168*9c5db199SXin Li 'Filesystem OS type': '-o', 169*9c5db199SXin Li 'Filesystem revision #': '-r', 170*9c5db199SXin Li 'Filesystem volume name': '-L', 171*9c5db199SXin Li 'Flex block group size': '-G', 172*9c5db199SXin Li 'Fragment size': '-f', 173*9c5db199SXin Li 'Inode count': '-i', 174*9c5db199SXin Li 'Inode size': '-I', 175*9c5db199SXin Li 'Journal inode': '-j', 176*9c5db199SXin Li 'Reserved block count': '-m'} 177*9c5db199SXin Li 178*9c5db199SXin Li conversions = { 179*9c5db199SXin Li 'Journal inode': lambda d, k: None, 180*9c5db199SXin Li 'Filesystem volume name': __volume_name, 181*9c5db199SXin Li 'Reserved block count': __block_count, 182*9c5db199SXin Li 'Inode count': __inode_count, 183*9c5db199SXin Li 'Filesystem features': lambda d, k: re.sub(' ', ',', d[k]), 184*9c5db199SXin Li 'Filesystem revision #': lambda d, k: d[k][0]} 185*9c5db199SXin Li 186*9c5db199SXin Li for key, value in six.iteritems(ext_mapping): 187*9c5db199SXin Li if key not in tune2fs_dict: 188*9c5db199SXin Li continue 189*9c5db199SXin Li if key in conversions: 190*9c5db199SXin Li mkfs_option[value] = conversions[key](tune2fs_dict, key) 191*9c5db199SXin Li else: 192*9c5db199SXin Li mkfs_option[value] = tune2fs_dict[key] 193*9c5db199SXin Li 194*9c5db199SXin Li 195*9c5db199SXin Lidef xfs_tunables(dev): 196*9c5db199SXin Li """Call xfs_grow -n to get filesystem tunables.""" 197*9c5db199SXin Li # Have to mount the filesystem to call xfs_grow. 198*9c5db199SXin Li tmp_mount_dir = tempfile.mkdtemp() 199*9c5db199SXin Li cmd = 'mount %s %s' % (dev, tmp_mount_dir) 200*9c5db199SXin Li utils.system_output(cmd) 201*9c5db199SXin Li xfs_growfs = os.path.join(os.environ['AUTODIR'], 'tools', 'xfs_growfs') 202*9c5db199SXin Li cmd = '%s -n %s' % (xfs_growfs, dev) 203*9c5db199SXin Li try: 204*9c5db199SXin Li out = utils.system_output(cmd) 205*9c5db199SXin Li finally: 206*9c5db199SXin Li # Clean. 207*9c5db199SXin Li cmd = 'umount %s' % dev 208*9c5db199SXin Li utils.system_output(cmd, ignore_status=True) 209*9c5db199SXin Li os.rmdir(tmp_mount_dir) 210*9c5db199SXin Li 211*9c5db199SXin Li ## The output format is given in report_info (xfs_growfs.c) 212*9c5db199SXin Li ## "meta-data=%-22s isize=%-6u agcount=%u, agsize=%u blks\n" 213*9c5db199SXin Li ## " =%-22s sectsz=%-5u attr=%u\n" 214*9c5db199SXin Li ## "data =%-22s bsize=%-6u blocks=%llu, imaxpct=%u\n" 215*9c5db199SXin Li ## " =%-22s sunit=%-6u swidth=%u blks\n" 216*9c5db199SXin Li ## "naming =version %-14u bsize=%-6u\n" 217*9c5db199SXin Li ## "log =%-22s bsize=%-6u blocks=%u, version=%u\n" 218*9c5db199SXin Li ## " =%-22s sectsz=%-5u sunit=%u blks, lazy-count=%u\n" 219*9c5db199SXin Li ## "realtime =%-22s extsz=%-6u blocks=%llu, rtextents=%llu\n" 220*9c5db199SXin Li 221*9c5db199SXin Li tune2fs_dict = {} 222*9c5db199SXin Li # Flag for extracting naming version number 223*9c5db199SXin Li keep_version = False 224*9c5db199SXin Li for line in out.splitlines(): 225*9c5db199SXin Li m = re.search('^([-\w]+)', line) 226*9c5db199SXin Li if m: 227*9c5db199SXin Li main_tag = m.group(1) 228*9c5db199SXin Li pairs = line.split() 229*9c5db199SXin Li for pair in pairs: 230*9c5db199SXin Li # naming: version needs special treatment 231*9c5db199SXin Li if pair == '=version': 232*9c5db199SXin Li # 1 means the next pair is the version number we want 233*9c5db199SXin Li keep_version = True 234*9c5db199SXin Li continue 235*9c5db199SXin Li if keep_version: 236*9c5db199SXin Li tune2fs_dict['naming: version'] = pair 237*9c5db199SXin Li # Resets the flag since we have logged the version 238*9c5db199SXin Li keep_version = False 239*9c5db199SXin Li continue 240*9c5db199SXin Li # Ignores the strings without '=', such as 'blks' 241*9c5db199SXin Li if '=' not in pair: 242*9c5db199SXin Li continue 243*9c5db199SXin Li key, value = pair.split('=') 244*9c5db199SXin Li tagged_key = '%s: %s' % (main_tag, key) 245*9c5db199SXin Li if re.match('[0-9]+', value): 246*9c5db199SXin Li tune2fs_dict[tagged_key] = int(value.rstrip(',')) 247*9c5db199SXin Li else: 248*9c5db199SXin Li tune2fs_dict[tagged_key] = value.rstrip(',') 249*9c5db199SXin Li 250*9c5db199SXin Li return tune2fs_dict 251*9c5db199SXin Li 252*9c5db199SXin Li 253*9c5db199SXin Lidef xfs_mkfs_options(tune2fs_dict, mkfs_option): 254*9c5db199SXin Li """Maps filesystem tunables to their corresponding mkfs options.""" 255*9c5db199SXin Li 256*9c5db199SXin Li # Mappings 257*9c5db199SXin Li xfs_mapping = {'meta-data: isize': '-i size', 258*9c5db199SXin Li 'meta-data: agcount': '-d agcount', 259*9c5db199SXin Li 'meta-data: sectsz': '-s size', 260*9c5db199SXin Li 'meta-data: attr': '-i attr', 261*9c5db199SXin Li 'data: bsize': '-b size', 262*9c5db199SXin Li 'data: imaxpct': '-i maxpct', 263*9c5db199SXin Li 'data: sunit': '-d sunit', 264*9c5db199SXin Li 'data: swidth': '-d swidth', 265*9c5db199SXin Li 'data: unwritten': '-d unwritten', 266*9c5db199SXin Li 'naming: version': '-n version', 267*9c5db199SXin Li 'naming: bsize': '-n size', 268*9c5db199SXin Li 'log: version': '-l version', 269*9c5db199SXin Li 'log: sectsz': '-l sectsize', 270*9c5db199SXin Li 'log: sunit': '-l sunit', 271*9c5db199SXin Li 'log: lazy-count': '-l lazy-count', 272*9c5db199SXin Li 'realtime: extsz': '-r extsize', 273*9c5db199SXin Li 'realtime: blocks': '-r size', 274*9c5db199SXin Li 'realtime: rtextents': '-r rtdev'} 275*9c5db199SXin Li 276*9c5db199SXin Li mkfs_option['-l size'] = tune2fs_dict['log: bsize'] * ( 277*9c5db199SXin Li tune2fs_dict['log: blocks']) 278*9c5db199SXin Li 279*9c5db199SXin Li for key, value in six.iteritems(xfs_mapping): 280*9c5db199SXin Li mkfs_option[value] = tune2fs_dict[key] 281*9c5db199SXin Li 282*9c5db199SXin Li 283*9c5db199SXin Lidef compare_features(needed_feature, current_feature): 284*9c5db199SXin Li """Compare two ext* feature lists.""" 285*9c5db199SXin Li if len(needed_feature) != len(current_feature): 286*9c5db199SXin Li return False 287*9c5db199SXin Li for feature in current_feature: 288*9c5db199SXin Li if feature not in needed_feature: 289*9c5db199SXin Li return False 290*9c5db199SXin Li return True 291*9c5db199SXin Li 292*9c5db199SXin Li 293*9c5db199SXin Lidef match_ext_options(fs_type, dev, needed_options): 294*9c5db199SXin Li """Compare the current ext* filesystem tunables with needed ones.""" 295*9c5db199SXin Li # mkfs.ext* will load default options from /etc/mke2fs.conf 296*9c5db199SXin Li conf_opt = parse_mke2fs_conf(fs_type) 297*9c5db199SXin Li # We need to convert the conf options to mkfs options. 298*9c5db199SXin Li conf_mkfs_opt = convert_conf_opt(conf_opt) 299*9c5db199SXin Li # Breaks user mkfs option string to dictionary. 300*9c5db199SXin Li needed_opt_dict = opt_string2dict(needed_options) 301*9c5db199SXin Li # Removes ignored options. 302*9c5db199SXin Li ignored_option = ['-c', '-q', '-E', '-F'] 303*9c5db199SXin Li for opt in ignored_option: 304*9c5db199SXin Li if opt in needed_opt_dict: 305*9c5db199SXin Li del needed_opt_dict[opt] 306*9c5db199SXin Li 307*9c5db199SXin Li # User options override config options. 308*9c5db199SXin Li needed_opt = conf_mkfs_opt 309*9c5db199SXin Li for key, value in six.iteritems(needed_opt_dict): 310*9c5db199SXin Li if key == '-N' or key == '-T': 311*9c5db199SXin Li raise Exception('-N/T is not allowed.') 312*9c5db199SXin Li elif key == '-O': 313*9c5db199SXin Li needed_opt[key] = merge_ext_features(needed_opt[key], value) 314*9c5db199SXin Li else: 315*9c5db199SXin Li needed_opt[key] = value 316*9c5db199SXin Li 317*9c5db199SXin Li # '-j' option will add 'has_journal' feature. 318*9c5db199SXin Li if '-j' in needed_opt and 'has_journal' not in needed_opt['-O']: 319*9c5db199SXin Li needed_opt['-O'].append('has_journal') 320*9c5db199SXin Li # 'extents' will be shown as 'extent' in the outcome of tune2fs 321*9c5db199SXin Li if 'extents' in needed_opt['-O']: 322*9c5db199SXin Li needed_opt['-O'].append('extent') 323*9c5db199SXin Li needed_opt['-O'].remove('extents') 324*9c5db199SXin Li # large_file is a byproduct of resize_inode. 325*9c5db199SXin Li if 'large_file' not in needed_opt['-O'] and ( 326*9c5db199SXin Li 'resize_inode' in needed_opt['-O']): 327*9c5db199SXin Li needed_opt['-O'].append('large_file') 328*9c5db199SXin Li 329*9c5db199SXin Li current_opt = {} 330*9c5db199SXin Li tune2fs_dict = ext_tunables(dev) 331*9c5db199SXin Li ext_mkfs_options(tune2fs_dict, current_opt) 332*9c5db199SXin Li 333*9c5db199SXin Li # Does the match 334*9c5db199SXin Li for key, value in six.iteritems(needed_opt): 335*9c5db199SXin Li if key == '-O': 336*9c5db199SXin Li if not compare_features(value, current_opt[key].split(',')): 337*9c5db199SXin Li return False 338*9c5db199SXin Li elif key not in current_opt or value != current_opt[key]: 339*9c5db199SXin Li return False 340*9c5db199SXin Li return True 341*9c5db199SXin Li 342*9c5db199SXin Li 343*9c5db199SXin Lidef match_xfs_options(dev, needed_options): 344*9c5db199SXin Li """Compare the current ext* filesystem tunables with needed ones.""" 345*9c5db199SXin Li tmp_mount_dir = tempfile.mkdtemp() 346*9c5db199SXin Li cmd = 'mount %s %s' % (dev, tmp_mount_dir) 347*9c5db199SXin Li utils.system_output(cmd) 348*9c5db199SXin Li xfs_growfs = os.path.join(os.environ['AUTODIR'], 'tools', 'xfs_growfs') 349*9c5db199SXin Li cmd = '%s -n %s' % (xfs_growfs, dev) 350*9c5db199SXin Li try: 351*9c5db199SXin Li current_option = utils.system_output(cmd) 352*9c5db199SXin Li finally: 353*9c5db199SXin Li # Clean. 354*9c5db199SXin Li cmd = 'umount %s' % dev 355*9c5db199SXin Li utils.system_output(cmd, ignore_status=True) 356*9c5db199SXin Li os.rmdir(tmp_mount_dir) 357*9c5db199SXin Li 358*9c5db199SXin Li # '-N' has the same effect as '-n' in mkfs.ext*. Man mkfs.xfs for details. 359*9c5db199SXin Li cmd = 'mkfs.xfs %s -N -f %s' % (needed_options, dev) 360*9c5db199SXin Li needed_out = utils.system_output(cmd) 361*9c5db199SXin Li # 'mkfs.xfs -N' produces slightly different result than 'xfs_growfs -n' 362*9c5db199SXin Li needed_out = re.sub('internal log', 'internal ', needed_out) 363*9c5db199SXin Li if current_option == needed_out: 364*9c5db199SXin Li return True 365*9c5db199SXin Li else: 366*9c5db199SXin Li return False 367*9c5db199SXin Li 368*9c5db199SXin Li 369*9c5db199SXin Lidef match_mkfs_option(fs_type, dev, needed_options): 370*9c5db199SXin Li """Compare the current filesystem tunables with needed ones.""" 371*9c5db199SXin Li if fs_type.startswith('ext'): 372*9c5db199SXin Li ret = match_ext_options(fs_type, dev, needed_options) 373*9c5db199SXin Li elif fs_type == 'xfs': 374*9c5db199SXin Li ret = match_xfs_options(dev, needed_options) 375*9c5db199SXin Li else: 376*9c5db199SXin Li ret = False 377*9c5db199SXin Li 378*9c5db199SXin Li return ret 379