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