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        'apic': ['timer', 'timer_expiry'],
46        'e1000': ['dev', 'parent_obj'],
47        'ehci': ['dev', 'pcidev'],
48        'I440FX': ['dev', 'parent_obj'],
49        'ich9_ahci': ['card', 'parent_obj'],
50        'ich9-ahci': ['ahci', 'ich9_ahci'],
51        'ioh3420': ['PCIDevice', 'PCIEDevice'],
52        'ioh-3240-express-root-port': ['port.br.dev',
53                                       'parent_obj.parent_obj.parent_obj',
54                                       'port.br.dev.exp.aer_log',
55                                'parent_obj.parent_obj.parent_obj.exp.aer_log'],
56        'lsiscsi': ['dev', 'parent_obj'],
57        'mch': ['d', 'parent_obj'],
58        'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
59        'pcnet': ['pci_dev', 'parent_obj'],
60        'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
61        'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
62                     'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
63                     'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
64                     'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
65                     'tmr.timer', 'ar.tmr.timer',
66                     'tmr.overflow_time', 'ar.tmr.overflow_time',
67                     'gpe', 'ar.gpe'],
68        'rtl8139': ['dev', 'parent_obj'],
69        'qxl': ['num_surfaces', 'ssd.num_surfaces'],
70        'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
71        'usb-host': ['dev', 'parent_obj'],
72        'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
73        'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
74        'vmware_vga': ['card', 'parent_obj'],
75        'vmware_vga_internal': ['depth', 'new_depth'],
76        'xhci': ['pci_dev', 'parent_obj'],
77        'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
78        'xio3130-express-downstream-port': ['port.br.dev',
79                                            'parent_obj.parent_obj.parent_obj',
80                                            'port.br.dev.exp.aer_log',
81                                'parent_obj.parent_obj.parent_obj.exp.aer_log'],
82        'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
83        'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
84                                          'br.dev.exp.aer_log',
85                                          'parent_obj.parent_obj.exp.aer_log'],
86    }
87
88    if not name in changed_names:
89        return False
90
91    if s_field in changed_names[name] and d_field in changed_names[name]:
92        return True
93
94    return False
95
96def get_changed_sec_name(sec):
97    # Section names can change -- see commit 292b1634 for an example.
98    changes = {
99        "ICH9 LPC": "ICH9-LPC",
100    }
101
102    for item in changes:
103        if item == sec:
104            return changes[item]
105        if changes[item] == sec:
106            return item
107    return ""
108
109def exists_in_substruct(fields, item):
110    # Some QEMU versions moved a few fields inside a substruct.  This
111    # kept the on-wire format the same.  This function checks if
112    # something got shifted inside a substruct.  For example, the
113    # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
114
115    if not "Description" in fields:
116        return False
117
118    if not "Fields" in fields["Description"]:
119        return False
120
121    substruct_fields = fields["Description"]["Fields"]
122
123    if substruct_fields == []:
124        return False
125
126    return check_fields_match(fields["Description"]["name"],
127                              substruct_fields[0]["field"], item)
128
129
130def check_fields(src_fields, dest_fields, desc, sec):
131    # This function checks for all the fields in a section.  If some
132    # fields got embedded into a substruct, this function will also
133    # attempt to check inside the substruct.
134
135    d_iter = iter(dest_fields)
136    s_iter = iter(src_fields)
137
138    # Using these lists as stacks to store previous value of s_iter
139    # and d_iter, so that when time comes to exit out of a substruct,
140    # we can go back one level up and continue from where we left off.
141
142    s_iter_list = []
143    d_iter_list = []
144
145    advance_src = True
146    advance_dest = True
147    unused_count = 0
148
149    while True:
150        if advance_src:
151            try:
152                s_item = s_iter.next()
153            except StopIteration:
154                if s_iter_list == []:
155                    break
156
157                s_iter = s_iter_list.pop()
158                continue
159        else:
160            if unused_count == 0:
161                # We want to avoid advancing just once -- when entering a
162                # dest substruct, or when exiting one.
163                advance_src = True
164
165        if advance_dest:
166            try:
167                d_item = d_iter.next()
168            except StopIteration:
169                if d_iter_list == []:
170                    # We were not in a substruct
171                    print "Section \"" + sec + "\",",
172                    print "Description " + "\"" + desc + "\":",
173                    print "expected field \"" + s_item["field"] + "\",",
174                    print "while dest has no further fields"
175                    bump_taint()
176                    break
177
178                d_iter = d_iter_list.pop()
179                advance_src = False
180                continue
181        else:
182            if unused_count == 0:
183                advance_dest = True
184
185        if unused_count > 0:
186            if advance_dest == False:
187                unused_count = unused_count - s_item["size"]
188                if unused_count == 0:
189                    advance_dest = True
190                    continue
191                if unused_count < 0:
192                    print "Section \"" + sec + "\",",
193                    print "Description \"" + desc + "\":",
194                    print "unused size mismatch near \"",
195                    print s_item["field"] + "\""
196                    bump_taint()
197                    break
198                continue
199
200            if advance_src == False:
201                unused_count = unused_count - d_item["size"]
202                if unused_count == 0:
203                    advance_src = True
204                    continue
205                if unused_count < 0:
206                    print "Section \"" + sec + "\",",
207                    print "Description \"" + desc + "\":",
208                    print "unused size mismatch near \"",
209                    print d_item["field"] + "\""
210                    bump_taint()
211                    break
212                continue
213
214        if not check_fields_match(desc, s_item["field"], d_item["field"]):
215            # Some fields were put in substructs, keeping the
216            # on-wire format the same, but breaking static tools
217            # like this one.
218
219            # First, check if dest has a new substruct.
220            if exists_in_substruct(d_item, s_item["field"]):
221                # listiterators don't have a prev() function, so we
222                # have to store our current location, descend into the
223                # substruct, and ensure we come out as if nothing
224                # happened when the substruct is over.
225                #
226                # Essentially we're opening the substructs that got
227                # added which didn't change the wire format.
228                d_iter_list.append(d_iter)
229                substruct_fields = d_item["Description"]["Fields"]
230                d_iter = iter(substruct_fields)
231                advance_src = False
232                continue
233
234            # Next, check if src has substruct that dest removed
235            # (can happen in backward migration: 2.0 -> 1.5)
236            if exists_in_substruct(s_item, d_item["field"]):
237                s_iter_list.append(s_iter)
238                substruct_fields = s_item["Description"]["Fields"]
239                s_iter = iter(substruct_fields)
240                advance_dest = False
241                continue
242
243            if s_item["field"] == "unused" or d_item["field"] == "unused":
244                if s_item["size"] == d_item["size"]:
245                    continue
246
247                if d_item["field"] == "unused":
248                    advance_dest = False
249                    unused_count = d_item["size"] - s_item["size"]
250                    continue
251
252                if s_item["field"] == "unused":
253                    advance_src = False
254                    unused_count = s_item["size"] - d_item["size"]
255                    continue
256
257            print "Section \"" + sec + "\",",
258            print "Description \"" + desc + "\":",
259            print "expected field \"" + s_item["field"] + "\",",
260            print "got \"" + d_item["field"] + "\"; skipping rest"
261            bump_taint()
262            break
263
264        check_version(s_item, d_item, sec, desc)
265
266        if not "Description" in s_item:
267            # Check size of this field only if it's not a VMSTRUCT entry
268            check_size(s_item, d_item, sec, desc, s_item["field"])
269
270        check_description_in_list(s_item, d_item, sec, desc)
271
272
273def check_subsections(src_sub, dest_sub, desc, sec):
274    for s_item in src_sub:
275        found = False
276        for d_item in dest_sub:
277            if s_item["name"] != d_item["name"]:
278                continue
279
280            found = True
281            check_descriptions(s_item, d_item, sec)
282
283        if not found:
284            print "Section \"" + sec + "\", Description \"" + desc + "\":",
285            print "Subsection \"" + s_item["name"] + "\" not found"
286            bump_taint()
287
288
289def check_description_in_list(s_item, d_item, sec, desc):
290    if not "Description" in s_item:
291        return
292
293    if not "Description" in d_item:
294        print "Section \"" + sec + "\", Description \"" + desc + "\",",
295        print "Field \"" + s_item["field"] + "\": missing description"
296        bump_taint()
297        return
298
299    check_descriptions(s_item["Description"], d_item["Description"], sec)
300
301
302def check_descriptions(src_desc, dest_desc, sec):
303    check_version(src_desc, dest_desc, sec, src_desc["name"])
304
305    if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
306        print "Section \"" + sec + "\":",
307        print "Description \"" + src_desc["name"] + "\"",
308        print "missing, got \"" + dest_desc["name"] + "\" instead; skipping"
309        bump_taint()
310        return
311
312    for f in src_desc:
313        if not f in dest_desc:
314            print "Section \"" + sec + "\"",
315            print "Description \"" + src_desc["name"] + "\":",
316            print "Entry \"" + f + "\" missing"
317            bump_taint()
318            continue
319
320        if f == 'Fields':
321            check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
322
323        if f == 'Subsections':
324            check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
325
326
327def check_version(s, d, sec, desc=None):
328    if s["version_id"] > d["version_id"]:
329        print "Section \"" + sec + "\"",
330        if desc:
331            print "Description \"" + desc + "\":",
332        print "version error:", s["version_id"], ">", d["version_id"]
333        bump_taint()
334
335    if not "minimum_version_id" in d:
336        return
337
338    if s["version_id"] < d["minimum_version_id"]:
339        print "Section \"" + sec + "\"",
340        if desc:
341            print "Description \"" + desc + "\":",
342            print "minimum version error:", s["version_id"], "<",
343            print d["minimum_version_id"]
344            bump_taint()
345
346
347def check_size(s, d, sec, desc=None, field=None):
348    if s["size"] != d["size"]:
349        print "Section \"" + sec + "\"",
350        if desc:
351            print "Description \"" + desc + "\"",
352        if field:
353            print "Field \"" + field + "\"",
354        print "size mismatch:", s["size"], ",", d["size"]
355        bump_taint()
356
357
358def check_machine_type(s, d):
359    if s["Name"] != d["Name"]:
360        print "Warning: checking incompatible machine types:",
361        print "\"" + s["Name"] + "\", \"" + d["Name"] + "\""
362    return
363
364
365def main():
366    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."
367
368    parser = argparse.ArgumentParser(description=help_text)
369    parser.add_argument('-s', '--src', type=file, required=True,
370                        help='json dump from src qemu')
371    parser.add_argument('-d', '--dest', type=file, required=True,
372                        help='json dump from dest qemu')
373    parser.add_argument('--reverse', required=False, default=False,
374                        action='store_true',
375                        help='reverse the direction')
376    args = parser.parse_args()
377
378    src_data = json.load(args.src)
379    dest_data = json.load(args.dest)
380    args.src.close()
381    args.dest.close()
382
383    if args.reverse:
384        temp = src_data
385        src_data = dest_data
386        dest_data = temp
387
388    for sec in src_data:
389        dest_sec = sec
390        if not dest_sec in dest_data:
391            # Either the section name got changed, or the section
392            # doesn't exist in dest.
393            dest_sec = get_changed_sec_name(sec)
394            if not dest_sec in dest_data:
395                print "Section \"" + sec + "\" does not exist in dest"
396                bump_taint()
397                continue
398
399        s = src_data[sec]
400        d = dest_data[dest_sec]
401
402        if sec == "vmschkmachine":
403            check_machine_type(s, d)
404            continue
405
406        check_version(s, d, sec)
407
408        for entry in s:
409            if not entry in d:
410                print "Section \"" + sec + "\": Entry \"" + entry + "\"",
411                print "missing"
412                bump_taint()
413                continue
414
415            if entry == "Description":
416                check_descriptions(s[entry], d[entry], sec)
417
418    return taint
419
420
421if __name__ == '__main__':
422    sys.exit(main())
423