qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Kevin Wolf <kwolf@redhat.com>
To: anthony@codemonkey.ws
Cc: kwolf@redhat.com, qemu-devel@nongnu.org
Subject: [Qemu-devel] [PATCH 03/12] qemu-iotests: add qed.py image manipulation utility
Date: Mon,  6 Aug 2012 22:44:42 +0200	[thread overview]
Message-ID: <1344285891-6578-4-git-send-email-kwolf@redhat.com> (raw)
In-Reply-To: <1344285891-6578-1-git-send-email-kwolf@redhat.com>

From: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>

The qed.py utility can inspect and manipulate QED image files.  It can
be used for testing to see the state of image metadata and also to
inject corruptions into the image file.  It also has a scrubbing feature
to copy just the metadata out of an image file, allowing users to share
broken image files without revealing data in bug reports.

This has lived in my local repo for a long time but could be useful
to others.

Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 tests/qemu-iotests/qed.py |  235 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 235 insertions(+), 0 deletions(-)
 create mode 100755 tests/qemu-iotests/qed.py

diff --git a/tests/qemu-iotests/qed.py b/tests/qemu-iotests/qed.py
new file mode 100755
index 0000000..52ff845
--- /dev/null
+++ b/tests/qemu-iotests/qed.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+#
+# Tool to manipulate QED image files
+#
+# Copyright (C) 2010 IBM, Corp.
+#
+# Authors:
+#  Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+
+import sys
+import struct
+import random
+import optparse
+
+# This can be used as a module
+__all__ = ['QED_F_NEED_CHECK', 'QED']
+
+QED_F_NEED_CHECK = 0x02
+
+header_fmt = '<IIIIQQQQQII'
+header_size = struct.calcsize(header_fmt)
+field_names = ['magic', 'cluster_size', 'table_size',
+               'header_size', 'features', 'compat_features',
+               'autoclear_features', 'l1_table_offset', 'image_size',
+               'backing_filename_offset', 'backing_filename_size']
+table_elem_fmt = '<Q'
+table_elem_size = struct.calcsize(table_elem_fmt)
+
+def err(msg):
+    sys.stderr.write(msg + '\n')
+    sys.exit(1)
+
+def unpack_header(s):
+    fields = struct.unpack(header_fmt, s)
+    return dict((field_names[idx], val) for idx, val in enumerate(fields))
+
+def pack_header(header):
+    fields = tuple(header[x] for x in field_names)
+    return struct.pack(header_fmt, *fields)
+
+def unpack_table_elem(s):
+    return struct.unpack(table_elem_fmt, s)[0]
+
+def pack_table_elem(elem):
+    return struct.pack(table_elem_fmt, elem)
+
+class QED(object):
+    def __init__(self, f):
+        self.f = f
+
+        self.f.seek(0, 2)
+        self.filesize = f.tell()
+
+        self.load_header()
+        self.load_l1_table()
+
+    def raw_pread(self, offset, size):
+        self.f.seek(offset)
+        return self.f.read(size)
+
+    def raw_pwrite(self, offset, data):
+        self.f.seek(offset)
+        return self.f.write(data)
+
+    def load_header(self):
+        self.header = unpack_header(self.raw_pread(0, header_size))
+
+    def store_header(self):
+        self.raw_pwrite(0, pack_header(self.header))
+
+    def read_table(self, offset):
+        size = self.header['table_size'] * self.header['cluster_size']
+        s = self.raw_pread(offset, size)
+        table = [unpack_table_elem(s[i:i + table_elem_size]) for i in xrange(0, size, table_elem_size)]
+        return table
+
+    def load_l1_table(self):
+        self.l1_table = self.read_table(self.header['l1_table_offset'])
+        self.table_nelems = self.header['table_size'] * self.header['cluster_size'] / table_elem_size
+
+    def write_table(self, offset, table):
+        s = ''.join(pack_table_elem(x) for x in table)
+        self.raw_pwrite(offset, s)
+
+def random_table_item(table):
+    vals = [(index, offset) for index, offset in enumerate(table) if offset != 0]
+    if not vals:
+        err('cannot pick random item because table is empty')
+    return random.choice(vals)
+
+def corrupt_table_duplicate(table):
+    '''Corrupt a table by introducing a duplicate offset'''
+    victim_idx, victim_val = random_table_item(table)
+    unique_vals = set(table)
+    if len(unique_vals) == 1:
+        err('no duplication corruption possible in table')
+    dup_val = random.choice(list(unique_vals.difference([victim_val])))
+    table[victim_idx] = dup_val
+
+def corrupt_table_invalidate(qed, table):
+    '''Corrupt a table by introducing an invalid offset'''
+    index, _ = random_table_item(table)
+    table[index] = qed.filesize + random.randint(0, 100 * 1024 * 1024 * 1024 * 1024)
+
+def cmd_show(qed, *args):
+    '''show [header|l1|l2 <offset>]- Show header or l1/l2 tables'''
+    if not args or args[0] == 'header':
+        print qed.header
+    elif args[0] == 'l1':
+        print qed.l1_table
+    elif len(args) == 2 and args[0] == 'l2':
+        offset = int(args[1])
+        print qed.read_table(offset)
+    else:
+        err('unrecognized sub-command')
+
+def cmd_duplicate(qed, table_level):
+    '''duplicate l1|l2 - Duplicate a random table element'''
+    if table_level == 'l1':
+        offset = qed.header['l1_table_offset']
+        table = qed.l1_table
+    elif table_level == 'l2':
+        _, offset = random_table_item(qed.l1_table)
+        table = qed.read_table(offset)
+    else:
+        err('unrecognized sub-command')
+    corrupt_table_duplicate(table)
+    qed.write_table(offset, table)
+
+def cmd_invalidate(qed, table_level):
+    '''invalidate l1|l2 - Plant an invalid table element at random'''
+    if table_level == 'l1':
+        offset = qed.header['l1_table_offset']
+        table = qed.l1_table
+    elif table_level == 'l2':
+        _, offset = random_table_item(qed.l1_table)
+        table = qed.read_table(offset)
+    else:
+        err('unrecognized sub-command')
+    corrupt_table_invalidate(qed, table)
+    qed.write_table(offset, table)
+
+def cmd_need_check(qed, *args):
+    '''need-check [on|off] - Test, set, or clear the QED_F_NEED_CHECK header bit'''
+    if not args:
+        print bool(qed.header['features'] & QED_F_NEED_CHECK)
+        return
+
+    if args[0] == 'on':
+        qed.header['features'] |= QED_F_NEED_CHECK
+    elif args[0] == 'off':
+        qed.header['features'] &= ~QED_F_NEED_CHECK
+    else:
+        err('unrecognized sub-command')
+    qed.store_header()
+
+def cmd_zero_cluster(qed, pos, *args):
+    '''zero-cluster <pos> [<n>] - Zero data clusters'''
+    pos, n = int(pos), 1
+    if args:
+        if len(args) != 1:
+            err('expected one argument')
+        n = int(args[0])
+
+    for i in xrange(n):
+        l1_index = pos / qed.header['cluster_size'] / len(qed.l1_table)
+        if qed.l1_table[l1_index] == 0:
+            err('no l2 table allocated')
+
+        l2_offset = qed.l1_table[l1_index]
+        l2_table = qed.read_table(l2_offset)
+
+        l2_index = (pos / qed.header['cluster_size']) % len(qed.l1_table)
+        l2_table[l2_index] = 1 # zero the data cluster
+        qed.write_table(l2_offset, l2_table)
+        pos += qed.header['cluster_size']
+
+def cmd_copy_metadata(qed, outfile):
+    '''copy-metadata <outfile> - Copy metadata only (for scrubbing corrupted images)'''
+    out = open(outfile, 'wb')
+
+    # Match file size
+    out.seek(qed.filesize - 1)
+    out.write('\0')
+
+    # Copy header clusters
+    out.seek(0)
+    header_size_bytes = qed.header['header_size'] * qed.header['cluster_size']
+    out.write(qed.raw_pread(0, header_size_bytes))
+
+    # Copy L1 table
+    out.seek(qed.header['l1_table_offset'])
+    s = ''.join(pack_table_elem(x) for x in qed.l1_table)
+    out.write(s)
+
+    # Copy L2 tables
+    for l2_offset in qed.l1_table:
+        if l2_offset == 0:
+            continue
+        l2_table = qed.read_table(l2_offset)
+        out.seek(l2_offset)
+        s = ''.join(pack_table_elem(x) for x in l2_table)
+        out.write(s)
+
+    out.close()
+
+def usage():
+    print 'Usage: %s <file> <cmd> [<arg>, ...]' % sys.argv[0]
+    print
+    print 'Supported commands:'
+    for cmd in sorted(x for x in globals() if x.startswith('cmd_')):
+        print globals()[cmd].__doc__
+    sys.exit(1)
+
+def main():
+    if len(sys.argv) < 3:
+        usage()
+    filename, cmd = sys.argv[1:3]
+
+    cmd = 'cmd_' + cmd.replace('-', '_')
+    if cmd not in globals():
+        usage()
+
+    qed = QED(open(filename, 'r+b'))
+    try:
+        globals()[cmd](qed, *sys.argv[3:])
+    except TypeError, e:
+        sys.stderr.write(globals()[cmd].__doc__ + '\n')
+        sys.exit(1)
+
+if __name__ == '__main__':
+    main()
-- 
1.7.6.5

  parent reply	other threads:[~2012-08-06 20:45 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-08-06 20:44 [Qemu-devel] [PULL 00/12] Block patches Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 01/12] ide scsi: Mess with geometry only for hard disk devices Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 02/12] qapi: generalize documentation of streaming commands Kevin Wolf
2012-08-06 20:44 ` Kevin Wolf [this message]
2012-08-06 20:44 ` [Qemu-devel] [PATCH 04/12] docs: add dirty bit to qcow2 specification Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 05/12] qcow2: introduce dirty bit Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 06/12] docs: add lazy refcounts bit to qcow2 specification Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 07/12] qemu-iotests: ignore qemu-img create lazy_refcounts output Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 08/12] qcow2: implement lazy refcounts Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 09/12] qemu-io: add "abort" command to simulate program crash Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 10/12] qemu-iotests: add 039 qcow2 lazy refcounts test Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 11/12] qemu-iotests: Be more flexible with image creation options Kevin Wolf
2012-08-06 21:54   ` Eric Blake
2012-08-06 21:57     ` Eric Blake
2012-08-09 11:13       ` Kevin Wolf
2012-08-06 20:44 ` [Qemu-devel] [PATCH 12/12] qemu-img: use QemuOpts instead of QEMUOptionParameter in resize function Kevin Wolf
2012-08-07  8:28 ` [Qemu-devel] [PULL 00/12] Block patches Paolo Bonzini
2012-08-07 15:36 ` Anthony Liguori

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1344285891-6578-4-git-send-email-kwolf@redhat.com \
    --to=kwolf@redhat.com \
    --cc=anthony@codemonkey.ws \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).