image.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. # SPDX-License-Identifier: GPL-2.0+
  2. # Copyright (c) 2016 Google, Inc
  3. # Written by Simon Glass <sjg@chromium.org>
  4. #
  5. # Class for an image, the output of binman
  6. #
  7. from collections import OrderedDict
  8. import fnmatch
  9. from operator import attrgetter
  10. import os
  11. import re
  12. import sys
  13. from binman.entry import Entry
  14. from binman.etype import fdtmap
  15. from binman.etype import image_header
  16. from binman.etype import section
  17. from dtoc import fdt
  18. from dtoc import fdt_util
  19. from patman import tools
  20. from patman import tout
  21. class Image(section.Entry_section):
  22. """A Image, representing an output from binman
  23. An image is comprised of a collection of entries each containing binary
  24. data. The image size must be large enough to hold all of this data.
  25. This class implements the various operations needed for images.
  26. Attributes:
  27. filename: Output filename for image
  28. image_node: Name of node containing the description for this image
  29. fdtmap_dtb: Fdt object for the fdtmap when loading from a file
  30. fdtmap_data: Contents of the fdtmap when loading from a file
  31. allow_repack: True to add properties to allow the image to be safely
  32. repacked later
  33. Args:
  34. copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
  35. from the device tree
  36. test: True if this is being called from a test of Images. This this case
  37. there is no device tree defining the structure of the section, so
  38. we create a section manually.
  39. """
  40. def __init__(self, name, node, copy_to_orig=True, test=False):
  41. super().__init__(None, 'section', node, test=test)
  42. self.copy_to_orig = copy_to_orig
  43. self.name = 'main-section'
  44. self.image_name = name
  45. self._filename = '%s.bin' % self.image_name
  46. self.fdtmap_dtb = None
  47. self.fdtmap_data = None
  48. self.allow_repack = False
  49. if not test:
  50. self.ReadNode()
  51. def ReadNode(self):
  52. super().ReadNode()
  53. filename = fdt_util.GetString(self._node, 'filename')
  54. if filename:
  55. self._filename = filename
  56. self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
  57. @classmethod
  58. def FromFile(cls, fname):
  59. """Convert an image file into an Image for use in binman
  60. Args:
  61. fname: Filename of image file to read
  62. Returns:
  63. Image object on success
  64. Raises:
  65. ValueError if something goes wrong
  66. """
  67. data = tools.ReadFile(fname)
  68. size = len(data)
  69. # First look for an image header
  70. pos = image_header.LocateHeaderOffset(data)
  71. if pos is None:
  72. # Look for the FDT map
  73. pos = fdtmap.LocateFdtmap(data)
  74. if pos is None:
  75. raise ValueError('Cannot find FDT map in image')
  76. # We don't know the FDT size, so check its header first
  77. probe_dtb = fdt.Fdt.FromData(
  78. data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
  79. dtb_size = probe_dtb.GetFdtObj().totalsize()
  80. fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
  81. fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
  82. out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
  83. tools.WriteFile(out_fname, fdt_data)
  84. dtb = fdt.Fdt(out_fname)
  85. dtb.Scan()
  86. # Return an Image with the associated nodes
  87. root = dtb.GetRoot()
  88. image = Image('image', root, copy_to_orig=False)
  89. image.image_node = fdt_util.GetString(root, 'image-node', 'image')
  90. image.fdtmap_dtb = dtb
  91. image.fdtmap_data = fdtmap_data
  92. image._data = data
  93. image._filename = fname
  94. image.image_name, _ = os.path.splitext(fname)
  95. return image
  96. def Raise(self, msg):
  97. """Convenience function to raise an error referencing an image"""
  98. raise ValueError("Image '%s': %s" % (self._node.path, msg))
  99. def PackEntries(self):
  100. """Pack all entries into the image"""
  101. super().Pack(0)
  102. def SetImagePos(self):
  103. # This first section in the image so it starts at 0
  104. super().SetImagePos(0)
  105. def ProcessEntryContents(self):
  106. """Call the ProcessContents() method for each entry
  107. This is intended to adjust the contents as needed by the entry type.
  108. Returns:
  109. True if the new data size is OK, False if expansion is needed
  110. """
  111. sizes_ok = True
  112. for entry in self._entries.values():
  113. if not entry.ProcessContents():
  114. sizes_ok = False
  115. tout.Debug("Entry '%s' size change" % self._node.path)
  116. return sizes_ok
  117. def WriteSymbols(self):
  118. """Write symbol values into binary files for access at run time"""
  119. super().WriteSymbols(self)
  120. def BuildImage(self):
  121. """Write the image to a file"""
  122. fname = tools.GetOutputFilename(self._filename)
  123. tout.Info("Writing image to '%s'" % fname)
  124. with open(fname, 'wb') as fd:
  125. data = self.GetPaddedData()
  126. fd.write(data)
  127. tout.Info("Wrote %#x bytes" % len(data))
  128. def WriteMap(self):
  129. """Write a map of the image to a .map file
  130. Returns:
  131. Filename of map file written
  132. """
  133. filename = '%s.map' % self.image_name
  134. fname = tools.GetOutputFilename(filename)
  135. with open(fname, 'w') as fd:
  136. print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
  137. file=fd)
  138. super().WriteMap(fd, 0)
  139. return fname
  140. def BuildEntryList(self):
  141. """List the files in an image
  142. Returns:
  143. List of entry.EntryInfo objects describing all entries in the image
  144. """
  145. entries = []
  146. self.ListEntries(entries, 0)
  147. return entries
  148. def FindEntryPath(self, entry_path):
  149. """Find an entry at a given path in the image
  150. Args:
  151. entry_path: Path to entry (e.g. /ro-section/u-boot')
  152. Returns:
  153. Entry object corresponding to that past
  154. Raises:
  155. ValueError if no entry found
  156. """
  157. parts = entry_path.split('/')
  158. entries = self.GetEntries()
  159. parent = '/'
  160. for part in parts:
  161. entry = entries.get(part)
  162. if not entry:
  163. raise ValueError("Entry '%s' not found in '%s'" %
  164. (part, parent))
  165. parent = entry.GetPath()
  166. entries = entry.GetEntries()
  167. return entry
  168. def ReadData(self, decomp=True):
  169. tout.Debug("Image '%s' ReadData(), size=%#x" %
  170. (self.GetPath(), len(self._data)))
  171. return self._data
  172. def GetListEntries(self, entry_paths):
  173. """List the entries in an image
  174. This decodes the supplied image and returns a list of entries from that
  175. image, preceded by a header.
  176. Args:
  177. entry_paths: List of paths to match (each can have wildcards). Only
  178. entries whose names match one of these paths will be printed
  179. Returns:
  180. String error message if something went wrong, otherwise
  181. 3-Tuple:
  182. List of EntryInfo objects
  183. List of lines, each
  184. List of text columns, each a string
  185. List of widths of each column
  186. """
  187. def _EntryToStrings(entry):
  188. """Convert an entry to a list of strings, one for each column
  189. Args:
  190. entry: EntryInfo object containing information to output
  191. Returns:
  192. List of strings, one for each field in entry
  193. """
  194. def _AppendHex(val):
  195. """Append a hex value, or an empty string if val is None
  196. Args:
  197. val: Integer value, or None if none
  198. """
  199. args.append('' if val is None else '>%x' % val)
  200. args = [' ' * entry.indent + entry.name]
  201. _AppendHex(entry.image_pos)
  202. _AppendHex(entry.size)
  203. args.append(entry.etype)
  204. _AppendHex(entry.offset)
  205. _AppendHex(entry.uncomp_size)
  206. return args
  207. def _DoLine(lines, line):
  208. """Add a line to the output list
  209. This adds a line (a list of columns) to the output list. It also updates
  210. the widths[] array with the maximum width of each column
  211. Args:
  212. lines: List of lines to add to
  213. line: List of strings, one for each column
  214. """
  215. for i, item in enumerate(line):
  216. widths[i] = max(widths[i], len(item))
  217. lines.append(line)
  218. def _NameInPaths(fname, entry_paths):
  219. """Check if a filename is in a list of wildcarded paths
  220. Args:
  221. fname: Filename to check
  222. entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
  223. 'section/u-boot'])
  224. Returns:
  225. True if any wildcard matches the filename (using Unix filename
  226. pattern matching, not regular expressions)
  227. False if not
  228. """
  229. for path in entry_paths:
  230. if fnmatch.fnmatch(fname, path):
  231. return True
  232. return False
  233. entries = self.BuildEntryList()
  234. # This is our list of lines. Each item in the list is a list of strings, one
  235. # for each column
  236. lines = []
  237. HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
  238. 'Uncomp-size']
  239. num_columns = len(HEADER)
  240. # This records the width of each column, calculated as the maximum width of
  241. # all the strings in that column
  242. widths = [0] * num_columns
  243. _DoLine(lines, HEADER)
  244. # We won't print anything unless it has at least this indent. So at the
  245. # start we will print nothing, unless a path matches (or there are no
  246. # entry paths)
  247. MAX_INDENT = 100
  248. min_indent = MAX_INDENT
  249. path_stack = []
  250. path = ''
  251. indent = 0
  252. selected_entries = []
  253. for entry in entries:
  254. if entry.indent > indent:
  255. path_stack.append(path)
  256. elif entry.indent < indent:
  257. path_stack.pop()
  258. if path_stack:
  259. path = path_stack[-1] + '/' + entry.name
  260. indent = entry.indent
  261. # If there are entry paths to match and we are not looking at a
  262. # sub-entry of a previously matched entry, we need to check the path
  263. if entry_paths and indent <= min_indent:
  264. if _NameInPaths(path[1:], entry_paths):
  265. # Print this entry and all sub-entries (=higher indent)
  266. min_indent = indent
  267. else:
  268. # Don't print this entry, nor any following entries until we get
  269. # a path match
  270. min_indent = MAX_INDENT
  271. continue
  272. _DoLine(lines, _EntryToStrings(entry))
  273. selected_entries.append(entry)
  274. return selected_entries, lines, widths