| #!/usr/bin/env python3 | 
 | # | 
 | # Manipulations with qcow2 image | 
 | # | 
 | # Copyright (C) 2012 Red Hat, Inc. | 
 | # | 
 | # 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/>. | 
 | # | 
 |  | 
 | import sys | 
 |  | 
 | from qcow2_format import ( | 
 |     QcowHeader, | 
 |     QcowHeaderExtension | 
 | ) | 
 |  | 
 |  | 
 | is_json = False | 
 |  | 
 |  | 
 | def cmd_dump_header(fd): | 
 |     h = QcowHeader(fd) | 
 |     h.dump(is_json) | 
 |     print() | 
 |     h.dump_extensions(is_json) | 
 |  | 
 |  | 
 | def cmd_dump_header_exts(fd): | 
 |     h = QcowHeader(fd) | 
 |     h.dump_extensions(is_json) | 
 |  | 
 |  | 
 | def cmd_set_header(fd, name, value): | 
 |     try: | 
 |         value = int(value, 0) | 
 |     except ValueError: | 
 |         print("'%s' is not a valid number" % value) | 
 |         sys.exit(1) | 
 |  | 
 |     fields = (field[2] for field in QcowHeader.fields) | 
 |     if name not in fields: | 
 |         print("'%s' is not a known header field" % name) | 
 |         sys.exit(1) | 
 |  | 
 |     h = QcowHeader(fd) | 
 |     h.__dict__[name] = value | 
 |     h.update(fd) | 
 |  | 
 |  | 
 | def cmd_add_header_ext(fd, magic, data): | 
 |     try: | 
 |         magic = int(magic, 0) | 
 |     except ValueError: | 
 |         print("'%s' is not a valid magic number" % magic) | 
 |         sys.exit(1) | 
 |  | 
 |     h = QcowHeader(fd) | 
 |     h.extensions.append(QcowHeaderExtension.create(magic, | 
 |                                                    data.encode('ascii'))) | 
 |     h.update(fd) | 
 |  | 
 |  | 
 | def cmd_add_header_ext_stdio(fd, magic): | 
 |     data = sys.stdin.read() | 
 |     cmd_add_header_ext(fd, magic, data) | 
 |  | 
 |  | 
 | def cmd_del_header_ext(fd, magic): | 
 |     try: | 
 |         magic = int(magic, 0) | 
 |     except ValueError: | 
 |         print("'%s' is not a valid magic number" % magic) | 
 |         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: | 
 |         print("No such header extension") | 
 |         return | 
 |  | 
 |     h.update(fd) | 
 |  | 
 |  | 
 | def cmd_set_feature_bit(fd, group, bit): | 
 |     try: | 
 |         bit = int(bit, 0) | 
 |         if bit < 0 or bit >= 64: | 
 |             raise ValueError | 
 |     except ValueError: | 
 |         print("'%s' is not a valid bit number in range [0, 64)" % bit) | 
 |         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: | 
 |         print("'%s' is not a valid group, try " | 
 |               "'incompatible', 'compatible', or 'autoclear'" % group) | 
 |         sys.exit(1) | 
 |  | 
 |     h.update(fd) | 
 |  | 
 |  | 
 | cmds = [ | 
 |     ['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'], | 
 | ] | 
 |  | 
 |  | 
 | 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 | 
 |         print("Unknown command '%s'" % cmd) | 
 |     finally: | 
 |         fd.close() | 
 |  | 
 |  | 
 | def usage(): | 
 |     print("Usage: %s <file> <cmd> [<arg>, ...] [<key>, ...]" % sys.argv[0]) | 
 |     print("") | 
 |     print("Supported commands:") | 
 |     for name, handler, num_args, desc in cmds: | 
 |         print("    %-20s - %s" % (name, desc)) | 
 |     print("") | 
 |     print("Supported keys:") | 
 |     print("    %-20s - %s" % ('-j', 'Dump in JSON format')) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     if len(sys.argv) < 3: | 
 |         usage() | 
 |         sys.exit(1) | 
 |  | 
 |     is_json = '-j' in sys.argv | 
 |     if is_json: | 
 |         sys.argv.remove('-j') | 
 |  | 
 |     main(sys.argv[1], sys.argv[2], sys.argv[3:]) |