xref: /qemu/tests/qemu-iotests/qcow2.py (revision a4bcfc33)
1#!/usr/bin/env python3
2#
3# Manipulations with qcow2 image
4#
5# Copyright (C) 2012 Red Hat, Inc.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20
21import sys
22
23from qcow2_format import (
24    QcowHeader,
25    QcowHeaderExtension
26)
27
28
29is_json = False
30
31
32def cmd_dump_header(fd):
33    h = QcowHeader(fd)
34    h.dump(is_json)
35    print()
36    h.dump_extensions(is_json)
37
38
39def cmd_dump_header_exts(fd):
40    h = QcowHeader(fd)
41    h.dump_extensions(is_json)
42
43
44def cmd_set_header(fd, name, value):
45    try:
46        value = int(value, 0)
47    except ValueError:
48        print("'%s' is not a valid number" % value)
49        sys.exit(1)
50
51    fields = (field[2] for field in QcowHeader.fields)
52    if name not in fields:
53        print("'%s' is not a known header field" % name)
54        sys.exit(1)
55
56    h = QcowHeader(fd)
57    h.__dict__[name] = value
58    h.update(fd)
59
60
61def cmd_add_header_ext(fd, magic, data):
62    try:
63        magic = int(magic, 0)
64    except ValueError:
65        print("'%s' is not a valid magic number" % magic)
66        sys.exit(1)
67
68    h = QcowHeader(fd)
69    h.extensions.append(QcowHeaderExtension.create(magic,
70                                                   data.encode('ascii')))
71    h.update(fd)
72
73
74def cmd_add_header_ext_stdio(fd, magic):
75    data = sys.stdin.read()
76    cmd_add_header_ext(fd, magic, data)
77
78
79def cmd_del_header_ext(fd, magic):
80    try:
81        magic = int(magic, 0)
82    except ValueError:
83        print("'%s' is not a valid magic number" % magic)
84        sys.exit(1)
85
86    h = QcowHeader(fd)
87    found = False
88
89    for ex in h.extensions:
90        if ex.magic == magic:
91            found = True
92            h.extensions.remove(ex)
93
94    if not found:
95        print("No such header extension")
96        return
97
98    h.update(fd)
99
100
101def cmd_set_feature_bit(fd, group, bit):
102    try:
103        bit = int(bit, 0)
104        if bit < 0 or bit >= 64:
105            raise ValueError
106    except ValueError:
107        print("'%s' is not a valid bit number in range [0, 64)" % bit)
108        sys.exit(1)
109
110    h = QcowHeader(fd)
111    if group == 'incompatible':
112        h.incompatible_features |= 1 << bit
113    elif group == 'compatible':
114        h.compatible_features |= 1 << bit
115    elif group == 'autoclear':
116        h.autoclear_features |= 1 << bit
117    else:
118        print("'%s' is not a valid group, try "
119              "'incompatible', 'compatible', or 'autoclear'" % group)
120        sys.exit(1)
121
122    h.update(fd)
123
124
125cmds = [
126    ['dump-header', cmd_dump_header, 0,
127     'Dump image header and header extensions'],
128    ['dump-header-exts', cmd_dump_header_exts, 0,
129     'Dump image header extensions'],
130    ['set-header', cmd_set_header, 2, 'Set a field in the header'],
131    ['add-header-ext', cmd_add_header_ext, 2, 'Add a header extension'],
132    ['add-header-ext-stdio', cmd_add_header_ext_stdio, 1,
133     'Add a header extension, data from stdin'],
134    ['del-header-ext', cmd_del_header_ext, 1, 'Delete a header extension'],
135    ['set-feature-bit', cmd_set_feature_bit, 2, 'Set a feature bit'],
136]
137
138
139def main(filename, cmd, args):
140    fd = open(filename, "r+b")
141    try:
142        for name, handler, num_args, desc in cmds:
143            if name != cmd:
144                continue
145            elif len(args) != num_args:
146                usage()
147                return
148            else:
149                handler(fd, *args)
150                return
151        print("Unknown command '%s'" % cmd)
152    finally:
153        fd.close()
154
155
156def usage():
157    print("Usage: %s <file> <cmd> [<arg>, ...] [<key>, ...]" % sys.argv[0])
158    print("")
159    print("Supported commands:")
160    for name, handler, num_args, desc in cmds:
161        print("    %-20s - %s" % (name, desc))
162    print("")
163    print("Supported keys:")
164    print("    %-20s - %s" % ('-j', 'Dump in JSON format'))
165
166
167if __name__ == '__main__':
168    if len(sys.argv) < 3:
169        usage()
170        sys.exit(1)
171
172    is_json = '-j' in sys.argv
173    if is_json:
174        sys.argv.remove('-j')
175
176    main(sys.argv[1], sys.argv[2], sys.argv[3:])
177