qed.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #!/usr/bin/env python3
  2. #
  3. # Tool to manipulate QED image files
  4. #
  5. # Copyright (C) 2010 IBM, Corp.
  6. #
  7. # Authors:
  8. # Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
  9. #
  10. # This work is licensed under the terms of the GNU GPL, version 2 or later.
  11. # See the COPYING file in the top-level directory.
  12. import sys
  13. import struct
  14. import random
  15. import optparse
  16. # This can be used as a module
  17. __all__ = ['QED_F_NEED_CHECK', 'QED']
  18. QED_F_NEED_CHECK = 0x02
  19. header_fmt = '<IIIIQQQQQII'
  20. header_size = struct.calcsize(header_fmt)
  21. field_names = ['magic', 'cluster_size', 'table_size',
  22. 'header_size', 'features', 'compat_features',
  23. 'autoclear_features', 'l1_table_offset', 'image_size',
  24. 'backing_filename_offset', 'backing_filename_size']
  25. table_elem_fmt = '<Q'
  26. table_elem_size = struct.calcsize(table_elem_fmt)
  27. def err(msg):
  28. sys.stderr.write(msg + '\n')
  29. sys.exit(1)
  30. def unpack_header(s):
  31. fields = struct.unpack(header_fmt, s)
  32. return dict((field_names[idx], val) for idx, val in enumerate(fields))
  33. def pack_header(header):
  34. fields = tuple(header[x] for x in field_names)
  35. return struct.pack(header_fmt, *fields)
  36. def unpack_table_elem(s):
  37. return struct.unpack(table_elem_fmt, s)[0]
  38. def pack_table_elem(elem):
  39. return struct.pack(table_elem_fmt, elem)
  40. class QED(object):
  41. def __init__(self, f):
  42. self.f = f
  43. self.f.seek(0, 2)
  44. self.filesize = f.tell()
  45. self.load_header()
  46. self.load_l1_table()
  47. def raw_pread(self, offset, size):
  48. self.f.seek(offset)
  49. return self.f.read(size)
  50. def raw_pwrite(self, offset, data):
  51. self.f.seek(offset)
  52. return self.f.write(data)
  53. def load_header(self):
  54. self.header = unpack_header(self.raw_pread(0, header_size))
  55. def store_header(self):
  56. self.raw_pwrite(0, pack_header(self.header))
  57. def read_table(self, offset):
  58. size = self.header['table_size'] * self.header['cluster_size']
  59. s = self.raw_pread(offset, size)
  60. table = [unpack_table_elem(s[i:i + table_elem_size]) for i in xrange(0, size, table_elem_size)]
  61. return table
  62. def load_l1_table(self):
  63. self.l1_table = self.read_table(self.header['l1_table_offset'])
  64. self.table_nelems = self.header['table_size'] * self.header['cluster_size'] // table_elem_size
  65. def write_table(self, offset, table):
  66. s = ''.join(pack_table_elem(x) for x in table)
  67. self.raw_pwrite(offset, s)
  68. def random_table_item(table):
  69. vals = [(index, offset) for index, offset in enumerate(table) if offset != 0]
  70. if not vals:
  71. err('cannot pick random item because table is empty')
  72. return random.choice(vals)
  73. def corrupt_table_duplicate(table):
  74. '''Corrupt a table by introducing a duplicate offset'''
  75. victim_idx, victim_val = random_table_item(table)
  76. unique_vals = set(table)
  77. if len(unique_vals) == 1:
  78. err('no duplication corruption possible in table')
  79. dup_val = random.choice(list(unique_vals.difference([victim_val])))
  80. table[victim_idx] = dup_val
  81. def corrupt_table_invalidate(qed, table):
  82. '''Corrupt a table by introducing an invalid offset'''
  83. index, _ = random_table_item(table)
  84. table[index] = qed.filesize + random.randint(0, 100 * 1024 * 1024 * 1024 * 1024)
  85. def cmd_show(qed, *args):
  86. '''show [header|l1|l2 <offset>]- Show header or l1/l2 tables'''
  87. if not args or args[0] == 'header':
  88. print(qed.header)
  89. elif args[0] == 'l1':
  90. print(qed.l1_table)
  91. elif len(args) == 2 and args[0] == 'l2':
  92. offset = int(args[1])
  93. print(qed.read_table(offset))
  94. else:
  95. err('unrecognized sub-command')
  96. def cmd_duplicate(qed, table_level):
  97. '''duplicate l1|l2 - Duplicate a random table element'''
  98. if table_level == 'l1':
  99. offset = qed.header['l1_table_offset']
  100. table = qed.l1_table
  101. elif table_level == 'l2':
  102. _, offset = random_table_item(qed.l1_table)
  103. table = qed.read_table(offset)
  104. else:
  105. err('unrecognized sub-command')
  106. corrupt_table_duplicate(table)
  107. qed.write_table(offset, table)
  108. def cmd_invalidate(qed, table_level):
  109. '''invalidate l1|l2 - Plant an invalid table element at random'''
  110. if table_level == 'l1':
  111. offset = qed.header['l1_table_offset']
  112. table = qed.l1_table
  113. elif table_level == 'l2':
  114. _, offset = random_table_item(qed.l1_table)
  115. table = qed.read_table(offset)
  116. else:
  117. err('unrecognized sub-command')
  118. corrupt_table_invalidate(qed, table)
  119. qed.write_table(offset, table)
  120. def cmd_need_check(qed, *args):
  121. '''need-check [on|off] - Test, set, or clear the QED_F_NEED_CHECK header bit'''
  122. if not args:
  123. print(bool(qed.header['features'] & QED_F_NEED_CHECK))
  124. return
  125. if args[0] == 'on':
  126. qed.header['features'] |= QED_F_NEED_CHECK
  127. elif args[0] == 'off':
  128. qed.header['features'] &= ~QED_F_NEED_CHECK
  129. else:
  130. err('unrecognized sub-command')
  131. qed.store_header()
  132. def cmd_zero_cluster(qed, pos, *args):
  133. '''zero-cluster <pos> [<n>] - Zero data clusters'''
  134. pos, n = int(pos), 1
  135. if args:
  136. if len(args) != 1:
  137. err('expected one argument')
  138. n = int(args[0])
  139. for i in xrange(n):
  140. l1_index = pos // qed.header['cluster_size'] // len(qed.l1_table)
  141. if qed.l1_table[l1_index] == 0:
  142. err('no l2 table allocated')
  143. l2_offset = qed.l1_table[l1_index]
  144. l2_table = qed.read_table(l2_offset)
  145. l2_index = (pos // qed.header['cluster_size']) % len(qed.l1_table)
  146. l2_table[l2_index] = 1 # zero the data cluster
  147. qed.write_table(l2_offset, l2_table)
  148. pos += qed.header['cluster_size']
  149. def cmd_copy_metadata(qed, outfile):
  150. '''copy-metadata <outfile> - Copy metadata only (for scrubbing corrupted images)'''
  151. out = open(outfile, 'wb')
  152. # Match file size
  153. out.seek(qed.filesize - 1)
  154. out.write('\0')
  155. # Copy header clusters
  156. out.seek(0)
  157. header_size_bytes = qed.header['header_size'] * qed.header['cluster_size']
  158. out.write(qed.raw_pread(0, header_size_bytes))
  159. # Copy L1 table
  160. out.seek(qed.header['l1_table_offset'])
  161. s = ''.join(pack_table_elem(x) for x in qed.l1_table)
  162. out.write(s)
  163. # Copy L2 tables
  164. for l2_offset in qed.l1_table:
  165. if l2_offset == 0:
  166. continue
  167. l2_table = qed.read_table(l2_offset)
  168. out.seek(l2_offset)
  169. s = ''.join(pack_table_elem(x) for x in l2_table)
  170. out.write(s)
  171. out.close()
  172. def usage():
  173. print('Usage: %s <file> <cmd> [<arg>, ...]' % sys.argv[0])
  174. print()
  175. print('Supported commands:')
  176. for cmd in sorted(x for x in globals() if x.startswith('cmd_')):
  177. print(globals()[cmd].__doc__)
  178. sys.exit(1)
  179. def main():
  180. if len(sys.argv) < 3:
  181. usage()
  182. filename, cmd = sys.argv[1:3]
  183. cmd = 'cmd_' + cmd.replace('-', '_')
  184. if cmd not in globals():
  185. usage()
  186. qed = QED(open(filename, 'r+b'))
  187. try:
  188. globals()[cmd](qed, *sys.argv[3:])
  189. except TypeError as e:
  190. sys.stderr.write(globals()[cmd].__doc__ + '\n')
  191. sys.exit(1)
  192. if __name__ == '__main__':
  193. main()