1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5Libxl Migration v2 streams
6
7Record structures as per docs/specs/libxl-migration-stream.pandoc, and
8verification routines.
9"""
10
11import sys
12
13from struct import calcsize, unpack, unpack_from
14from xen.migration.verify import StreamError, RecordError, VerifyBase
15from xen.migration.libxc import VerifyLibxc
16
17# Header
18HDR_FORMAT = "!QII"
19
20HDR_IDENT = 0x4c6962786c466d74 # "LibxlFmt" in ASCII
21HDR_VERSION = 2
22
23HDR_OPT_BIT_ENDIAN = 0
24HDR_OPT_BIT_LEGACY = 1
25
26HDR_OPT_LE     = (0 << HDR_OPT_BIT_ENDIAN)
27HDR_OPT_BE     = (1 << HDR_OPT_BIT_ENDIAN)
28HDR_OPT_LEGACY = (1 << HDR_OPT_BIT_LEGACY)
29
30HDR_OPT_RESZ_MASK = 0xfffc
31
32# Records
33RH_FORMAT = "II"
34
35REC_TYPE_end                    = 0x00000000
36REC_TYPE_libxc_context          = 0x00000001
37REC_TYPE_emulator_xenstore_data = 0x00000002
38REC_TYPE_emulator_context       = 0x00000003
39REC_TYPE_checkpoint_end         = 0x00000004
40REC_TYPE_checkpoint_state       = 0x00000005
41
42rec_type_to_str = {
43    REC_TYPE_end                    : "End",
44    REC_TYPE_libxc_context          : "Libxc context",
45    REC_TYPE_emulator_xenstore_data : "Emulator xenstore data",
46    REC_TYPE_emulator_context       : "Emulator context",
47    REC_TYPE_checkpoint_end         : "Checkpoint end",
48    REC_TYPE_checkpoint_state       : "Checkpoint state",
49}
50
51# emulator_* header
52EMULATOR_HEADER_FORMAT = "II"
53
54EMULATOR_ID_unknown       = 0x00000000
55EMULATOR_ID_qemu_trad     = 0x00000001
56EMULATOR_ID_qemu_upstream = 0x00000002
57
58emulator_id_to_str = {
59    EMULATOR_ID_unknown       : "Unknown",
60    EMULATOR_ID_qemu_trad     : "Qemu Traditional",
61    EMULATOR_ID_qemu_upstream : "Qemu Upstream",
62}
63
64
65#
66# libxl format
67#
68
69LIBXL_QEMU_SIGNATURE = "DeviceModelRecord0002"
70LIBXL_QEMU_RECORD_HDR = "=%dsI" % (len(LIBXL_QEMU_SIGNATURE), )
71
72class VerifyLibxl(VerifyBase):
73    """ Verify a Libxl v2 stream """
74
75    def __init__(self, info, read):
76        VerifyBase.__init__(self, info, read)
77
78
79    def verify(self):
80        """ Verity a libxl stream """
81
82        self.verify_hdr()
83
84        while self.verify_record() != REC_TYPE_end:
85            pass
86
87
88    def verify_hdr(self):
89        """ Verify a Header """
90        ident, version, options = self.unpack_exact(HDR_FORMAT)
91
92        if ident != HDR_IDENT:
93            raise StreamError("Bad image id: Expected 0x%x, got 0x%x" %
94                              (HDR_IDENT, ident))
95
96        if version != HDR_VERSION:
97            raise StreamError("Unknown image version: Expected %d, got %d" %
98                              (HDR_VERSION, version))
99
100        if options & HDR_OPT_RESZ_MASK:
101            raise StreamError("Reserved bits set in image options field: 0x%x" %
102                              (options & HDR_OPT_RESZ_MASK))
103
104        if ( (sys.byteorder == "little") and
105             ((options & HDR_OPT_BIT_ENDIAN) != HDR_OPT_LE) ):
106            raise StreamError(
107                "Stream is not native endianess - unable to validate")
108
109        endian = ["little", "big"][options & HDR_OPT_LE]
110
111        if options & HDR_OPT_LEGACY:
112            self.info("Libxl Header: %s endian, legacy converted" % (endian, ))
113        else:
114            self.info("Libxl Header: %s endian" % (endian, ))
115
116
117    def verify_record(self):
118        """ Verify an individual record """
119        rtype, length = self.unpack_exact(RH_FORMAT)
120
121        if rtype not in rec_type_to_str:
122            raise StreamError("Unrecognised record type %x" % (rtype, ))
123
124        self.info("Libxl Record: %s, length %d" %
125                  (rec_type_to_str[rtype], length))
126
127        contentsz = (length + 7) & ~7
128        content = self.rdexact(contentsz)
129
130        padding = content[length:]
131        if padding != b"\x00" * len(padding):
132            raise StreamError("Padding containing non0 bytes found")
133
134        if rtype not in record_verifiers:
135            raise RuntimeError(
136                "No verification function for libxl record '%s'" %
137                rec_type_to_str[rtype])
138        else:
139            record_verifiers[rtype](self, content[:length])
140
141        return rtype
142
143
144    def verify_record_end(self, content):
145        """ End record """
146
147        if len(content) != 0:
148            raise RecordError("End record with non-zero length")
149
150
151    def verify_record_libxc_context(self, content):
152        """ Libxc context record """
153
154        if len(content) != 0:
155            raise RecordError("Libxc context record with non-zero length")
156
157        # Verify the libxc stream, as we can't seek forwards through it
158        VerifyLibxc(self.info, self.read).verify()
159
160
161    def verify_record_emulator_xenstore_data(self, content):
162        """ Emulator Xenstore Data record """
163        minsz = calcsize(EMULATOR_HEADER_FORMAT)
164
165        if len(content) < minsz:
166            raise RecordError("Length must be at least %d bytes, got %d" %
167                              (minsz, len(content)))
168
169        emu_id, emu_idx = unpack(EMULATOR_HEADER_FORMAT, content[:minsz])
170
171        if emu_id not in emulator_id_to_str:
172            raise RecordError("Unrecognised emulator id 0x%x" % (emu_id, ))
173
174        self.info("Emulator Xenstore Data (%s, idx %d)" %
175                  (emulator_id_to_str[emu_id], emu_idx))
176
177        # Chop off the emulator header
178        content = content[minsz:]
179
180        if len(content):
181
182            if content[-1] != '\x00':
183                raise RecordError("Data not NUL terminated")
184
185            # Split without the final NUL, to get an even number of parts
186            parts = content[:-1].split("\x00")
187
188            if (len(parts) % 2) != 0:
189                raise RecordError("Expected an even number of strings, got %d" %
190                                  (len(parts), ))
191
192            for key, val in zip(parts[0::2], parts[1::2]):
193                self.info("  '%s' = '%s'" % (key, val))
194
195
196    def verify_record_emulator_context(self, content):
197        """ Emulator Context record """
198        minsz = calcsize(EMULATOR_HEADER_FORMAT)
199
200        if len(content) < minsz:
201            raise RecordError("Length must be at least %d bytes, got %d" %
202                              (minsz, len(content)))
203
204        emu_id, emu_idx = unpack(EMULATOR_HEADER_FORMAT, content[:minsz])
205
206        if emu_id not in emulator_id_to_str:
207            raise RecordError("Unrecognised emulator id 0x%x" % (emu_id, ))
208
209        self.info("  Index %d, type %s" % (emu_idx, emulator_id_to_str[emu_id]))
210
211
212    def verify_record_checkpoint_end(self, content):
213        """ Checkpoint end record """
214
215        if len(content) != 0:
216            raise RecordError("Checkpoint end record with non-zero length")
217
218    def verify_record_checkpoint_state(self, content):
219        """ Checkpoint state """
220        if len(content) == 0:
221            raise RecordError("Checkpoint state record with zero length")
222
223
224record_verifiers = {
225    REC_TYPE_end:
226        VerifyLibxl.verify_record_end,
227    REC_TYPE_libxc_context:
228        VerifyLibxl.verify_record_libxc_context,
229    REC_TYPE_emulator_xenstore_data:
230        VerifyLibxl.verify_record_emulator_xenstore_data,
231    REC_TYPE_emulator_context:
232        VerifyLibxl.verify_record_emulator_context,
233    REC_TYPE_checkpoint_end:
234        VerifyLibxl.verify_record_checkpoint_end,
235    REC_TYPE_checkpoint_state:
236        VerifyLibxl.verify_record_checkpoint_state,
237}
238