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