buddy.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import bisect
  4. import struct
  5. import binascii
  6. try:
  7. {}.iterkeys
  8. iterkeys = lambda x: x.iterkeys()
  9. except AttributeError:
  10. iterkeys = lambda x: x.keys()
  11. try:
  12. unicode
  13. except NameError:
  14. unicode = str
  15. class BuddyError(Exception):
  16. pass
  17. class Block(object):
  18. def __init__(self, allocator, offset, size):
  19. self._allocator = allocator
  20. self._offset = offset
  21. self._size = size
  22. self._value = bytearray(allocator.read(offset, size))
  23. self._pos = 0
  24. self._dirty = False
  25. def __len__(self):
  26. return self._size
  27. def __enter__(self):
  28. return self
  29. def __exit__(self, exc_type, exc_value, traceback):
  30. self.close()
  31. def close(self):
  32. if self._dirty:
  33. self.flush()
  34. def flush(self):
  35. if self._dirty:
  36. self._dirty = False
  37. self._allocator.write(self._offset, self._value)
  38. def invalidate(self):
  39. self._dirty = False
  40. def zero_fill(self):
  41. len = self._size - self._pos
  42. zeroes = b'\0' * len
  43. self._value[self._pos:self._size] = zeroes
  44. self._dirty = True
  45. def tell(self):
  46. return self._pos
  47. def seek(self, pos, whence=os.SEEK_SET):
  48. if whence == os.SEEK_CUR:
  49. pos += self._pos
  50. elif whence == os.SEEK_END:
  51. pos = self._size - pos
  52. if pos < 0 or pos > self._size:
  53. raise ValueError('Seek out of range in Block instance')
  54. self._pos = pos
  55. def read(self, size_or_format):
  56. if isinstance(size_or_format, (str, unicode, bytes)):
  57. size = struct.calcsize(size_or_format)
  58. fmt = size_or_format
  59. else:
  60. size = size_or_format
  61. fmt = None
  62. if self._size - self._pos < size:
  63. raise BuddyError('Unable to read %lu bytes in block' % size)
  64. data = self._value[self._pos:self._pos + size]
  65. self._pos += size
  66. if fmt is not None:
  67. if isinstance(data, bytearray):
  68. return struct.unpack_from(fmt, bytes(data))
  69. else:
  70. return struct.unpack(fmt, data)
  71. else:
  72. return data
  73. def write(self, data_or_format, *args):
  74. if len(args):
  75. data = struct.pack(data_or_format, *args)
  76. else:
  77. data = data_or_format
  78. if self._pos + len(data) > self._size:
  79. raise ValueError('Attempt to write past end of Block')
  80. self._value[self._pos:self._pos + len(data)] = data
  81. self._pos += len(data)
  82. self._dirty = True
  83. def insert(self, data_or_format, *args):
  84. if len(args):
  85. data = struct.pack(data_or_format, *args)
  86. else:
  87. data = data_or_format
  88. del self._value[-len(data):]
  89. self._value[self._pos:self._pos] = data
  90. self._pos += len(data)
  91. self._dirty = True
  92. def delete(self, size):
  93. if self._pos + size > self._size:
  94. raise ValueError('Attempt to delete past end of Block')
  95. del self._value[self._pos:self._pos + size]
  96. self._value += b'\0' * size
  97. self._dirty = True
  98. def __str__(self):
  99. return binascii.b2a_hex(self._value)
  100. class Allocator(object):
  101. def __init__(self, the_file):
  102. self._file = the_file
  103. self._dirty = False
  104. self._file.seek(0)
  105. # Read the header
  106. magic1, magic2, offset, size, offset2, self._unknown1 \
  107. = self.read(-4, '>I4sIII16s')
  108. if magic2 != b'Bud1' or magic1 != 1:
  109. raise BuddyError('Not a buddy file')
  110. if offset != offset2:
  111. raise BuddyError('Root addresses differ')
  112. self._root = Block(self, offset, size)
  113. # Read the block offsets
  114. count, self._unknown2 = self._root.read('>II')
  115. self._offsets = []
  116. c = (count + 255) & ~255
  117. while c:
  118. self._offsets += self._root.read('>256I')
  119. c -= 256
  120. self._offsets = self._offsets[:count]
  121. # Read the TOC
  122. self._toc = {}
  123. count = self._root.read('>I')[0]
  124. for n in range(count):
  125. nlen = self._root.read('B')[0]
  126. name = bytes(self._root.read(nlen))
  127. value = self._root.read('>I')[0]
  128. self._toc[name] = value
  129. # Read the free lists
  130. self._free = []
  131. for n in range(32):
  132. count = self._root.read('>I')
  133. self._free.append(list(self._root.read('>%uI' % count)))
  134. @classmethod
  135. def open(cls, file_or_name, mode='r+'):
  136. if isinstance(file_or_name, (str, unicode)):
  137. if not 'b' in mode:
  138. mode = mode[:1] + 'b' + mode[1:]
  139. f = open(file_or_name, mode)
  140. else:
  141. f = file_or_name
  142. if 'w' in mode:
  143. # Create an empty file in this case
  144. f.truncate()
  145. # An empty root block needs 1264 bytes:
  146. #
  147. # 0 4 offset count
  148. # 4 4 unknown
  149. # 8 4 root block offset (2048)
  150. # 12 255 * 4 padding (offsets are in multiples of 256)
  151. # 1032 4 toc count (0)
  152. # 1036 228 free list
  153. # total 1264
  154. # The free list will contain the following:
  155. #
  156. # 0 5 * 4 no blocks of width less than 5
  157. # 20 6 * 8 1 block each of widths 5 to 10
  158. # 68 4 no blocks of width 11 (allocated for the root)
  159. # 72 19 * 8 1 block each of widths 12 to 30
  160. # 224 4 no blocks of width 31
  161. # total 228
  162. #
  163. # (The reason for this layout is that we allocate 2**5 bytes for
  164. # the header, which splits the initial 2GB region into every size
  165. # below 2**31, including *two* blocks of size 2**5, one of which
  166. # we take. The root block itself then needs a block of size
  167. # 2**11. Conveniently, each of these initial blocks will be
  168. # located at offset 2**n where n is its width.)
  169. # Write the header
  170. header = struct.pack(b'>I4sIII16s',
  171. 1, b'Bud1',
  172. 2048, 1264, 2048,
  173. b'\x00\x00\x10\x0c'
  174. b'\x00\x00\x00\x87'
  175. b'\x00\x00\x20\x0b'
  176. b'\x00\x00\x00\x00')
  177. f.write(header)
  178. f.write(b'\0' * 2016)
  179. # Write the root block
  180. free_list = [struct.pack(b'>5I', 0, 0, 0, 0, 0)]
  181. for n in range(5, 11):
  182. free_list.append(struct.pack(b'>II', 1, 2**n))
  183. free_list.append(struct.pack(b'>I', 0))
  184. for n in range(12, 31):
  185. free_list.append(struct.pack(b'>II', 1, 2**n))
  186. free_list.append(struct.pack(b'>I', 0))
  187. root = b''.join([struct.pack(b'>III', 1, 0, 2048 | 5),
  188. struct.pack(b'>I', 0) * 255,
  189. struct.pack(b'>I', 0)] + free_list)
  190. f.write(root)
  191. return Allocator(f)
  192. def __enter__(self):
  193. return self
  194. def __exit__(self, exc_type, exc_value, traceback):
  195. self.close()
  196. def close(self):
  197. self.flush()
  198. self._file.close()
  199. def flush(self):
  200. if self._dirty:
  201. size = self._root_block_size()
  202. self.allocate(size, 0)
  203. with self.get_block(0) as rblk:
  204. self._write_root_block_into(rblk)
  205. addr = self._offsets[0]
  206. offset = addr & ~0x1f
  207. size = 1 << (addr & 0x1f)
  208. self._file.seek(0, os.SEEK_SET)
  209. self._file.write(struct.pack(b'>I4sIII16s',
  210. 1, b'Bud1',
  211. offset, size, offset,
  212. self._unknown1))
  213. self._dirty = False
  214. self._file.flush()
  215. def read(self, offset, size_or_format):
  216. """Read data at `offset', or raise an exception. `size_or_format'
  217. may either be a byte count, in which case we return raw data,
  218. or a format string for `struct.unpack', in which case we
  219. work out the size and unpack the data before returning it."""
  220. # N.B. There is a fixed offset of four bytes(!)
  221. self._file.seek(offset + 4, os.SEEK_SET)
  222. if isinstance(size_or_format, (str, unicode)):
  223. size = struct.calcsize(size_or_format)
  224. fmt = size_or_format
  225. else:
  226. size = size_or_format
  227. fmt = None
  228. ret = self._file.read(size)
  229. if len(ret) < size:
  230. ret += b'\0' * (size - len(ret))
  231. if fmt is not None:
  232. if isinstance(ret, bytearray):
  233. ret = struct.unpack_from(fmt, bytes(ret))
  234. else:
  235. ret = struct.unpack(fmt, ret)
  236. return ret
  237. def write(self, offset, data_or_format, *args):
  238. """Write data at `offset', or raise an exception. `data_or_format'
  239. may either be the data to write, or a format string for `struct.pack',
  240. in which case we pack the additional arguments and write the
  241. resulting data."""
  242. # N.B. There is a fixed offset of four bytes(!)
  243. self._file.seek(offset + 4, os.SEEK_SET)
  244. if len(args):
  245. data = struct.pack(data_or_format, *args)
  246. else:
  247. data = data_or_format
  248. self._file.write(data)
  249. def get_block(self, block):
  250. try:
  251. addr = self._offsets[block]
  252. except IndexError:
  253. return None
  254. offset = addr & ~0x1f
  255. size = 1 << (addr & 0x1f)
  256. return Block(self, offset, size)
  257. def _root_block_size(self):
  258. """Return the number of bytes required by the root block."""
  259. # Offsets
  260. size = 8
  261. size += 4 * ((len(self._offsets) + 255) & ~255)
  262. # TOC
  263. size += 4
  264. size += sum([5 + len(s) for s in self._toc])
  265. # Free list
  266. size += sum([4 + 4 * len(fl) for fl in self._free])
  267. return size
  268. def _write_root_block_into(self, block):
  269. # Offsets
  270. block.write('>II', len(self._offsets), self._unknown2)
  271. block.write('>%uI' % len(self._offsets), *self._offsets)
  272. extra = len(self._offsets) & 255
  273. if extra:
  274. block.write(b'\0\0\0\0' * (256 - extra))
  275. # TOC
  276. keys = list(self._toc.keys())
  277. keys.sort()
  278. block.write('>I', len(keys))
  279. for k in keys:
  280. block.write('B', len(k))
  281. block.write(k)
  282. block.write('>I', self._toc[k])
  283. # Free list
  284. for w, f in enumerate(self._free):
  285. block.write('>I', len(f))
  286. if len(f):
  287. block.write('>%uI' % len(f), *f)
  288. def _buddy(self, offset, width):
  289. f = self._free[width]
  290. b = offset ^ (1 << width)
  291. try:
  292. ndx = f.index(b)
  293. except ValueError:
  294. ndx = None
  295. return (f, b, ndx)
  296. def _release(self, offset, width):
  297. # Coalesce
  298. while True:
  299. f,b,ndx = self._buddy(offset, width)
  300. if ndx is None:
  301. break
  302. offset &= b
  303. width += 1
  304. del f[ndx]
  305. # Add to the list
  306. bisect.insort(f, offset)
  307. # Mark as dirty
  308. self._dirty = True
  309. def _alloc(self, width):
  310. w = width
  311. while not self._free[w]:
  312. w += 1
  313. while w > width:
  314. offset = self._free[w].pop(0)
  315. w -= 1
  316. self._free[w] = [offset, offset ^ (1 << w)]
  317. self._dirty = True
  318. return self._free[width].pop(0)
  319. def allocate(self, bytes, block=None):
  320. """Allocate or reallocate a block such that it has space for at least
  321. `bytes' bytes."""
  322. if block is None:
  323. # Find the first unused block
  324. try:
  325. block = self._offsets.index(0)
  326. except ValueError:
  327. block = len(self._offsets)
  328. self._offsets.append(0)
  329. # Compute block width
  330. width = max(bytes.bit_length(), 5)
  331. addr = self._offsets[block]
  332. offset = addr & ~0x1f
  333. if addr:
  334. blkwidth = addr & 0x1f
  335. if blkwidth == width:
  336. return block
  337. self._release(offset, width)
  338. self._offsets[block] = 0
  339. offset = self._alloc(width)
  340. self._offsets[block] = offset | width
  341. return block
  342. def release(self, block):
  343. addr = self._offsets[block]
  344. if addr:
  345. width = addr & 0x1f
  346. offset = addr & ~0x1f
  347. self._release(offset, width)
  348. if block == len(self._offsets):
  349. del self._offsets[block]
  350. else:
  351. self._offsets[block] = 0
  352. def __len__(self):
  353. return len(self._toc)
  354. def __getitem__(self, key):
  355. if not isinstance(key, (str, unicode)):
  356. raise TypeError('Keys must be of string type')
  357. if not isinstance(key, bytes):
  358. key = key.encode('latin_1')
  359. return self._toc[key]
  360. def __setitem__(self, key, value):
  361. if not isinstance(key, (str, unicode)):
  362. raise TypeError('Keys must be of string type')
  363. if not isinstance(key, bytes):
  364. key = key.encode('latin_1')
  365. self._toc[key] = value
  366. self._dirty = True
  367. def __delitem__(self, key):
  368. if not isinstance(key, (str, unicode)):
  369. raise TypeError('Keys must be of string type')
  370. if not isinstance(key, bytes):
  371. key = key.encode('latin_1')
  372. del self._toc[key]
  373. self._dirty = True
  374. def iterkeys(self):
  375. return iterkeys(self._toc)
  376. def keys(self):
  377. return iterkeys(self._toc)
  378. def __iter__(self):
  379. return iterkeys(self._toc)
  380. def __contains__(self, key):
  381. return key in self._toc