image.py 11 KB

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