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