2020-01-30 17:32:23 +01:00
|
|
|
#!/usr/bin/env python3
|
2020-06-06 10:17:55 +02:00
|
|
|
#
|
|
|
|
# Manipulations with qcow2 image
|
|
|
|
#
|
2020-06-09 22:59:44 +02:00
|
|
|
# Copyright (C) 2012 Red Hat, Inc.
|
|
|
|
#
|
2020-06-06 10:17:55 +02:00
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
2012-02-15 16:36:03 +01:00
|
|
|
|
|
|
|
import sys
|
|
|
|
|
2020-06-06 10:17:56 +02:00
|
|
|
from qcow2_format import (
|
|
|
|
QcowHeader,
|
|
|
|
QcowHeaderExtension
|
|
|
|
)
|
2012-02-15 16:36:03 +01:00
|
|
|
|
|
|
|
|
2020-08-06 21:35:54 +02:00
|
|
|
is_json = False
|
|
|
|
|
|
|
|
|
2012-02-15 16:36:03 +01:00
|
|
|
def cmd_dump_header(fd):
|
|
|
|
h = QcowHeader(fd)
|
2020-08-06 21:35:54 +02:00
|
|
|
h.dump(is_json)
|
2020-06-06 10:17:57 +02:00
|
|
|
print()
|
2020-08-06 21:35:54 +02:00
|
|
|
h.dump_extensions(is_json)
|
2012-02-15 16:36:03 +01:00
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2019-11-07 17:36:48 +01:00
|
|
|
def cmd_dump_header_exts(fd):
|
|
|
|
h = QcowHeader(fd)
|
2020-08-06 21:35:54 +02:00
|
|
|
h.dump_extensions(is_json)
|
2019-11-07 17:36:48 +01:00
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2013-05-13 14:00:15 +02:00
|
|
|
def cmd_set_header(fd, name, value):
|
|
|
|
try:
|
|
|
|
value = int(value, 0)
|
2020-06-06 10:17:54 +02:00
|
|
|
except ValueError:
|
2018-06-08 14:29:43 +02:00
|
|
|
print("'%s' is not a valid number" % value)
|
2013-05-13 14:00:15 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
fields = (field[2] for field in QcowHeader.fields)
|
2020-06-06 10:17:54 +02:00
|
|
|
if name not in fields:
|
2018-06-08 14:29:43 +02:00
|
|
|
print("'%s' is not a known header field" % name)
|
2013-05-13 14:00:15 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
h = QcowHeader(fd)
|
|
|
|
h.__dict__[name] = value
|
|
|
|
h.update(fd)
|
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2012-02-15 16:36:03 +01:00
|
|
|
def cmd_add_header_ext(fd, magic, data):
|
|
|
|
try:
|
|
|
|
magic = int(magic, 0)
|
2020-06-06 10:17:54 +02:00
|
|
|
except ValueError:
|
2018-06-08 14:29:43 +02:00
|
|
|
print("'%s' is not a valid magic number" % magic)
|
2012-02-15 16:36:03 +01:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
h = QcowHeader(fd)
|
2020-06-06 10:17:54 +02:00
|
|
|
h.extensions.append(QcowHeaderExtension.create(magic,
|
|
|
|
data.encode('ascii')))
|
2012-02-15 16:36:03 +01:00
|
|
|
h.update(fd)
|
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2014-07-17 11:41:53 +02:00
|
|
|
def cmd_add_header_ext_stdio(fd, magic):
|
|
|
|
data = sys.stdin.read()
|
|
|
|
cmd_add_header_ext(fd, magic, data)
|
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2012-02-15 16:36:03 +01:00
|
|
|
def cmd_del_header_ext(fd, magic):
|
|
|
|
try:
|
|
|
|
magic = int(magic, 0)
|
2020-06-06 10:17:54 +02:00
|
|
|
except ValueError:
|
2018-06-08 14:29:43 +02:00
|
|
|
print("'%s' is not a valid magic number" % magic)
|
2012-02-15 16:36:03 +01:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
h = QcowHeader(fd)
|
|
|
|
found = False
|
|
|
|
|
|
|
|
for ex in h.extensions:
|
|
|
|
if ex.magic == magic:
|
|
|
|
found = True
|
|
|
|
h.extensions.remove(ex)
|
|
|
|
|
|
|
|
if not found:
|
2018-06-08 14:29:43 +02:00
|
|
|
print("No such header extension")
|
2012-02-15 16:36:03 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
h.update(fd)
|
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2012-06-14 14:58:17 +02:00
|
|
|
def cmd_set_feature_bit(fd, group, bit):
|
|
|
|
try:
|
|
|
|
bit = int(bit, 0)
|
|
|
|
if bit < 0 or bit >= 64:
|
|
|
|
raise ValueError
|
2020-06-06 10:17:54 +02:00
|
|
|
except ValueError:
|
2018-06-08 14:29:43 +02:00
|
|
|
print("'%s' is not a valid bit number in range [0, 64)" % bit)
|
2012-06-14 14:58:17 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
h = QcowHeader(fd)
|
|
|
|
if group == 'incompatible':
|
|
|
|
h.incompatible_features |= 1 << bit
|
|
|
|
elif group == 'compatible':
|
|
|
|
h.compatible_features |= 1 << bit
|
|
|
|
elif group == 'autoclear':
|
|
|
|
h.autoclear_features |= 1 << bit
|
|
|
|
else:
|
2020-06-06 10:17:54 +02:00
|
|
|
print("'%s' is not a valid group, try "
|
|
|
|
"'incompatible', 'compatible', or 'autoclear'" % group)
|
2012-06-14 14:58:17 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
h.update(fd)
|
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2012-02-15 16:36:03 +01:00
|
|
|
cmds = [
|
2020-06-06 10:17:54 +02:00
|
|
|
['dump-header', cmd_dump_header, 0,
|
|
|
|
'Dump image header and header extensions'],
|
|
|
|
['dump-header-exts', cmd_dump_header_exts, 0,
|
|
|
|
'Dump image header extensions'],
|
|
|
|
['set-header', cmd_set_header, 2, 'Set a field in the header'],
|
|
|
|
['add-header-ext', cmd_add_header_ext, 2, 'Add a header extension'],
|
|
|
|
['add-header-ext-stdio', cmd_add_header_ext_stdio, 1,
|
|
|
|
'Add a header extension, data from stdin'],
|
|
|
|
['del-header-ext', cmd_del_header_ext, 1, 'Delete a header extension'],
|
|
|
|
['set-feature-bit', cmd_set_feature_bit, 2, 'Set a feature bit'],
|
2012-02-15 16:36:03 +01:00
|
|
|
]
|
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2012-02-15 16:36:03 +01:00
|
|
|
def main(filename, cmd, args):
|
|
|
|
fd = open(filename, "r+b")
|
|
|
|
try:
|
|
|
|
for name, handler, num_args, desc in cmds:
|
|
|
|
if name != cmd:
|
|
|
|
continue
|
|
|
|
elif len(args) != num_args:
|
|
|
|
usage()
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
handler(fd, *args)
|
|
|
|
return
|
2018-06-08 14:29:43 +02:00
|
|
|
print("Unknown command '%s'" % cmd)
|
2012-02-15 16:36:03 +01:00
|
|
|
finally:
|
|
|
|
fd.close()
|
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2012-02-15 16:36:03 +01:00
|
|
|
def usage():
|
2020-08-06 21:35:54 +02:00
|
|
|
print("Usage: %s <file> <cmd> [<arg>, ...] [<key>, ...]" % sys.argv[0])
|
2018-06-08 14:29:43 +02:00
|
|
|
print("")
|
|
|
|
print("Supported commands:")
|
2012-02-15 16:36:03 +01:00
|
|
|
for name, handler, num_args, desc in cmds:
|
2018-06-08 14:29:43 +02:00
|
|
|
print(" %-20s - %s" % (name, desc))
|
2020-08-06 21:35:54 +02:00
|
|
|
print("")
|
|
|
|
print("Supported keys:")
|
|
|
|
print(" %-20s - %s" % ('-j', 'Dump in JSON format'))
|
2012-02-15 16:36:03 +01:00
|
|
|
|
2020-06-06 10:17:54 +02:00
|
|
|
|
2012-10-26 20:31:15 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
if len(sys.argv) < 3:
|
|
|
|
usage()
|
|
|
|
sys.exit(1)
|
2012-02-15 16:36:03 +01:00
|
|
|
|
2020-08-06 21:35:54 +02:00
|
|
|
is_json = '-j' in sys.argv
|
|
|
|
if is_json:
|
|
|
|
sys.argv.remove('-j')
|
|
|
|
|
2012-10-26 20:31:15 +02:00
|
|
|
main(sys.argv[1], sys.argv[2], sys.argv[3:])
|