blob: 0d4705db7605181e520a0230befaa2d7d56a4ff3 [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 Glass578d53e2019-07-08 13:18:51 -06006from __future__ import print_function
7
Simon Glass0362cd22018-07-17 13:25:43 -06008import command
Simon Glassac6328c2018-09-14 04:57:28 -06009import glob
Simon Glassbe39f052016-07-25 18:59:08 -060010import os
11import shutil
Simon Glass37fdd142019-07-20 12:24:06 -060012import struct
Simon Glassac0d4952019-05-14 15:53:47 -060013import sys
Simon Glassbe39f052016-07-25 18:59:08 -060014import tempfile
15
16import tout
17
Simon Glass0362cd22018-07-17 13:25:43 -060018# Output directly (generally this is temporary)
Simon Glassbe39f052016-07-25 18:59:08 -060019outdir = None
Simon Glass0362cd22018-07-17 13:25:43 -060020
21# True to keep the output directory around after exiting
Simon Glassbe39f052016-07-25 18:59:08 -060022preserve_outdir = False
23
Simon Glass0362cd22018-07-17 13:25:43 -060024# Path to the Chrome OS chroot, if we know it
25chroot_path = None
26
27# Search paths to use for Filename(), used to find files
28search_paths = []
29
Simon Glass90c3c4c2019-07-08 13:18:27 -060030tool_search_paths = []
31
Simon Glass778ab842018-09-14 04:57:25 -060032# Tools and the packages that contain them, on debian
33packages = {
34 'lz4': 'liblz4-tool',
35 }
Simon Glass0362cd22018-07-17 13:25:43 -060036
Simon Glass867b9062018-10-01 21:12:44 -060037# List of paths to use when looking for an input file
38indir = []
39
Simon Glassbe39f052016-07-25 18:59:08 -060040def PrepareOutputDir(dirname, preserve=False):
41 """Select an output directory, ensuring it exists.
42
43 This either creates a temporary directory or checks that the one supplied
44 by the user is valid. For a temporary directory, it makes a note to
45 remove it later if required.
46
47 Args:
48 dirname: a string, name of the output directory to use to store
49 intermediate and output files. If is None - create a temporary
50 directory.
51 preserve: a Boolean. If outdir above is None and preserve is False, the
52 created temporary directory will be destroyed on exit.
53
54 Raises:
55 OSError: If it cannot create the output directory.
56 """
57 global outdir, preserve_outdir
58
59 preserve_outdir = dirname or preserve
60 if dirname:
61 outdir = dirname
62 if not os.path.isdir(outdir):
63 try:
64 os.makedirs(outdir)
65 except OSError as err:
66 raise CmdError("Cannot make output directory '%s': '%s'" %
67 (outdir, err.strerror))
68 tout.Debug("Using output directory '%s'" % outdir)
69 else:
70 outdir = tempfile.mkdtemp(prefix='binman.')
71 tout.Debug("Using temporary directory '%s'" % outdir)
72
73def _RemoveOutputDir():
74 global outdir
75
76 shutil.rmtree(outdir)
77 tout.Debug("Deleted temporary directory '%s'" % outdir)
78 outdir = None
79
80def FinaliseOutputDir():
81 global outdir, preserve_outdir
82
83 """Tidy up: delete output directory if temporary and not preserved."""
84 if outdir and not preserve_outdir:
85 _RemoveOutputDir()
Simon Glass7f47e082019-07-20 12:24:07 -060086 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -060087
88def GetOutputFilename(fname):
89 """Return a filename within the output directory.
90
91 Args:
92 fname: Filename to use for new file
93
94 Returns:
95 The full path of the filename, within the output directory
96 """
97 return os.path.join(outdir, fname)
98
99def _FinaliseForTest():
100 """Remove the output directory (for use by tests)"""
101 global outdir
102
103 if outdir:
104 _RemoveOutputDir()
Simon Glass7f47e082019-07-20 12:24:07 -0600105 outdir = None
Simon Glassbe39f052016-07-25 18:59:08 -0600106
107def SetInputDirs(dirname):
108 """Add a list of input directories, where input files are kept.
109
110 Args:
111 dirname: a list of paths to input directories to use for obtaining
112 files needed by binman to place in the image.
113 """
114 global indir
115
116 indir = dirname
117 tout.Debug("Using input directories %s" % indir)
118
119def GetInputFilename(fname):
120 """Return a filename for use as input.
121
122 Args:
123 fname: Filename to use for new file
124
125 Returns:
126 The full path of the filename, within the input directory
127 """
128 if not indir:
129 return fname
130 for dirname in indir:
131 pathname = os.path.join(dirname, fname)
132 if os.path.exists(pathname):
133 return pathname
134
Simon Glassf2eb0542018-07-17 13:25:45 -0600135 raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
136 (fname, ','.join(indir), os.getcwd()))
Simon Glassbe39f052016-07-25 18:59:08 -0600137
Simon Glassac6328c2018-09-14 04:57:28 -0600138def GetInputFilenameGlob(pattern):
139 """Return a list of filenames for use as input.
140
141 Args:
142 pattern: Filename pattern to search for
143
144 Returns:
145 A list of matching files in all input directories
146 """
147 if not indir:
148 return glob.glob(fname)
149 files = []
150 for dirname in indir:
151 pathname = os.path.join(dirname, pattern)
152 files += glob.glob(pathname)
153 return sorted(files)
154
Simon Glassbe39f052016-07-25 18:59:08 -0600155def Align(pos, align):
156 if align:
157 mask = align - 1
158 pos = (pos + mask) & ~mask
159 return pos
160
161def NotPowerOfTwo(num):
162 return num and (num & (num - 1))
Simon Glass0362cd22018-07-17 13:25:43 -0600163
Simon Glass90c3c4c2019-07-08 13:18:27 -0600164def SetToolPaths(toolpaths):
165 """Set the path to search for tools
166
167 Args:
168 toolpaths: List of paths to search for tools executed by Run()
169 """
170 global tool_search_paths
171
172 tool_search_paths = toolpaths
173
174def PathHasFile(path_spec, fname):
Simon Glass778ab842018-09-14 04:57:25 -0600175 """Check if a given filename is in the PATH
176
177 Args:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600178 path_spec: Value of PATH variable to check
Simon Glass778ab842018-09-14 04:57:25 -0600179 fname: Filename to check
180
181 Returns:
182 True if found, False if not
183 """
Simon Glass90c3c4c2019-07-08 13:18:27 -0600184 for dir in path_spec.split(':'):
Simon Glass778ab842018-09-14 04:57:25 -0600185 if os.path.exists(os.path.join(dir, fname)):
186 return True
187 return False
188
Simon Glassd8e9bca2019-05-14 15:53:44 -0600189def Run(name, *args, **kwargs):
Simon Glass90c3c4c2019-07-08 13:18:27 -0600190 """Run a tool with some arguments
191
192 This runs a 'tool', which is a program used by binman to process files and
193 perhaps produce some output. Tools can be located on the PATH or in a
194 search path.
195
196 Args:
197 name: Command name to run
198 args: Arguments to the tool
199 kwargs: Options to pass to command.run()
200
201 Returns:
202 CommandResult object
203 """
Simon Glass778ab842018-09-14 04:57:25 -0600204 try:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600205 env = None
206 if tool_search_paths:
207 env = dict(os.environ)
208 env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
209 return command.Run(name, *args, capture=True,
210 capture_stderr=True, env=env, **kwargs)
Simon Glass778ab842018-09-14 04:57:25 -0600211 except:
Simon Glass90c3c4c2019-07-08 13:18:27 -0600212 if env and not PathHasFile(env['PATH'], name):
213 msg = "Please install tool '%s'" % name
Simon Glass778ab842018-09-14 04:57:25 -0600214 package = packages.get(name)
215 if package:
216 msg += " (e.g. from package '%s')" % package
217 raise ValueError(msg)
218 raise
Simon Glass0362cd22018-07-17 13:25:43 -0600219
220def Filename(fname):
221 """Resolve a file path to an absolute path.
222
223 If fname starts with ##/ and chroot is available, ##/ gets replaced with
224 the chroot path. If chroot is not available, this file name can not be
225 resolved, `None' is returned.
226
227 If fname is not prepended with the above prefix, and is not an existing
228 file, the actual file name is retrieved from the passed in string and the
229 search_paths directories (if any) are searched to for the file. If found -
230 the path to the found file is returned, `None' is returned otherwise.
231
232 Args:
233 fname: a string, the path to resolve.
234
235 Returns:
236 Absolute path to the file or None if not found.
237 """
238 if fname.startswith('##/'):
239 if chroot_path:
240 fname = os.path.join(chroot_path, fname[3:])
241 else:
242 return None
243
244 # Search for a pathname that exists, and return it if found
245 if fname and not os.path.exists(fname):
246 for path in search_paths:
247 pathname = os.path.join(path, os.path.basename(fname))
248 if os.path.exists(pathname):
249 return pathname
250
251 # If not found, just return the standard, unchanged path
252 return fname
253
Simon Glass0696ee62019-05-17 22:00:44 -0600254def ReadFile(fname, binary=True):
Simon Glass0362cd22018-07-17 13:25:43 -0600255 """Read and return the contents of a file.
256
257 Args:
258 fname: path to filename to read, where ## signifiies the chroot.
259
260 Returns:
261 data read from file, as a string.
262 """
Simon Glass0696ee62019-05-17 22:00:44 -0600263 with open(Filename(fname), binary and 'rb' or 'r') as fd:
Simon Glass0362cd22018-07-17 13:25:43 -0600264 data = fd.read()
265 #self._out.Info("Read file '%s' size %d (%#0x)" %
266 #(fname, len(data), len(data)))
267 return data
268
269def WriteFile(fname, data):
270 """Write data into a file.
271
272 Args:
273 fname: path to filename to write
274 data: data to write to file, as a string
275 """
276 #self._out.Info("Write file '%s' size %d (%#0x)" %
277 #(fname, len(data), len(data)))
278 with open(Filename(fname), 'wb') as fd:
279 fd.write(data)
Simon Glassac0d4952019-05-14 15:53:47 -0600280
281def GetBytes(byte, size):
282 """Get a string of bytes of a given size
283
284 This handles the unfortunate different between Python 2 and Python 2.
285
286 Args:
287 byte: Numeric byte value to use
288 size: Size of bytes/string to return
289
290 Returns:
291 A bytes type with 'byte' repeated 'size' times
292 """
293 if sys.version_info[0] >= 3:
294 data = bytes([byte]) * size
295 else:
296 data = chr(byte) * size
297 return data
Simon Glass8bb7a7a2019-05-14 15:53:50 -0600298
299def ToUnicode(val):
300 """Make sure a value is a unicode string
301
302 This allows some amount of compatibility between Python 2 and Python3. For
303 the former, it returns a unicode object.
304
305 Args:
306 val: string or unicode object
307
308 Returns:
309 unicode version of val
310 """
311 if sys.version_info[0] >= 3:
312 return val
313 return val if isinstance(val, unicode) else val.decode('utf-8')
314
315def FromUnicode(val):
316 """Make sure a value is a non-unicode string
317
318 This allows some amount of compatibility between Python 2 and Python3. For
319 the former, it converts a unicode object to a string.
320
321 Args:
322 val: string or unicode object
323
324 Returns:
325 non-unicode version of val
326 """
327 if sys.version_info[0] >= 3:
328 return val
329 return val if isinstance(val, str) else val.encode('utf-8')
Simon Glassd8f593f2019-05-17 22:00:35 -0600330
331def ToByte(ch):
332 """Convert a character to an ASCII value
333
334 This is useful because in Python 2 bytes is an alias for str, but in
335 Python 3 they are separate types. This function converts the argument to
336 an ASCII value in either case.
337
338 Args:
339 ch: A string (Python 2) or byte (Python 3) value
340
341 Returns:
342 integer ASCII value for ch
343 """
344 return ord(ch) if type(ch) == str else ch
345
346def ToChar(byte):
347 """Convert a byte to a character
348
349 This is useful because in Python 2 bytes is an alias for str, but in
350 Python 3 they are separate types. This function converts an ASCII value to
351 a value with the appropriate type in either case.
352
353 Args:
354 byte: A byte or str value
355 """
356 return chr(byte) if type(byte) != str else byte
Simon Glass1cd40082019-05-17 22:00:36 -0600357
358def ToChars(byte_list):
359 """Convert a list of bytes to a str/bytes type
360
361 Args:
362 byte_list: List of ASCII values representing the string
363
364 Returns:
365 string made by concatenating all the ASCII values
366 """
367 return ''.join([chr(byte) for byte in byte_list])
368
369def ToBytes(string):
370 """Convert a str type into a bytes type
371
372 Args:
373 string: string to convert value
374
375 Returns:
376 Python 3: A bytes type
377 Python 2: A string type
378 """
379 if sys.version_info[0] >= 3:
380 return string.encode('utf-8')
381 return string
Simon Glassdfd19012019-07-08 13:18:41 -0600382
Simon Glass37fdd142019-07-20 12:24:06 -0600383def Compress(indata, algo, with_header=True):
Simon Glassdfd19012019-07-08 13:18:41 -0600384 """Compress some data using a given algorithm
385
386 Note that for lzma this uses an old version of the algorithm, not that
387 provided by xz.
388
389 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
390 directory to be previously set up, by calling PrepareOutputDir().
391
392 Args:
393 indata: Input data to compress
394 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
395
396 Returns:
397 Compressed data
398 """
399 if algo == 'none':
400 return indata
401 fname = GetOutputFilename('%s.comp.tmp' % algo)
402 WriteFile(fname, indata)
403 if algo == 'lz4':
404 data = Run('lz4', '--no-frame-crc', '-c', fname, binary=True)
405 # cbfstool uses a very old version of lzma
406 elif algo == 'lzma':
407 outfname = GetOutputFilename('%s.comp.otmp' % algo)
408 Run('lzma_alone', 'e', fname, outfname, '-lc1', '-lp0', '-pb0', '-d8')
409 data = ReadFile(outfname)
410 elif algo == 'gzip':
411 data = Run('gzip', '-c', fname, binary=True)
412 else:
413 raise ValueError("Unknown algorithm '%s'" % algo)
Simon Glass37fdd142019-07-20 12:24:06 -0600414 if with_header:
415 hdr = struct.pack('<I', len(data))
416 data = hdr + data
Simon Glassdfd19012019-07-08 13:18:41 -0600417 return data
418
Simon Glass37fdd142019-07-20 12:24:06 -0600419def Decompress(indata, algo, with_header=True):
Simon Glassdfd19012019-07-08 13:18:41 -0600420 """Decompress some data using a given algorithm
421
422 Note that for lzma this uses an old version of the algorithm, not that
423 provided by xz.
424
425 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
426 directory to be previously set up, by calling PrepareOutputDir().
427
428 Args:
429 indata: Input data to decompress
430 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
431
432 Returns:
433 Compressed data
434 """
435 if algo == 'none':
436 return indata
Simon Glass37fdd142019-07-20 12:24:06 -0600437 if with_header:
438 data_len = struct.unpack('<I', indata[:4])[0]
439 indata = indata[4:4 + data_len]
Simon Glassdfd19012019-07-08 13:18:41 -0600440 fname = GetOutputFilename('%s.decomp.tmp' % algo)
441 with open(fname, 'wb') as fd:
442 fd.write(indata)
443 if algo == 'lz4':
444 data = Run('lz4', '-dc', fname, binary=True)
445 elif algo == 'lzma':
446 outfname = GetOutputFilename('%s.decomp.otmp' % algo)
447 Run('lzma_alone', 'd', fname, outfname)
448 data = ReadFile(outfname)
449 elif algo == 'gzip':
450 data = Run('gzip', '-cd', fname, binary=True)
451 else:
452 raise ValueError("Unknown algorithm '%s'" % algo)
453 return data
Simon Glass578d53e2019-07-08 13:18:51 -0600454
455CMD_CREATE, CMD_DELETE, CMD_ADD, CMD_REPLACE, CMD_EXTRACT = range(5)
456
457IFWITOOL_CMDS = {
458 CMD_CREATE: 'create',
459 CMD_DELETE: 'delete',
460 CMD_ADD: 'add',
461 CMD_REPLACE: 'replace',
462 CMD_EXTRACT: 'extract',
463 }
464
465def RunIfwiTool(ifwi_file, cmd, fname=None, subpart=None, entry_name=None):
466 """Run ifwitool with the given arguments:
467
468 Args:
469 ifwi_file: IFWI file to operation on
470 cmd: Command to execute (CMD_...)
471 fname: Filename of file to add/replace/extract/create (None for
472 CMD_DELETE)
473 subpart: Name of sub-partition to operation on (None for CMD_CREATE)
474 entry_name: Name of directory entry to operate on, or None if none
475 """
476 args = ['ifwitool', ifwi_file]
477 args.append(IFWITOOL_CMDS[cmd])
478 if fname:
479 args += ['-f', fname]
480 if subpart:
481 args += ['-n', subpart]
482 if entry_name:
483 args += ['-d', '-e', entry_name]
484 Run(*args)
Simon Glassb6dff4c2019-07-20 12:23:36 -0600485
486def ToHex(val):
487 """Convert an integer value (or None) to a string
488
489 Returns:
490 hex value, or 'None' if the value is None
491 """
492 return 'None' if val is None else '%#x' % val
493
494def ToHexSize(val):
495 """Return the size of an object in hex
496
497 Returns:
498 hex value of size, or 'None' if the value is None
499 """
500 return 'None' if val is None else '%#x' % len(val)