1 # SPDX-License-Identifier: GPL-2.0+
3 # Copyright (c) 2016 Google, Inc
6 from __future__ import print_function
17 # Output directly (generally this is temporary)
20 # True to keep the output directory around after exiting
21 preserve_outdir = False
23 # Path to the Chrome OS chroot, if we know it
26 # Search paths to use for Filename(), used to find files
29 tool_search_paths = []
31 # Tools and the packages that contain them, on debian
36 # List of paths to use when looking for an input file
39 def PrepareOutputDir(dirname, preserve=False):
40 """Select an output directory, ensuring it exists.
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.
47 dirname: a string, name of the output directory to use to store
48 intermediate and output files. If is None - create a temporary
50 preserve: a Boolean. If outdir above is None and preserve is False, the
51 created temporary directory will be destroyed on exit.
54 OSError: If it cannot create the output directory.
56 global outdir, preserve_outdir
58 preserve_outdir = dirname or preserve
61 if not os.path.isdir(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)
69 outdir = tempfile.mkdtemp(prefix='binman.')
70 tout.Debug("Using temporary directory '%s'" % outdir)
72 def _RemoveOutputDir():
76 tout.Debug("Deleted temporary directory '%s'" % outdir)
79 def FinaliseOutputDir():
80 global outdir, preserve_outdir
82 """Tidy up: delete output directory if temporary and not preserved."""
83 if outdir and not preserve_outdir:
86 def GetOutputFilename(fname):
87 """Return a filename within the output directory.
90 fname: Filename to use for new file
93 The full path of the filename, within the output directory
95 return os.path.join(outdir, fname)
97 def _FinaliseForTest():
98 """Remove the output directory (for use by tests)"""
104 def SetInputDirs(dirname):
105 """Add a list of input directories, where input files are kept.
108 dirname: a list of paths to input directories to use for obtaining
109 files needed by binman to place in the image.
114 tout.Debug("Using input directories %s" % indir)
116 def GetInputFilename(fname):
117 """Return a filename for use as input.
120 fname: Filename to use for new file
123 The full path of the filename, within the input directory
127 for dirname in indir:
128 pathname = os.path.join(dirname, fname)
129 if os.path.exists(pathname):
132 raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
133 (fname, ','.join(indir), os.getcwd()))
135 def GetInputFilenameGlob(pattern):
136 """Return a list of filenames for use as input.
139 pattern: Filename pattern to search for
142 A list of matching files in all input directories
145 return glob.glob(fname)
147 for dirname in indir:
148 pathname = os.path.join(dirname, pattern)
149 files += glob.glob(pathname)
152 def Align(pos, align):
155 pos = (pos + mask) & ~mask
158 def NotPowerOfTwo(num):
159 return num and (num & (num - 1))
161 def SetToolPaths(toolpaths):
162 """Set the path to search for tools
165 toolpaths: List of paths to search for tools executed by Run()
167 global tool_search_paths
169 tool_search_paths = toolpaths
171 def PathHasFile(path_spec, fname):
172 """Check if a given filename is in the PATH
175 path_spec: Value of PATH variable to check
176 fname: Filename to check
179 True if found, False if not
181 for dir in path_spec.split(':'):
182 if os.path.exists(os.path.join(dir, fname)):
186 def Run(name, *args, **kwargs):
187 """Run a tool with some arguments
189 This runs a 'tool', which is a program used by binman to process files and
190 perhaps produce some output. Tools can be located on the PATH or in a
194 name: Command name to run
195 args: Arguments to the tool
196 kwargs: Options to pass to command.run()
203 if tool_search_paths:
204 env = dict(os.environ)
205 env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
206 return command.Run(name, *args, capture=True,
207 capture_stderr=True, env=env, **kwargs)
209 if env and not PathHasFile(env['PATH'], name):
210 msg = "Please install tool '%s'" % name
211 package = packages.get(name)
213 msg += " (e.g. from package '%s')" % package
214 raise ValueError(msg)
218 """Resolve a file path to an absolute path.
220 If fname starts with ##/ and chroot is available, ##/ gets replaced with
221 the chroot path. If chroot is not available, this file name can not be
222 resolved, `None' is returned.
224 If fname is not prepended with the above prefix, and is not an existing
225 file, the actual file name is retrieved from the passed in string and the
226 search_paths directories (if any) are searched to for the file. If found -
227 the path to the found file is returned, `None' is returned otherwise.
230 fname: a string, the path to resolve.
233 Absolute path to the file or None if not found.
235 if fname.startswith('##/'):
237 fname = os.path.join(chroot_path, fname[3:])
241 # Search for a pathname that exists, and return it if found
242 if fname and not os.path.exists(fname):
243 for path in search_paths:
244 pathname = os.path.join(path, os.path.basename(fname))
245 if os.path.exists(pathname):
248 # If not found, just return the standard, unchanged path
251 def ReadFile(fname, binary=True):
252 """Read and return the contents of a file.
255 fname: path to filename to read, where ## signifiies the chroot.
258 data read from file, as a string.
260 with open(Filename(fname), binary and 'rb' or 'r') as fd:
262 #self._out.Info("Read file '%s' size %d (%#0x)" %
263 #(fname, len(data), len(data)))
266 def WriteFile(fname, data):
267 """Write data into a file.
270 fname: path to filename to write
271 data: data to write to file, as a string
273 #self._out.Info("Write file '%s' size %d (%#0x)" %
274 #(fname, len(data), len(data)))
275 with open(Filename(fname), 'wb') as fd:
278 def GetBytes(byte, size):
279 """Get a string of bytes of a given size
281 This handles the unfortunate different between Python 2 and Python 2.
284 byte: Numeric byte value to use
285 size: Size of bytes/string to return
288 A bytes type with 'byte' repeated 'size' times
290 if sys.version_info[0] >= 3:
291 data = bytes([byte]) * size
293 data = chr(byte) * size
297 """Make sure a value is a unicode string
299 This allows some amount of compatibility between Python 2 and Python3. For
300 the former, it returns a unicode object.
303 val: string or unicode object
306 unicode version of val
308 if sys.version_info[0] >= 3:
310 return val if isinstance(val, unicode) else val.decode('utf-8')
312 def FromUnicode(val):
313 """Make sure a value is a non-unicode string
315 This allows some amount of compatibility between Python 2 and Python3. For
316 the former, it converts a unicode object to a string.
319 val: string or unicode object
322 non-unicode version of val
324 if sys.version_info[0] >= 3:
326 return val if isinstance(val, str) else val.encode('utf-8')
329 """Convert a character to an ASCII value
331 This is useful because in Python 2 bytes is an alias for str, but in
332 Python 3 they are separate types. This function converts the argument to
333 an ASCII value in either case.
336 ch: A string (Python 2) or byte (Python 3) value
339 integer ASCII value for ch
341 return ord(ch) if type(ch) == str else ch
344 """Convert a byte to a character
346 This is useful because in Python 2 bytes is an alias for str, but in
347 Python 3 they are separate types. This function converts an ASCII value to
348 a value with the appropriate type in either case.
351 byte: A byte or str value
353 return chr(byte) if type(byte) != str else byte
355 def ToChars(byte_list):
356 """Convert a list of bytes to a str/bytes type
359 byte_list: List of ASCII values representing the string
362 string made by concatenating all the ASCII values
364 return ''.join([chr(byte) for byte in byte_list])
367 """Convert a str type into a bytes type
370 string: string to convert value
373 Python 3: A bytes type
374 Python 2: A string type
376 if sys.version_info[0] >= 3:
377 return string.encode('utf-8')
380 def Compress(indata, algo):
381 """Compress some data using a given algorithm
383 Note that for lzma this uses an old version of the algorithm, not that
386 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
387 directory to be previously set up, by calling PrepareOutputDir().
390 indata: Input data to compress
391 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
398 fname = GetOutputFilename('%s.comp.tmp' % algo)
399 WriteFile(fname, indata)
401 data = Run('lz4', '--no-frame-crc', '-c', fname, binary=True)
402 # cbfstool uses a very old version of lzma
404 outfname = GetOutputFilename('%s.comp.otmp' % algo)
405 Run('lzma_alone', 'e', fname, outfname, '-lc1', '-lp0', '-pb0', '-d8')
406 data = ReadFile(outfname)
408 data = Run('gzip', '-c', fname, binary=True)
410 raise ValueError("Unknown algorithm '%s'" % algo)
413 def Decompress(indata, algo):
414 """Decompress some data using a given algorithm
416 Note that for lzma this uses an old version of the algorithm, not that
419 This requires 'lz4' and 'lzma_alone' tools. It also requires an output
420 directory to be previously set up, by calling PrepareOutputDir().
423 indata: Input data to decompress
424 algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
431 fname = GetOutputFilename('%s.decomp.tmp' % algo)
432 with open(fname, 'wb') as fd:
435 data = Run('lz4', '-dc', fname, binary=True)
437 outfname = GetOutputFilename('%s.decomp.otmp' % algo)
438 Run('lzma_alone', 'd', fname, outfname)
439 data = ReadFile(outfname)
441 data = Run('gzip', '-cd', fname, binary=True)
443 raise ValueError("Unknown algorithm '%s'" % algo)
446 CMD_CREATE, CMD_DELETE, CMD_ADD, CMD_REPLACE, CMD_EXTRACT = range(5)
449 CMD_CREATE: 'create',
450 CMD_DELETE: 'delete',
452 CMD_REPLACE: 'replace',
453 CMD_EXTRACT: 'extract',
456 def RunIfwiTool(ifwi_file, cmd, fname=None, subpart=None, entry_name=None):
457 """Run ifwitool with the given arguments:
460 ifwi_file: IFWI file to operation on
461 cmd: Command to execute (CMD_...)
462 fname: Filename of file to add/replace/extract/create (None for
464 subpart: Name of sub-partition to operation on (None for CMD_CREATE)
465 entry_name: Name of directory entry to operate on, or None if none
467 args = ['ifwitool', ifwi_file]
468 args.append(IFWITOOL_CMDS[cmd])
470 args += ['-f', fname]
472 args += ['-n', subpart]
474 args += ['-d', '-e', entry_name]
478 """Convert an integer value (or None) to a string
481 hex value, or 'None' if the value is None
483 return 'None' if val is None else '%#x' % val
486 """Return the size of an object in hex
489 hex value of size, or 'None' if the value is None
491 return 'None' if val is None else '%#x' % len(val)