blob: 86c4f6162064761ba283281ff4c075c3fb90282a [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
Paul Barkerce7efa02021-09-08 12:38:02 +01008import shlex
Simon Glassbe39f052016-07-25 18:59:08 -06009import shutil
Simon Glass37fdd142019-07-20 12:24:06 -060010import struct
Simon Glassac0d4952019-05-14 15:53:47 -060011import sys
Simon Glassbe39f052016-07-25 18:59:08 -060012import tempfile
13
Simon Glassa997ea52020-04-17 18:09:04 -060014from patman import command
15from patman import tout
Simon Glassbe39f052016-07-25 18:59:08 -060016
Simon Glass0362cd22018-07-17 13:25:43 -060017# Output directly (generally this is temporary)
Simon Glassbe39f052016-07-25 18:59:08 -060018outdir = None
Simon Glass0362cd22018-07-17 13:25:43 -060019
20# True to keep the output directory around after exiting
Simon Glassbe39f052016-07-25 18:59:08 -060021preserve_outdir = False
22
Simon Glass0362cd22018-07-17 13:25:43 -060023# Path to the Chrome OS chroot, if we know it
24chroot_path = None
25
26# Search paths to use for Filename(), used to find files
27search_paths = []
28
Simon Glass90c3c4c2019-07-08 13:18:27 -060029tool_search_paths = []
30
Simon Glass778ab842018-09-14 04:57:25 -060031# Tools and the packages that contain them, on debian
32packages = {
33 'lz4': 'liblz4-tool',
34 }
Simon Glass0362cd22018-07-17 13:25:43 -060035
Simon Glass867b9062018-10-01 21:12:44 -060036# List of paths to use when looking for an input file
37indir = []
38
Simon Glassbe39f052016-07-25 18:59:08 -060039def PrepareOutputDir(dirname, preserve=False):
40 """Select an output directory, ensuring it exists.
41
42 This either creates a temporary directory or checks that the one supplied
43 by the user is valid. For a temporary directory, it makes a note to
44 remove it later if required.
45
46 Args:
47 dirname: a string, name of the output directory to use to store
48 intermediate and output files. If is None - create a temporary
49 directory.
50 preserve: a Boolean. If outdir above is None and preserve is False, the
51 created temporary directory will be destroyed on exit.
52
53 Raises:
54 OSError: If it cannot create the output directory.
55 """
56 global outdir, preserve_outdir
57
58 preserve_outdir = dirname or preserve
59 if dirname:
60 outdir = dirname
61 if not os.path.isdir(outdir):
62 try:
63 os.makedirs(outdir)
64 except OSError as err:
65 raise CmdError("Cannot make output directory '%s': '%s'" %
66 (outdir, err.strerror))
67 tout.Debug("Using output directory '%s'" % outdir)
68 else:
69 outdir = tempfile.mkdtemp(prefix='binman.')
70 tout.Debug("Using temporary directory '%s'" % outdir)
71
72def _RemoveOutputDir():
73 global outdir
74
75 shutil.rmtree(outdir)
76 tout.Debug("Deleted temporary directory '%s'" % outdir)
77 outdir = None
78
79def FinaliseOutputDir():
80 global outdir, preserve_outdir
81
82 """Tidy up: delete output directory if temporary and not preserved."""
83 if outdir and not preserve_outdir:
84 _RemoveOutputDir()
Simon Glass7f47e082019-07-20 12:24:07 -060085 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -060086
87def GetOutputFilename(fname):
88 """Return a filename within the output directory.
89
90 Args:
91 fname: Filename to use for new file
92
93 Returns:
94 The full path of the filename, within the output directory
95 """
96 return os.path.join(outdir, fname)
97
Simon Glass4e8e8462020-12-28 20:34:52 -070098def GetOutputDir():
99 """Return the current output directory
100
101 Returns:
102 str: The output directory
103 """
104 return outdir
105
Simon Glassbe39f052016-07-25 18:59:08 -0600106def _FinaliseForTest():
107 """Remove the output directory (for use by tests)"""
108 global outdir
109
110 if outdir:
111 _RemoveOutputDir()
Simon Glass7f47e082019-07-20 12:24:07 -0600112 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -0600113
114def SetInputDirs(dirname):
115 """Add a list of input directories, where input files are kept.
116
117 Args:
118 dirname: a list of paths to input directories to use for obtaining
119 files needed by binman to place in the image.
120 """
121 global indir
122
123 indir = dirname
124 tout.Debug("Using input directories %s" % indir)
125
Simon Glass5d94cc62020-07-09 18:39:38 -0600126def GetInputFilename(fname, allow_missing=False):
Simon Glassbe39f052016-07-25 18:59:08 -0600127 """Return a filename for use as input.
128
129 Args:
130 fname: Filename to use for new file
Simon Glass5d94cc62020-07-09 18:39:38 -0600131 allow_missing: True if the filename can be missing
Simon Glassbe39f052016-07-25 18:59:08 -0600132
133 Returns:
Simon Glasseb751fd2021-03-18 20:25:03 +1300134 fname, if indir is None;
135 full path of the filename, within the input directory;
136 None, if file is missing and allow_missing is True
137
138 Raises:
139 ValueError if file is missing and allow_missing is False
Simon Glassbe39f052016-07-25 18:59:08 -0600140 """
Simon Glass4affd4b2019-08-24 07:22:54 -0600141 if not indir or fname[:1] == '/':
Simon Glassbe39f052016-07-25 18:59:08 -0600142 return fname
143 for dirname in indir:
144 pathname = os.path.join(dirname, fname)
145 if os.path.exists(pathname):
146 return pathname
147
Simon Glass5d94cc62020-07-09 18:39:38 -0600148 if allow_missing:
149 return None
Simon Glassf2eb0542018-07-17 13:25:45 -0600150 raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
151 (fname, ','.join(indir), os.getcwd()))
Simon Glassbe39f052016-07-25 18:59:08 -0600152
Simon Glassac6328c2018-09-14 04:57:28 -0600153def GetInputFilenameGlob(pattern):
154 """Return a list of filenames for use as input.
155
156 Args:
157 pattern: Filename pattern to search for
158
159 Returns:
160 A list of matching files in all input directories
161 """
162 if not indir:
163 return glob.glob(fname)
164 files = []
165 for dirname in indir:
166 pathname = os.path.join(dirname, pattern)
167 files += glob.glob(pathname)
168 return sorted(files)
169
Simon Glassbe39f052016-07-25 18:59:08 -0600170def Align(pos, align):
171 if align:
172 mask = align - 1
173 pos = (pos + mask) & ~mask
174 return pos
175
176def NotPowerOfTwo(num):
177 return num and (num & (num - 1))
Simon Glass0362cd22018-07-17 13:25:43 -0600178
Simon Glass90c3c4c2019-07-08 13:18:27 -0600179def SetToolPaths(toolpaths):
180 """Set the path to search for tools
181
182 Args:
183 toolpaths: List of paths to search for tools executed by Run()
184 """
185 global tool_search_paths
186
187 tool_search_paths = toolpaths
188
189def PathHasFile(path_spec, fname):
Simon Glass778ab842018-09-14 04:57:25 -0600190 """Check if a given filename is in the PATH
191
192 Args:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600193 path_spec: Value of PATH variable to check
Simon Glass778ab842018-09-14 04:57:25 -0600194 fname: Filename to check
195
196 Returns:
197 True if found, False if not
198 """
Simon Glass90c3c4c2019-07-08 13:18:27 -0600199 for dir in path_spec.split(':'):
Simon Glass778ab842018-09-14 04:57:25 -0600200 if os.path.exists(os.path.join(dir, fname)):
201 return True
202 return False
203
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300204def GetHostCompileTool(name):
205 """Get the host-specific version for a compile tool
206
207 This checks the environment variables that specify which version of
208 the tool should be used (e.g. ${HOSTCC}).
209
210 The following table lists the host-specific versions of the tools
211 this function resolves to:
212
213 Compile Tool | Host version
214 --------------+----------------
215 as | ${HOSTAS}
216 ld | ${HOSTLD}
217 cc | ${HOSTCC}
218 cpp | ${HOSTCPP}
219 c++ | ${HOSTCXX}
220 ar | ${HOSTAR}
221 nm | ${HOSTNM}
222 ldr | ${HOSTLDR}
223 strip | ${HOSTSTRIP}
224 objcopy | ${HOSTOBJCOPY}
225 objdump | ${HOSTOBJDUMP}
226 dtc | ${HOSTDTC}
227
228 Args:
229 name: Command name to run
230
231 Returns:
232 host_name: Exact command name to run instead
233 extra_args: List of extra arguments to pass
234 """
235 host_name = None
236 extra_args = []
237 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
238 'objcopy', 'objdump', 'dtc'):
239 host_name, *host_args = env.get('HOST' + name.upper(), '').split(' ')
240 elif name == 'c++':
241 host_name, *host_args = env.get('HOSTCXX', '').split(' ')
242
243 if host_name:
244 return host_name, extra_args
245 return name, []
246
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300247def GetTargetCompileTool(name, cross_compile=None):
248 """Get the target-specific version for a compile tool
249
250 This first checks the environment variables that specify which
251 version of the tool should be used (e.g. ${CC}). If those aren't
252 specified, it checks the CROSS_COMPILE variable as a prefix for the
253 tool with some substitutions (e.g. "${CROSS_COMPILE}gcc" for cc).
254
255 The following table lists the target-specific versions of the tools
256 this function resolves to:
257
258 Compile Tool | First choice | Second choice
259 --------------+----------------+----------------------------
260 as | ${AS} | ${CROSS_COMPILE}as
261 ld | ${LD} | ${CROSS_COMPILE}ld.bfd
262 | | or ${CROSS_COMPILE}ld
263 cc | ${CC} | ${CROSS_COMPILE}gcc
264 cpp | ${CPP} | ${CROSS_COMPILE}gcc -E
265 c++ | ${CXX} | ${CROSS_COMPILE}g++
266 ar | ${AR} | ${CROSS_COMPILE}ar
267 nm | ${NM} | ${CROSS_COMPILE}nm
268 ldr | ${LDR} | ${CROSS_COMPILE}ldr
269 strip | ${STRIP} | ${CROSS_COMPILE}strip
270 objcopy | ${OBJCOPY} | ${CROSS_COMPILE}objcopy
271 objdump | ${OBJDUMP} | ${CROSS_COMPILE}objdump
272 dtc | ${DTC} | (no CROSS_COMPILE version)
273
274 Args:
275 name: Command name to run
276
277 Returns:
278 target_name: Exact command name to run instead
279 extra_args: List of extra arguments to pass
280 """
281 env = dict(os.environ)
282
283 target_name = None
284 extra_args = []
285 if name in ('as', 'ld', 'cc', 'cpp', 'ar', 'nm', 'ldr', 'strip',
286 'objcopy', 'objdump', 'dtc'):
287 target_name, *extra_args = env.get(name.upper(), '').split(' ')
288 elif name == 'c++':
289 target_name, *extra_args = env.get('CXX', '').split(' ')
290
291 if target_name:
292 return target_name, extra_args
293
294 if cross_compile is None:
295 cross_compile = env.get('CROSS_COMPILE', '')
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300296
297 if name in ('as', 'ar', 'nm', 'ldr', 'strip', 'objcopy', 'objdump'):
298 target_name = cross_compile + name
299 elif name == 'ld':
300 try:
301 if Run(cross_compile + 'ld.bfd', '-v'):
302 target_name = cross_compile + 'ld.bfd'
303 except:
304 target_name = cross_compile + 'ld'
305 elif name == 'cc':
306 target_name = cross_compile + 'gcc'
307 elif name == 'cpp':
308 target_name = cross_compile + 'gcc'
309 extra_args = ['-E']
310 elif name == 'c++':
311 target_name = cross_compile + 'g++'
312 else:
313 target_name = name
314 return target_name, extra_args
315
Simon Glasscc311ac2019-10-31 07:42:50 -0600316def Run(name, *args, **kwargs):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600317 """Run a tool with some arguments
318
319 This runs a 'tool', which is a program used by binman to process files and
320 perhaps produce some output. Tools can be located on the PATH or in a
321 search path.
322
323 Args:
324 name: Command name to run
325 args: Arguments to the tool
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300326 for_host: True to resolve the command to the version for the host
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300327 for_target: False to run the command as-is, without resolving it
328 to the version for the compile target
Simon Glass90c3c4c2019-07-08 13:18:27 -0600329
330 Returns:
331 CommandResult object
332 """
Simon Glass778ab842018-09-14 04:57:25 -0600333 try:
Simon Glasscc311ac2019-10-31 07:42:50 -0600334 binary = kwargs.get('binary')
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300335 for_host = kwargs.get('for_host', False)
336 for_target = kwargs.get('for_target', not for_host)
Simon Glass90c3c4c2019-07-08 13:18:27 -0600337 env = None
338 if tool_search_paths:
339 env = dict(os.environ)
340 env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300341 if for_target:
342 name, extra_args = GetTargetCompileTool(name)
343 args = tuple(extra_args) + args
Alper Nebi Yasakcd740d32020-09-06 14:46:06 +0300344 elif for_host:
345 name, extra_args = GetHostCompileTool(name)
346 args = tuple(extra_args) + args
Simon Glass6e892242020-11-09 07:45:02 -0700347 name = os.path.expanduser(name) # Expand paths containing ~
Simon Glass9f5051a2019-08-24 07:22:42 -0600348 all_args = (name,) + args
349 result = command.RunPipe([all_args], capture=True, capture_stderr=True,
Simon Glasscc311ac2019-10-31 07:42:50 -0600350 env=env, raise_on_error=False, binary=binary)
Simon Glass9f5051a2019-08-24 07:22:42 -0600351 if result.return_code:
Simon Glass6db1e322021-11-03 21:09:14 -0600352 raise ValueError("Error %d running '%s': %s" %
Simon Glass9f5051a2019-08-24 07:22:42 -0600353 (result.return_code,' '.join(all_args),
354 result.stderr))
355 return result.stdout
Simon Glass778ab842018-09-14 04:57:25 -0600356 except:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600357 if env and not PathHasFile(env['PATH'], name):
358 msg = "Please install tool '%s'" % name
Simon Glass778ab842018-09-14 04:57:25 -0600359 package = packages.get(name)
360 if package:
361 msg += " (e.g. from package '%s')" % package
362 raise ValueError(msg)
363 raise
Simon Glass0362cd22018-07-17 13:25:43 -0600364
365def Filename(fname):
366 """Resolve a file path to an absolute path.
367
368 If fname starts with ##/ and chroot is available, ##/ gets replaced with
369 the chroot path. If chroot is not available, this file name can not be
370 resolved, `None' is returned.
371
372 If fname is not prepended with the above prefix, and is not an existing
373 file, the actual file name is retrieved from the passed in string and the
374 search_paths directories (if any) are searched to for the file. If found -
375 the path to the found file is returned, `None' is returned otherwise.
376
377 Args:
378 fname: a string, the path to resolve.
379
380 Returns:
381 Absolute path to the file or None if not found.
382 """
383 if fname.startswith('##/'):
384 if chroot_path:
385 fname = os.path.join(chroot_path, fname[3:])
386 else:
387 return None
388
389 # Search for a pathname that exists, and return it if found
390 if fname and not os.path.exists(fname):
391 for path in search_paths:
392 pathname = os.path.join(path, os.path.basename(fname))
393 if os.path.exists(pathname):
394 return pathname
395
396 # If not found, just return the standard, unchanged path
397 return fname
398
Simon Glass0696ee62019-05-17 22:00:44 -0600399def ReadFile(fname, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600400 """Read and return the contents of a file.
401
402 Args:
403 fname: path to filename to read, where ## signifiies the chroot.
404
405 Returns:
406 data read from file, as a string.
407 """
Simon Glass0696ee62019-05-17 22:00:44 -0600408 with open(Filename(fname), binary and 'rb' or 'r') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600409 data = fd.read()
410 #self._out.Info("Read file '%s' size %d (%#0x)" %
411 #(fname, len(data), len(data)))
412 return data
413
Simon Glass54f1c5b2020-07-05 21:41:50 -0600414def WriteFile(fname, data, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600415 """Write data into a file.
416
417 Args:
418 fname: path to filename to write
419 data: data to write to file, as a string
420 """
421 #self._out.Info("Write file '%s' size %d (%#0x)" %
422 #(fname, len(data), len(data)))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600423 with open(Filename(fname), binary and 'wb' or 'w') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600424 fd.write(data)
Simon Glassac0d4952019-05-14 15:53:47 -0600425
426def GetBytes(byte, size):
427 """Get a string of bytes of a given size
428
Simon Glassac0d4952019-05-14 15:53:47 -0600429 Args:
430 byte: Numeric byte value to use
431 size: Size of bytes/string to return
432
433 Returns:
434 A bytes type with 'byte' repeated 'size' times
435 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700436 return bytes([byte]) * size
Simon Glassd8f593f2019-05-17 22:00:35 -0600437
Simon Glass1cd40082019-05-17 22:00:36 -0600438def ToBytes(string):
439 """Convert a str type into a bytes type
440
441 Args:
Simon Glasscc311ac2019-10-31 07:42:50 -0600442 string: string to convert
Simon Glass1cd40082019-05-17 22:00:36 -0600443
444 Returns:
Simon Glass9dfb3112020-11-08 20:36:18 -0700445 A bytes type
Simon Glass1cd40082019-05-17 22:00:36 -0600446 """
Simon Glass9dfb3112020-11-08 20:36:18 -0700447 return string.encode('utf-8')
Simon Glassdfd19012019-07-08 13:18:41 -0600448
Simon Glasscc311ac2019-10-31 07:42:50 -0600449def ToString(bval):
450 """Convert a bytes type into a str type
451
452 Args:
453 bval: bytes value to convert
454
455 Returns:
456 Python 3: A bytes type
457 Python 2: A string type
458 """
459 return bval.decode('utf-8')
460
Simon Glass37fdd142019-07-20 12:24:06 -0600461def Compress(indata, algo, with_header=True):
Simon Glassdfd19012019-07-08 13:18:41 -0600462 """Compress some data using a given algorithm
463
464 Note that for lzma this uses an old version of the algorithm, not that
465 provided by xz.
466
467 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
468 directory to be previously set up, by calling PrepareOutputDir().
469
Simon Glassb87eac8f2021-07-06 10:36:36 -0600470 Care is taken to use unique temporary files so that this function can be
471 called from multiple threads.
472
Simon Glassdfd19012019-07-08 13:18:41 -0600473 Args:
474 indata: Input data to compress
475 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
476
477 Returns:
478 Compressed data
479 """
480 if algo == 'none':
481 return indata
Simon Glassb87eac8f2021-07-06 10:36:36 -0600482 fname = tempfile.NamedTemporaryFile(prefix='%s.comp.tmp' % algo,
483 dir=outdir).name
Simon Glassdfd19012019-07-08 13:18:41 -0600484 WriteFile(fname, indata)
485 if algo == 'lz4':
Simon Glass99209892021-01-06 21:35:11 -0700486 data = Run('lz4', '--no-frame-crc', '-B4', '-5', '-c', fname,
487 binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600488 # cbfstool uses a very old version of lzma
489 elif algo == 'lzma':
Simon Glassb87eac8f2021-07-06 10:36:36 -0600490 outfname = tempfile.NamedTemporaryFile(prefix='%s.comp.otmp' % algo,
491 dir=outdir).name
Simon Glassdfd19012019-07-08 13:18:41 -0600492 Run('lzma_alone', 'e', fname, outfname, '-lc1', '-lp0', '-pb0', '-d8')
493 data = ReadFile(outfname)
494 elif algo == 'gzip':
Simon Glasscc311ac2019-10-31 07:42:50 -0600495 data = Run('gzip', '-c', fname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600496 else:
497 raise ValueError("Unknown algorithm '%s'" % algo)
Simon Glass37fdd142019-07-20 12:24:06 -0600498 if with_header:
499 hdr = struct.pack('<I', len(data))
500 data = hdr + data
Simon Glassdfd19012019-07-08 13:18:41 -0600501 return data
502
Simon Glass37fdd142019-07-20 12:24:06 -0600503def Decompress(indata, algo, with_header=True):
Simon Glassdfd19012019-07-08 13:18:41 -0600504 """Decompress some data using a given algorithm
505
506 Note that for lzma this uses an old version of the algorithm, not that
507 provided by xz.
508
509 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
510 directory to be previously set up, by calling PrepareOutputDir().
511
512 Args:
513 indata: Input data to decompress
514 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
515
516 Returns:
517 Compressed data
518 """
519 if algo == 'none':
520 return indata
Simon Glass37fdd142019-07-20 12:24:06 -0600521 if with_header:
522 data_len = struct.unpack('<I', indata[:4])[0]
523 indata = indata[4:4 + data_len]
Simon Glassdfd19012019-07-08 13:18:41 -0600524 fname = GetOutputFilename('%s.decomp.tmp' % algo)
525 with open(fname, 'wb') as fd:
526 fd.write(indata)
527 if algo == 'lz4':
Simon Glasscc311ac2019-10-31 07:42:50 -0600528 data = Run('lz4', '-dc', fname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600529 elif algo == 'lzma':
530 outfname = GetOutputFilename('%s.decomp.otmp' % algo)
531 Run('lzma_alone', 'd', fname, outfname)
Simon Glasscc311ac2019-10-31 07:42:50 -0600532 data = ReadFile(outfname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600533 elif algo == 'gzip':
Simon Glasscc311ac2019-10-31 07:42:50 -0600534 data = Run('gzip', '-cd', fname, binary=True)
Simon Glassdfd19012019-07-08 13:18:41 -0600535 else:
536 raise ValueError("Unknown algorithm '%s'" % algo)
537 return data
Simon Glass578d53e2019-07-08 13:18:51 -0600538
539CMD_CREATE, CMD_DELETE, CMD_ADD, CMD_REPLACE, CMD_EXTRACT = range(5)
540
541IFWITOOL_CMDS = {
542 CMD_CREATE: 'create',
543 CMD_DELETE: 'delete',
544 CMD_ADD: 'add',
545 CMD_REPLACE: 'replace',
546 CMD_EXTRACT: 'extract',
547 }
548
549def RunIfwiTool(ifwi_file, cmd, fname=None, subpart=None, entry_name=None):
550 """Run ifwitool with the given arguments:
551
552 Args:
553 ifwi_file: IFWI file to operation on
554 cmd: Command to execute (CMD_...)
555 fname: Filename of file to add/replace/extract/create (None for
556 CMD_DELETE)
557 subpart: Name of sub-partition to operation on (None for CMD_CREATE)
558 entry_name: Name of directory entry to operate on, or None if none
559 """
560 args = ['ifwitool', ifwi_file]
561 args.append(IFWITOOL_CMDS[cmd])
562 if fname:
563 args += ['-f', fname]
564 if subpart:
565 args += ['-n', subpart]
566 if entry_name:
567 args += ['-d', '-e', entry_name]
568 Run(*args)
Simon Glassb6dff4c2019-07-20 12:23:36 -0600569
570def ToHex(val):
571 """Convert an integer value (or None) to a string
572
573 Returns:
574 hex value, or 'None' if the value is None
575 """
576 return 'None' if val is None else '%#x' % val
577
578def ToHexSize(val):
579 """Return the size of an object in hex
580
581 Returns:
582 hex value of size, or 'None' if the value is None
583 """
584 return 'None' if val is None else '%#x' % len(val)
Paul Barker25ecd972021-09-08 12:38:01 +0100585
586def PrintFullHelp(fname):
587 """Print the full help message for a tool using an appropriate pager.
588
589 Args:
590 fname: Path to a file containing the full help message
591 """
Paul Barkerce7efa02021-09-08 12:38:02 +0100592 pager = shlex.split(os.getenv('PAGER', ''))
Paul Barker25ecd972021-09-08 12:38:01 +0100593 if not pager:
Paul Barkerce7efa02021-09-08 12:38:02 +0100594 lesspath = shutil.which('less')
595 pager = [lesspath] if lesspath else None
Paul Barker25ecd972021-09-08 12:38:01 +0100596 if not pager:
Paul Barkerce7efa02021-09-08 12:38:02 +0100597 pager = ['more']
598 command.Run(*pager, fname)