]> git.proxmox.com Git - mirror_qemu.git/blame - scripts/vmstate-static-checker.py
Update version for v2.1.0-rc2 release
[mirror_qemu.git] / scripts / vmstate-static-checker.py
CommitLineData
426d1d01
AS
1#!/usr/bin/python
2#
3# Compares vmstate information stored in JSON format, obtained from
4# the -dump-vmstate QEMU command.
5#
6# Copyright 2014 Amit Shah <amit.shah@redhat.com>
7# Copyright 2014 Red Hat, Inc.
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, see <http://www.gnu.org/licenses/>.
21
22import argparse
23import json
24import sys
25
26# Count the number of errors found
27taint = 0
28
29def bump_taint():
30 global taint
31
32 # Ensure we don't wrap around or reset to 0 -- the shell only has
33 # an 8-bit return value.
34 if taint < 255:
35 taint = taint + 1
36
37
38def check_fields_match(name, s_field, d_field):
39 if s_field == d_field:
40 return True
41
42 # Some fields changed names between qemu versions. This list
43 # is used to whitelist such changes in each section / description.
44 changed_names = {
45 'e1000': ['dev', 'parent_obj'],
46 'ehci': ['dev', 'pcidev'],
47 'I440FX': ['dev', 'parent_obj'],
48 'ich9_ahci': ['card', 'parent_obj'],
49 'ioh-3240-express-root-port': ['port.br.dev',
50 'parent_obj.parent_obj.parent_obj',
51 'port.br.dev.exp.aer_log',
52 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
53 'mch': ['d', 'parent_obj'],
54 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
55 'pcnet': ['pci_dev', 'parent_obj'],
56 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
57 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
58 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]'],
59 'rtl8139': ['dev', 'parent_obj'],
60 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
61 'usb-host': ['dev', 'parent_obj'],
62 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
63 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
64 'xhci': ['pci_dev', 'parent_obj'],
65 'xio3130-express-downstream-port': ['port.br.dev',
66 'parent_obj.parent_obj.parent_obj',
67 'port.br.dev.exp.aer_log',
68 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
69 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
70 'br.dev.exp.aer_log',
71 'parent_obj.parent_obj.exp.aer_log'],
72 }
73
74 if not name in changed_names:
75 return False
76
77 if s_field in changed_names[name] and d_field in changed_names[name]:
78 return True
79
80 return False
81
82
83def exists_in_substruct(fields, item):
84 # Some QEMU versions moved a few fields inside a substruct. This
85 # kept the on-wire format the same. This function checks if
86 # something got shifted inside a substruct. For example, the
87 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
88
89 if not "Description" in fields:
90 return False
91
92 if not "Fields" in fields["Description"]:
93 return False
94
95 substruct_fields = fields["Description"]["Fields"]
96
97 if substruct_fields == []:
98 return False
99
100 return check_fields_match(fields["Description"]["name"],
101 substruct_fields[0]["field"], item)
102
103
104def check_fields(src_fields, dest_fields, desc, sec):
105 # This function checks for all the fields in a section. If some
106 # fields got embedded into a substruct, this function will also
107 # attempt to check inside the substruct.
108
109 d_iter = iter(dest_fields)
110 s_iter = iter(src_fields)
111
112 # Using these lists as stacks to store previous value of s_iter
113 # and d_iter, so that when time comes to exit out of a substruct,
114 # we can go back one level up and continue from where we left off.
115
116 s_iter_list = []
117 d_iter_list = []
118
119 advance_src = True
120 advance_dest = True
121
122 while True:
123 if advance_src:
124 try:
125 s_item = s_iter.next()
126 except StopIteration:
127 if s_iter_list == []:
128 break
129
130 s_iter = s_iter_list.pop()
131 continue
132 else:
133 # We want to avoid advancing just once -- when entering a
134 # dest substruct, or when exiting one.
135 advance_src = True
136
137 if advance_dest:
138 try:
139 d_item = d_iter.next()
140 except StopIteration:
141 if d_iter_list == []:
142 # We were not in a substruct
143 print "Section \"" + sec + "\",",
144 print "Description " + "\"" + desc + "\":",
145 print "expected field \"" + s_item["field"] + "\",",
146 print "while dest has no further fields"
147 bump_taint()
148 break
149
150 d_iter = d_iter_list.pop()
151 advance_src = False
152 continue
153 else:
154 advance_dest = True
155
156 if not check_fields_match(desc, s_item["field"], d_item["field"]):
157 # Some fields were put in substructs, keeping the
158 # on-wire format the same, but breaking static tools
159 # like this one.
160
161 # First, check if dest has a new substruct.
162 if exists_in_substruct(d_item, s_item["field"]):
163 # listiterators don't have a prev() function, so we
164 # have to store our current location, descend into the
165 # substruct, and ensure we come out as if nothing
166 # happened when the substruct is over.
167 #
168 # Essentially we're opening the substructs that got
169 # added which didn't change the wire format.
170 d_iter_list.append(d_iter)
171 substruct_fields = d_item["Description"]["Fields"]
172 d_iter = iter(substruct_fields)
173 advance_src = False
174 continue
175
176 # Next, check if src has substruct that dest removed
177 # (can happen in backward migration: 2.0 -> 1.5)
178 if exists_in_substruct(s_item, d_item["field"]):
179 s_iter_list.append(s_iter)
180 substruct_fields = s_item["Description"]["Fields"]
181 s_iter = iter(substruct_fields)
182 advance_dest = False
183 continue
184
185 print "Section \"" + sec + "\",",
186 print "Description \"" + desc + "\":",
187 print "expected field \"" + s_item["field"] + "\",",
188 print "got \"" + d_item["field"] + "\"; skipping rest"
189 bump_taint()
190 break
191
192 check_version(s_item, d_item, sec, desc)
193
194 if not "Description" in s_item:
195 # Check size of this field only if it's not a VMSTRUCT entry
196 check_size(s_item, d_item, sec, desc, s_item["field"])
197
198 check_description_in_list(s_item, d_item, sec, desc)
199
200
201def check_subsections(src_sub, dest_sub, desc, sec):
202 for s_item in src_sub:
203 found = False
204 for d_item in dest_sub:
205 if s_item["name"] != d_item["name"]:
206 continue
207
208 found = True
209 check_descriptions(s_item, d_item, sec)
210
211 if not found:
212 print "Section \"" + sec + "\", Description \"" + desc + "\":",
213 print "Subsection \"" + s_item["name"] + "\" not found"
214 bump_taint()
215
216
217def check_description_in_list(s_item, d_item, sec, desc):
218 if not "Description" in s_item:
219 return
220
221 if not "Description" in d_item:
222 print "Section \"" + sec + "\", Description \"" + desc + "\",",
223 print "Field \"" + s_item["field"] + "\": missing description"
224 bump_taint()
225 return
226
227 check_descriptions(s_item["Description"], d_item["Description"], sec)
228
229
230def check_descriptions(src_desc, dest_desc, sec):
231 check_version(src_desc, dest_desc, sec, src_desc["name"])
232
233 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
234 print "Section \"" + sec + "\":",
235 print "Description \"" + src_desc["name"] + "\"",
236 print "missing, got \"" + dest_desc["name"] + "\" instead; skipping"
237 bump_taint()
238 return
239
240 for f in src_desc:
241 if not f in dest_desc:
242 print "Section \"" + sec + "\"",
243 print "Description \"" + src_desc["name"] + "\":",
244 print "Entry \"" + f + "\" missing"
245 bump_taint()
246 continue
247
248 if f == 'Fields':
249 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
250
251 if f == 'Subsections':
252 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
253
254
255def check_version(s, d, sec, desc=None):
256 if s["version_id"] > d["version_id"]:
257 print "Section \"" + sec + "\"",
258 if desc:
259 print "Description \"" + desc + "\":",
260 print "version error:", s["version_id"], ">", d["version_id"]
261 bump_taint()
262
263 if not "minimum_version_id" in d:
264 return
265
266 if s["version_id"] < d["minimum_version_id"]:
267 print "Section \"" + sec + "\"",
268 if desc:
269 print "Description \"" + desc + "\":",
270 print "minimum version error:", s["version_id"], "<",
271 print d["minimum_version_id"]
272 bump_taint()
273
274
275def check_size(s, d, sec, desc=None, field=None):
276 if s["size"] != d["size"]:
277 print "Section \"" + sec + "\"",
278 if desc:
279 print "Description \"" + desc + "\"",
280 if field:
281 print "Field \"" + field + "\"",
282 print "size mismatch:", s["size"], ",", d["size"]
283 bump_taint()
284
285
286def check_machine_type(s, d):
287 if s["Name"] != d["Name"]:
288 print "Warning: checking incompatible machine types:",
289 print "\"" + s["Name"] + "\", \"" + d["Name"] + "\""
290 return
291
292
293def main():
294 help_text = "Parse JSON-formatted vmstate dumps from QEMU in files SRC and DEST. Checks whether migration from SRC to DEST QEMU versions would break based on the VMSTATE information contained within the JSON outputs. The JSON output is created from a QEMU invocation with the -dump-vmstate parameter and a filename argument to it. Other parameters to QEMU do not matter, except the -M (machine type) parameter."
295
296 parser = argparse.ArgumentParser(description=help_text)
297 parser.add_argument('-s', '--src', type=file, required=True,
298 help='json dump from src qemu')
299 parser.add_argument('-d', '--dest', type=file, required=True,
300 help='json dump from dest qemu')
301 parser.add_argument('--reverse', required=False, default=False,
302 action='store_true',
303 help='reverse the direction')
304 args = parser.parse_args()
305
306 src_data = json.load(args.src)
307 dest_data = json.load(args.dest)
308 args.src.close()
309 args.dest.close()
310
311 if args.reverse:
312 temp = src_data
313 src_data = dest_data
314 dest_data = temp
315
316 for sec in src_data:
317 if not sec in dest_data:
318 print "Section \"" + sec + "\" does not exist in dest"
319 bump_taint()
320 continue
321
322 s = src_data[sec]
323 d = dest_data[sec]
324
325 if sec == "vmschkmachine":
326 check_machine_type(s, d)
327 continue
328
329 check_version(s, d, sec)
330
331 for entry in s:
332 if not entry in d:
333 print "Section \"" + sec + "\": Entry \"" + entry + "\"",
334 print "missing"
335 bump_taint()
336 continue
337
338 if entry == "Description":
339 check_descriptions(s[entry], d[entry], sec)
340
341 return taint
342
343
344if __name__ == '__main__':
345 sys.exit(main())