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