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