blob: 877e37cd8da1b25d9899e753012e07e2737fb6b8 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassbe39f052016-07-25 18:59:08 -06002#
3# Copyright (c) 2016 Google, Inc
4#
Simon Glassbe39f052016-07-25 18:59:08 -06005
Simon Glassac6328c2018-09-14 04:57:28 -06006import glob
Simon Glassbe39f052016-07-25 18:59:08 -06007import os
8import shutil
Simon Glass37fdd142019-07-20 12:24:06 -06009import struct
Simon Glassac0d4952019-05-14 15:53:47 -060010import sys
Simon Glassbe39f052016-07-25 18:59:08 -060011import tempfile
12
Simon Glassa997ea52020-04-17 18:09:04 -060013from patman import command
14from patman import tout
Simon Glassbe39f052016-07-25 18:59:08 -060015
Simon Glass0362cd22018-07-17 13:25:43 -060016# Output directly (generally this is temporary)
Simon Glassbe39f052016-07-25 18:59:08 -060017outdir = None
Simon Glass0362cd22018-07-17 13:25:43 -060018
19# True to keep the output directory around after exiting
Simon Glassbe39f052016-07-25 18:59:08 -060020preserve_outdir = False
21
Simon Glass0362cd22018-07-17 13:25:43 -060022# Path to the Chrome OS chroot, if we know it
23chroot_path = None
24
25# Search paths to use for Filename(), used to find files
26search_paths = []
27
Simon Glass90c3c4c2019-07-08 13:18:27 -060028tool_search_paths = []
29
Simon Glass778ab842018-09-14 04:57:25 -060030# Tools and the packages that contain them, on debian
31packages = {
32 'lz4': 'liblz4-tool',
33 }
Simon Glass0362cd22018-07-17 13:25:43 -060034
Simon Glass867b9062018-10-01 21:12:44 -060035# List of paths to use when looking for an input file
36indir = []
37
Simon Glassbe39f052016-07-25 18:59:08 -060038def PrepareOutputDir(dirname, preserve=False):
39 """Select an output directory, ensuring it exists.
40
41 This either creates a temporary directory or checks that the one supplied
42 by the user is valid. For a temporary directory, it makes a note to
43 remove it later if required.
44
45 Args:
46 dirname: a string, name of the output directory to use to store
47 intermediate and output files. If is None - create a temporary
48 directory.
49 preserve: a Boolean. If outdir above is None and preserve is False, the
50 created temporary directory will be destroyed on exit.
51
52 Raises:
53 OSError: If it cannot create the output directory.
54 """
55 global outdir, preserve_outdir
56
57 preserve_outdir = dirname or preserve
58 if dirname:
59 outdir = dirname
60 if not os.path.isdir(outdir):
61 try:
62 os.makedirs(outdir)
63 except OSError as err:
64 raise CmdError("Cannot make output directory '%s': '%s'" %
65 (outdir, err.strerror))
66 tout.Debug("Using output directory '%s'" % outdir)
67 else:
68 outdir = tempfile.mkdtemp(prefix='binman.')
69 tout.Debug("Using temporary directory '%s'" % outdir)
70
71def _RemoveOutputDir():
72 global outdir
73
74 shutil.rmtree(outdir)
75 tout.Debug("Deleted temporary directory '%s'" % outdir)
76 outdir = None
77
78def FinaliseOutputDir():
79 global outdir, preserve_outdir
80
81 """Tidy up: delete output directory if temporary and not preserved."""
82 if outdir and not preserve_outdir:
83 _RemoveOutputDir()
Simon Glass7f47e082019-07-20 12:24:07 -060084 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -060085
86def GetOutputFilename(fname):
87 """Return a filename within the output directory.
88
89 Args:
90 fname: Filename to use for new file
91
92 Returns:
93 The full path of the filename, within the output directory
94 """
95 return os.path.join(outdir, fname)
96
Simon Glass4e8e8462020-12-28 20:34:52 -070097def GetOutputDir():
98 """Return the current output directory
99
100 Returns:
101 str: The output directory
102 """
103 return outdir
104
Simon Glassbe39f052016-07-25 18:59:08 -0600105def _FinaliseForTest():
106 """Remove the output directory (for use by tests)"""
107 global outdir
108
109 if outdir:
110 _RemoveOutputDir()
Simon Glass7f47e082019-07-20 12:24:07 -0600111 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -0600112
113def SetInputDirs(dirname):
114 """Add a list of input directories, where input files are kept.
115
116 Args:
117 dirname: a list of paths to input directories to use for obtaining
118 files needed by binman to place in the image.
119 """
120 global indir
121
122 indir = dirname
123 tout.Debug("Using input directories %s" % indir)
124
Simon Glass5d94cc62020-07-09 18:39:38 -0600125def GetInputFilename(fname, allow_missing=False):
Simon Glassbe39f052016-07-25 18:59:08 -0600126 """Return a filename for use as input.
127
128 Args:
129 fname: Filename to use for new file
Simon Glass5d94cc62020-07-09 18:39:38 -0600130 allow_missing: True if the filename can be missing
Simon Glassbe39f052016-07-25 18:59:08 -0600131
132 Returns:
Simon Glasseb751fd2021-03-18 20:25:03 +1300133 fname, if indir is None;
134 full path of the filename, within the input directory;
135 None, if file is missing and allow_missing is True
136
137 Raises:
138 ValueError if file is missing and allow_missing is False
Simon Glassbe39f052016-07-25 18:59:08 -0600139 """
Simon Glass4affd4b2019-08-24 07:22:54 -0600140 if not indir or fname[:1] == '/':
Simon Glassbe39f052016-07-25 18:59:08 -0600141 return fname
142 for dirname in indir:
143 pathname = os.path.join(dirname, fname)
144 if os.path.exists(pathname):
145 return pathname
146
Simon Glass5d94cc62020-07-09 18:39:38 -0600147 if allow_missing:
148 return None
Simon Glassf2eb0542018-07-17 13:25:45 -0600149 raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
150 (fname, ','.join(indir), os.getcwd()))
Simon Glassbe39f052016-07-25 18:59:08 -0600151
Simon Glassac6328c2018-09-14 04:57:28 -0600152def GetInputFilenameGlob(pattern):
153 """Return a list of filenames for use as input.
154
155 Args:
156 pattern: Filename pattern to search for
157
158 Returns:
159 A list of matching files in all input directories
160 """
161 if not indir:
162 return glob.glob(fname)
163 files = []
164 for dirname in indir:
165 pathname = os.path.join(dirname, pattern)
166 files += glob.glob(pathname)
167 return sorted(files)
168
Simon Glassbe39f052016-07-25 18:59:08 -0600169def Align(pos, align):
170 if align:
171 mask = align - 1
172 pos = (pos + mask) & ~mask
173 return pos
174
175def NotPowerOfTwo(num):
176 return num and (num & (num - 1))
Simon Glass0362cd22018-07-17 13:25:43 -0600177
Simon Glass90c3c4c2019-07-08 13:18:27 -0600178def SetToolPaths(toolpaths):
179 """Set the path to search for tools
180
181 Args:
182 toolpaths: List of paths to search for tools executed by Run()
183 """
184 global tool_search_paths
185
186 tool_search_paths = toolpaths
187
188def PathHasFile(path_spec, fname):
Simon Glass778ab842018-09-14 04:57:25 -0600189 """Check if a given filename is in the PATH
190
191 Args:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600192 path_spec: Value of PATH variable to check
Simon Glass778ab842018-09-14 04:57:25 -0600193 fname: Filename to check
194
195 Returns:
196 True if found, False if not
197 """
Simon Glass90c3c4c2019-07-08 13:18:27 -0600198 for dir in path_spec.split(':'):
Simon Glass778ab842018-09-14 04:57:25 -0600199 if os.path.exists(os.path.join(dir, fname)):
200 return True
201 return False
202
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300203def GetHostCompileTool(name):
204 """Get the host-specific version for a compile tool
205
206 This checks the environment variables that specify which version of
207 the tool should be used (e.g. ${HOSTCC}).
208
209 The following table lists the host-specific versions of the tools
210 this function resolves to:
211
212 Compile Tool | Host version
213 --------------+----------------
214 as | ${HOSTAS}
215 ld | ${HOSTLD}
216 cc | ${HOSTCC}
217 cpp | ${HOSTCPP}
218 c++ | ${HOSTCXX}
219 ar | ${HOSTAR}
220 nm | ${HOSTNM}
221 ldr | ${HOSTLDR}
222 strip | ${HOSTSTRIP}
223 objcopy | ${HOSTOBJCOPY}
224 objdump | ${HOSTOBJDUMP}
225 dtc | ${HOSTDTC}
226
227 Args:
228 name: Command name to run
229
230 Returns:
231 host_name: Exact command name to run instead
232 extra_args: List of extra arguments to pass
233 """
234 host_name = None
235 extra_args = []
236 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
237 'objcopy', 'objdump', 'dtc'):
238 host_name, *host_args = env.get('HOST' + name.upper(), '').split(' ')
239 elif name == 'c++':
240 host_name, *host_args = env.get('HOSTCXX', '').split(' ')
241
242 if host_name:
243 return host_name, extra_args
244 return name, []
245
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300246def GetTargetCompileTool(name, cross_compile=None):
247 """Get the target-specific version for a compile tool
248
249 This first checks the environment variables that specify which
250 version of the tool should be used (e.g. ${CC}). If those aren't
251 specified, it checks the CROSS_COMPILE variable as a prefix for the
252 tool with some substitutions (e.g. "${CROSS_COMPILE}gcc" for cc).
253
254 The following table lists the target-specific versions of the tools
255 this function resolves to:
256
257 Compile Tool | First choice | Second choice
258 --------------+----------------+----------------------------
259 as | ${AS} | ${CROSS_COMPILE}as
260 ld | ${LD} | ${CROSS_COMPILE}ld.bfd
261 | | or ${CROSS_COMPILE}ld
262 cc | ${CC} | ${CROSS_COMPILE}gcc
263 cpp | ${CPP} | ${CROSS_COMPILE}gcc -E
264 c++ | ${CXX} | ${CROSS_COMPILE}g++
265 ar | ${AR} | ${CROSS_COMPILE}ar
266 nm | ${NM} | ${CROSS_COMPILE}nm
267 ldr | ${LDR} | ${CROSS_COMPILE}ldr
268 strip | ${STRIP} | ${CROSS_COMPILE}strip
269 objcopy | ${OBJCOPY} | ${CROSS_COMPILE}objcopy
270 objdump | ${OBJDUMP} | ${CROSS_COMPILE}objdump
271 dtc | ${DTC} | (no CROSS_COMPILE version)
272
273 Args:
274 name: Command name to run
275
276 Returns:
277 target_name: Exact command name to run instead
278 extra_args: List of extra arguments to pass
279 """
280 env = dict(os.environ)
281
282 target_name = None
283 extra_args = []
284 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
285 'objcopy', 'objdump', 'dtc'):
286 target_name, *extra_args = env.get(name.upper(), '').split(' ')
287 elif name == 'c++':
288 target_name, *extra_args = env.get('CXX', '').split(' ')
289
290 if target_name:
291 return target_name, extra_args
292
293 if cross_compile is None:
294 cross_compile = env.get('CROSS_COMPILE', '')
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300295
296 if name in ('as', 'ar', 'nm', 'ldr', 'strip', 'objcopy', 'objdump'):
297 target_name = cross_compile + name
298 elif name == 'ld':
299 try:
300 if Run(cross_compile + 'ld.bfd', '-v'):
301 target_name = cross_compile + 'ld.bfd'
302 except:
303 target_name = cross_compile + 'ld'
304 elif name == 'cc':
305 target_name = cross_compile + 'gcc'
306 elif name == 'cpp':
307 target_name = cross_compile + 'gcc'
308 extra_args = ['-E']
309 elif name == 'c++':
310 target_name = cross_compile + 'g++'
311 else:
312 target_name = name
313 return target_name, extra_args
314
Simon Glasscc311ac2019-10-31 07:42:50 -0600315def Run(name, *args, **kwargs):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600316 """Run a tool with some arguments
317
318 This runs a 'tool', which is a program used by binman to process files and
319 perhaps produce some output. Tools can be located on the PATH or in a
320 search path.
321
322 Args:
323 name: Command name to run
324 args: Arguments to the tool
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300325 for_host: True to resolve the command to the version for the host
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300326 for_target: False to run the command as-is, without resolving it
327 to the version for the compile target
Simon Glass90c3c4c2019-07-08 13:18:27 -0600328
329 Returns:
330 CommandResult object
331 """
Simon Glass778ab842018-09-14 04:57:25 -0600332 try:
Simon Glasscc311ac2019-10-31 07:42:50 -0600333 binary = kwargs.get('binary')
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300334 for_host = kwargs.get('for_host', False)
335 for_target = kwargs.get('for_target', not for_host)
Simon Glass90c3c4c2019-07-08 13:18:27 -0600336 env = None
337 if tool_search_paths:
338 env = dict(os.environ)
339 env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300340 if for_target:
341 name, extra_args = GetTargetCompileTool(name)
342 args = tuple(extra_args) + args
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300343 elif for_host:
344 name, extra_args = GetHostCompileTool(name)
345 args = tuple(extra_args) + args
Simon Glass6e892242020-11-09 07:45:02 -0700346 name = os.path.expanduser(name) # Expand paths containing ~
Simon Glass9f5051a2019-08-24 07:22:42 -0600347 all_args = (name,) + args
348 result = command.RunPipe([all_args], capture=True, capture_stderr=True,
Simon Glasscc311ac2019-10-31 07:42:50 -0600349 env=env, raise_on_error=False, binary=binary)
Simon Glass9f5051a2019-08-24 07:22:42 -0600350 if result.return_code:
351 raise Exception("Error %d running '%s': %s" %
352 (result.return_code,' '.join(all_args),
353 result.stderr))
354 return result.stdout
Simon Glass778ab842018-09-14 04:57:25 -0600355 except:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600356 if env and not PathHasFile(env['PATH'], name):
357 msg = "Please install tool '%s'" % name
Simon Glass778ab842018-09-14 04:57:25 -0600358 package = packages.get(name)
359 if package:
360 msg += " (e.g. from package '%s')" % package
361 raise ValueError(msg)
362 raise
Simon Glass0362cd22018-07-17 13:25:43 -0600363
364def Filename(fname):
365 """Resolve a file path to an absolute path.
366
367 If fname starts with ##/ and chroot is available, ##/ gets replaced with
368 the chroot path. If chroot is not available, this file name can not be
369 resolved, `None' is returned.
370
371 If fname is not prepended with the above prefix, and is not an existing
372 file, the actual file name is retrieved from the passed in string and the
373 search_paths directories (if any) are searched to for the file. If found -
374 the path to the found file is returned, `None' is returned otherwise.
375
376 Args:
377 fname: a string, the path to resolve.
378
379 Returns:
380 Absolute path to the file or None if not found.
381 """
382 if fname.startswith('##/'):
383 if chroot_path:
384 fname = os.path.join(chroot_path, fname[3:])
385 else:
386 return None
387
388 # Search for a pathname that exists, and return it if found
389 if fname and not os.path.exists(fname):
390 for path in search_paths:
391 pathname = os.path.join(path, os.path.basename(fname))
392 if os.path.exists(pathname):
393 return pathname
394
395 # If not found, just return the standard, unchanged path
396 return fname
397
Simon Glass0696ee62019-05-17 22:00:44 -0600398def ReadFile(fname, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600399 """Read and return the contents of a file.
400
401 Args:
402 fname: path to filename to read, where ## signifiies the chroot.
403
404 Returns:
405 data read from file, as a string.
406 """
Simon Glass0696ee62019-05-17 22:00:44 -0600407 with open(Filename(fname), binary and 'rb' or 'r') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600408 data = fd.read()
409 #self._out.Info("Read file '%s' size %d (%#0x)" %
410 #(fname, len(data), len(data)))
411 return data
412
Simon Glass54f1c5b2020-07-05 21:41:50 -0600413def WriteFile(fname, data, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600414 """Write data into a file.
415
416 Args:
417 fname: path to filename to write
418 data: data to write to file, as a string
419 """
420 #self._out.Info("Write file '%s' size %d (%#0x)" %
421 #(fname, len(data), len(data)))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600422 with open(Filename(fname), binary and 'wb' or 'w') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600423 fd.write(data)
Simon Glassac0d4952019-05-14 15:53:47 -0600424
425def GetBytes(byte, size):
426 """Get a string of bytes of a given size
427
Simon Glassac0d4952019-05-14 15:53:47 -0600428 Args:
429 byte: Numeric byte value to use
430 size: Size of bytes/string to return
431
432 Returns:
433 A bytes type with 'byte' repeated 'size' times
434 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700435 return bytes([byte]) * size
Simon Glassd8f593f2019-05-17 22:00:35 -0600436
Simon Glass1cd40082019-05-17 22:00:36 -0600437def ToBytes(string):
438 """Convert a str type into a bytes type
439
440 Args:
Simon Glasscc311ac2019-10-31 07:42:50 -0600441 string: string to convert
Simon Glass1cd40082019-05-17 22:00:36 -0600442
443 Returns:
Simon Glass9dfb3112020-11-08 20:36:18 -0700444 A bytes type
Simon Glass1cd40082019-05-17 22:00:36 -0600445 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700446 return string.encode('utf-8')
Simon Glassdfd19012019-07-08 13:18:41 -0600447
Simon Glasscc311ac2019-10-31 07:42:50 -0600448def ToString(bval):
449 """Convert a bytes type into a str type
450
451 Args:
452 bval: bytes value to convert
453
454 Returns:
455 Python 3: A bytes type
456 Python 2: A string type
457 """
458 return bval.decode('utf-8')
459
Simon Glass37fdd142019-07-20 12:24:06 -0600460def Compress(indata, algo, with_header=True):
Simon Glassdfd19012019-07-08 13:18:41 -0600461 """Compress some data using a given algorithm
462
463 Note that for lzma this uses an old version of the algorithm, not that
464 provided by xz.
465
466 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
467 directory to be previously set up, by calling PrepareOutputDir().
468
Simon Glassb87eac8f2021-07-06 10:36:36 -0600469 Care is taken to use unique temporary files so that this function can be
470 called from multiple threads.
471
Simon Glassdfd19012019-07-08 13:18:41 -0600472 Args:
473 indata: Input data to compress
474 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
475
476 Returns:
477 Compressed data
478 """
479 if algo == 'none':
480 return indata
Simon Glassb87eac8f2021-07-06 10:36:36 -0600481 fname = tempfile.NamedTemporaryFile(prefix='%s.comp.tmp' % algo,
482 dir=outdir).name
Simon Glassdfd19012019-07-08 13:18:41 -0600483 WriteFile(fname, indata)
484 if algo == 'lz4':
Simon Glass99209892021-01-06 21:35:11 -0700485 data = Run('lz4', '--no-frame-crc', '-B4', '-5', '-c', fname,
486 binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600487 # cbfstool uses a very old version of lzma
488 elif algo == 'lzma':
Simon Glassb87eac8f2021-07-06 10:36:36 -0600489 outfname = tempfile.NamedTemporaryFile(prefix='%s.comp.otmp' % algo,
490 dir=outdir).name
Simon Glassdfd19012019-07-08 13:18:41 -0600491 Run('lzma_alone', 'e', fname, outfname, '-lc1', '-lp0', '-pb0', '-d8')
492 data = ReadFile(outfname)
493 elif algo == 'gzip':
Simon Glasscc311ac2019-10-31 07:42:50 -0600494 data = Run('gzip', '-c', fname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600495 else:
496 raise ValueError("Unknown algorithm '%s'" % algo)
Simon Glass37fdd142019-07-20 12:24:06 -0600497 if with_header:
498 hdr = struct.pack('<I', len(data))
499 data = hdr + data
Simon Glassdfd19012019-07-08 13:18:41 -0600500 return data
501
Simon Glass37fdd142019-07-20 12:24:06 -0600502def Decompress(indata, algo, with_header=True):
Simon Glassdfd19012019-07-08 13:18:41 -0600503 """Decompress some data using a given algorithm
504
505 Note that for lzma this uses an old version of the algorithm, not that
506 provided by xz.
507
508 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
509 directory to be previously set up, by calling PrepareOutputDir().
510
511 Args:
512 indata: Input data to decompress
513 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
514
515 Returns:
516 Compressed data
517 """
518 if algo == 'none':
519 return indata
Simon Glass37fdd142019-07-20 12:24:06 -0600520 if with_header:
521 data_len = struct.unpack('<I', indata[:4])[0]
522 indata = indata[4:4 + data_len]
Simon Glassdfd19012019-07-08 13:18:41 -0600523 fname = GetOutputFilename('%s.decomp.tmp' % algo)
524 with open(fname, 'wb') as fd:
525 fd.write(indata)
526 if algo == 'lz4':
Simon Glasscc311ac2019-10-31 07:42:50 -0600527 data = Run('lz4', '-dc', fname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600528 elif algo == 'lzma':
529 outfname = GetOutputFilename('%s.decomp.otmp' % algo)
530 Run('lzma_alone', 'd', fname, outfname)
Simon Glasscc311ac2019-10-31 07:42:50 -0600531 data = ReadFile(outfname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600532 elif algo == 'gzip':
Simon Glasscc311ac2019-10-31 07:42:50 -0600533 data = Run('gzip', '-cd', fname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600534 else:
535 raise ValueError("Unknown algorithm '%s'" % algo)
536 return data
Simon Glass578d53e2019-07-08 13:18:51 -0600537
538CMD_CREATE, CMD_DELETE, CMD_ADD, CMD_REPLACE, CMD_EXTRACT = range(5)
539
540IFWITOOL_CMDS = {
541 CMD_CREATE: 'create',
542 CMD_DELETE: 'delete',
543 CMD_ADD: 'add',
544 CMD_REPLACE: 'replace',
545 CMD_EXTRACT: 'extract',
546 }
547
548def RunIfwiTool(ifwi_file, cmd, fname=None, subpart=None, entry_name=None):
549 """Run ifwitool with the given arguments:
550
551 Args:
552 ifwi_file: IFWI file to operation on
553 cmd: Command to execute (CMD_...)
554 fname: Filename of file to add/replace/extract/create (None for
555 CMD_DELETE)
556 subpart: Name of sub-partition to operation on (None for CMD_CREATE)
557 entry_name: Name of directory entry to operate on, or None if none
558 """
559 args = ['ifwitool', ifwi_file]
560 args.append(IFWITOOL_CMDS[cmd])
561 if fname:
562 args += ['-f', fname]
563 if subpart:
564 args += ['-n', subpart]
565 if entry_name:
566 args += ['-d', '-e', entry_name]
567 Run(*args)
Simon Glassb6dff4c2019-07-20 12:23:36 -0600568
569def ToHex(val):
570 """Convert an integer value (or None) to a string
571
572 Returns:
573 hex value, or 'None' if the value is None
574 """
575 return 'None' if val is None else '%#x' % val
576
577def ToHexSize(val):
578 """Return the size of an object in hex
579
580 Returns:
581 hex value of size, or 'None' if the value is None
582 """
583 return 'None' if val is None else '%#x' % len(val)