xref: /aosp_15_r20/external/autotest/autotest_lib/client/bin/fsinfo.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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