binman: Add a utility library for coreboot CBFS
[oweals/u-boot.git] / tools / binman / cbfs_util.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2019 Google LLC
3 # Written by Simon Glass <sjg@chromium.org>
4
5 """Support for coreboot's CBFS format
6
7 CBFS supports a header followed by a number of files, generally targeted at SPI
8 flash.
9
10 The format is somewhat defined by documentation in the coreboot tree although
11 it is necessary to rely on the C structures and source code (mostly cbfstool)
12 to fully understand it.
13
14 Currently supported: raw and stage types with compression
15 """
16
17 from __future__ import print_function
18
19 from collections import OrderedDict
20 import io
21 import struct
22 import sys
23
24 import command
25 import elf
26 import tools
27
28 # Set to True to enable printing output while working
29 DEBUG = False
30
31 # Set to True to enable output from running cbfstool for debugging
32 VERBOSE = False
33
34 # The master header, at the start of the CBFS
35 HEADER_FORMAT      = '>IIIIIIII'
36 HEADER_LEN         = 0x20
37 HEADER_MAGIC       = 0x4f524243
38 HEADER_VERSION1    = 0x31313131
39 HEADER_VERSION2    = 0x31313132
40
41 # The file header, at the start of each file in the CBFS
42 FILE_HEADER_FORMAT = b'>8sIIII'
43 FILE_HEADER_LEN    = 0x18
44 FILE_MAGIC         = b'LARCHIVE'
45 FILENAME_ALIGN     = 16  # Filename lengths are aligned to this
46
47 # A stage header containing information about 'stage' files
48 # Yes this is correct: this header is in litte-endian format
49 STAGE_FORMAT       = '<IQQII'
50 STAGE_LEN          = 0x1c
51
52 # An attribute describring the compression used in a file
53 ATTR_COMPRESSION_FORMAT = '>IIII'
54 ATTR_COMPRESSION_LEN = 0x10
55
56 # Attribute tags
57 # Depending on how the header was initialised, it may be backed with 0x00 or
58 # 0xff. Support both.
59 FILE_ATTR_TAG_UNUSED        = 0
60 FILE_ATTR_TAG_UNUSED2       = 0xffffffff
61 FILE_ATTR_TAG_COMPRESSION   = 0x42435a4c
62 FILE_ATTR_TAG_HASH          = 0x68736148
63 FILE_ATTR_TAG_POSITION      = 0x42435350  # PSCB
64 FILE_ATTR_TAG_ALIGNMENT     = 0x42434c41  # ALCB
65 FILE_ATTR_TAG_PADDING       = 0x47444150  # PDNG
66
67 # This is 'the size of bootblock reserved in firmware image (cbfs.txt)'
68 # Not much more info is available, but we set it to 4, due to this comment in
69 # cbfstool.c:
70 # This causes 4 bytes to be left out at the end of the image, for two reasons:
71 # 1. The cbfs master header pointer resides there
72 # 2. Ssme cbfs implementations assume that an image that resides below 4GB has
73 #    a bootblock and get confused when the end of the image is at 4GB == 0.
74 MIN_BOOTBLOCK_SIZE     = 4
75
76 # Files start aligned to this boundary in the CBFS
77 ENTRY_ALIGN    = 0x40
78
79 # CBFSs must declare an architecture since much of the logic is designed with
80 # x86 in mind. The effect of setting this value is not well documented, but in
81 # general x86 is used and this makes use of a boot block and an image that ends
82 # at the end of 32-bit address space.
83 ARCHITECTURE_UNKNOWN  = 0xffffffff
84 ARCHITECTURE_X86      = 0x00000001
85 ARCHITECTURE_ARM      = 0x00000010
86 ARCHITECTURE_AARCH64  = 0x0000aa64
87 ARCHITECTURE_MIPS     = 0x00000100
88 ARCHITECTURE_RISCV    = 0xc001d0de
89 ARCHITECTURE_PPC64    = 0x407570ff
90
91 ARCH_NAMES = {
92     ARCHITECTURE_UNKNOWN  : 'unknown',
93     ARCHITECTURE_X86      : 'x86',
94     ARCHITECTURE_ARM      : 'arm',
95     ARCHITECTURE_AARCH64  : 'arm64',
96     ARCHITECTURE_MIPS     : 'mips',
97     ARCHITECTURE_RISCV    : 'riscv',
98     ARCHITECTURE_PPC64    : 'ppc64',
99     }
100
101 # File types. Only supported ones are included here
102 TYPE_CBFSHEADER     = 0x02   # Master header, HEADER_FORMAT
103 TYPE_STAGE          = 0x10   # Stage, holding an executable, see STAGE_FORMAT
104 TYPE_RAW            = 0x50   # Raw file, possibly compressed
105
106 # Compression types
107 COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
108
109 COMPRESS_NAMES = {
110     COMPRESS_NONE : 'none',
111     COMPRESS_LZMA : 'lzma',
112     COMPRESS_LZ4  : 'lz4',
113     }
114
115 def find_arch(find_name):
116     """Look up an architecture name
117
118     Args:
119         find_name: Architecture name to find
120
121     Returns:
122         ARCHITECTURE_... value or None if not found
123     """
124     for arch, name in ARCH_NAMES.items():
125         if name == find_name:
126             return arch
127     return None
128
129 def find_compress(find_name):
130     """Look up a compression algorithm name
131
132     Args:
133         find_name: Compression algorithm name to find
134
135     Returns:
136         COMPRESS_... value or None if not found
137     """
138     for compress, name in COMPRESS_NAMES.items():
139         if name == find_name:
140             return compress
141     return None
142
143 def align_int(val, align):
144     """Align a value up to the given alignment
145
146     Args:
147         val: Integer value to align
148         align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
149
150     Returns:
151         integer value aligned to the required boundary, rounding up if necessary
152     """
153     return int((val + align - 1) / align) * align
154
155 def _pack_string(instr):
156     """Pack a string to the required aligned size by adding padding
157
158     Args:
159         instr: String to process
160
161     Returns:
162         String with required padding (at least one 0x00 byte) at the end
163     """
164     val = tools.ToBytes(instr)
165     pad_len = align_int(len(val) + 1, FILENAME_ALIGN)
166     return val + tools.GetBytes(0, pad_len - len(val))
167
168
169 class CbfsFile(object):
170     """Class to represent a single CBFS file
171
172     This is used to hold the information about a file, including its contents.
173     Use the get_data() method to obtain the raw output for writing to CBFS.
174
175     Properties:
176         name: Name of file
177         offset: Offset of file data from start of file header
178         data: Contents of file, uncompressed
179         data_len: Length of (possibly compressed) data in bytes
180         ftype: File type (TYPE_...)
181         compression: Compression type (COMPRESS_...)
182         memlen: Length of data in memory (typically the uncompressed length)
183         load: Load address in memory if known, else None
184         entry: Entry address in memory if known, else None. This is where
185             execution starts after the file is loaded
186         base_address: Base address to use for 'stage' files
187     """
188     def __init__(self, name, ftype, data, compress=COMPRESS_NONE):
189         self.name = name
190         self.offset = None
191         self.data = data
192         self.ftype = ftype
193         self.compress = compress
194         self.memlen = len(data)
195         self.load = None
196         self.entry = None
197         self.base_address = None
198         self.data_len = 0
199
200     def decompress(self):
201         """Handle decompressing data if necessary"""
202         indata = self.data
203         if self.compress == COMPRESS_LZ4:
204             data = tools.Decompress(indata, 'lz4')
205         elif self.compress == COMPRESS_LZMA:
206             data = tools.Decompress(indata, 'lzma')
207         else:
208             data = indata
209         self.memlen = len(data)
210         self.data = data
211         self.data_len = len(indata)
212
213     @classmethod
214     def stage(cls, base_address, name, data):
215         """Create a new stage file
216
217         Args:
218             base_address: Int base address for memory-mapping of ELF file
219             name: String file name to put in CBFS (does not need to correspond
220                 to the name that the file originally came from)
221             data: Contents of file
222
223         Returns:
224             CbfsFile object containing the file information
225         """
226         cfile = CbfsFile(name, TYPE_STAGE, data)
227         cfile.base_address = base_address
228         return cfile
229
230     @classmethod
231     def raw(cls, name, data, compress):
232         """Create a new raw file
233
234         Args:
235             name: String file name to put in CBFS (does not need to correspond
236                 to the name that the file originally came from)
237             data: Contents of file
238             compress: Compression algorithm to use (COMPRESS_...)
239
240         Returns:
241             CbfsFile object containing the file information
242         """
243         return CbfsFile(name, TYPE_RAW, data, compress)
244
245     def get_data(self):
246         """Obtain the contents of the file, in CBFS format
247
248         Returns:
249             bytes representing the contents of this file, packed and aligned
250                 for directly inserting into the final CBFS output
251         """
252         name = _pack_string(self.name)
253         hdr_len = len(name) + FILE_HEADER_LEN
254         attr_pos = 0
255         content = b''
256         attr = b''
257         data = self.data
258         if self.ftype == TYPE_STAGE:
259             elf_data = elf.DecodeElf(data, self.base_address)
260             content = struct.pack(STAGE_FORMAT, self.compress,
261                                   elf_data.entry, elf_data.load,
262                                   len(elf_data.data), elf_data.memsize)
263             data = elf_data.data
264         elif self.ftype == TYPE_RAW:
265             orig_data = data
266             if self.compress == COMPRESS_LZ4:
267                 data = tools.Compress(orig_data, 'lz4')
268             elif self.compress == COMPRESS_LZMA:
269                 data = tools.Compress(orig_data, 'lzma')
270             attr = struct.pack(ATTR_COMPRESSION_FORMAT,
271                                FILE_ATTR_TAG_COMPRESSION, ATTR_COMPRESSION_LEN,
272                                self.compress, len(orig_data))
273         else:
274             raise ValueError('Unknown type %#x when writing\n' % self.ftype)
275         if attr:
276             attr_pos = hdr_len
277             hdr_len += len(attr)
278         hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC,
279                           len(content) + len(data),
280                           self.ftype, attr_pos, hdr_len)
281         return hdr + name + attr + content + data
282
283
284 class CbfsWriter(object):
285     """Class to handle writing a Coreboot File System (CBFS)
286
287     Usage is something like:
288
289         cbw = CbfsWriter(size)
290         cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
291         ...
292         data = cbw.get_data()
293
294     Attributes:
295         _master_name: Name of the file containing the master header
296         _size: Size of the filesystem, in bytes
297         _files: Ordered list of files in the CBFS, each a CbfsFile
298         _arch: Architecture of the CBFS (ARCHITECTURE_...)
299         _bootblock_size: Size of the bootblock, typically at the end of the CBFS
300         _erase_byte: Byte to use for empty space in the CBFS
301         _align: Alignment to use for files, typically ENTRY_ALIGN
302         _base_address: Boot block offset in bytes from the start of CBFS.
303             Typically this is located at top of the CBFS. It is 0 when there is
304             no boot block
305         _header_offset: Offset of master header in bytes from start of CBFS
306         _contents_offset: Offset of first file header
307         _hdr_at_start: True if the master header is at the start of the CBFS,
308             instead of the end as normal for x86
309         _add_fileheader: True to add a fileheader around the master header
310     """
311     def __init__(self, size, arch=ARCHITECTURE_X86):
312         """Set up a new CBFS
313
314         This sets up all properties to default values. Files can be added using
315         add_file_raw(), etc.
316
317         Args:
318             size: Size of CBFS in bytes
319             arch: Architecture to declare for CBFS
320         """
321         self._master_name = 'cbfs master header'
322         self._size = size
323         self._files = OrderedDict()
324         self._arch = arch
325         self._bootblock_size = 0
326         self._erase_byte = 0xff
327         self._align = ENTRY_ALIGN
328         self._add_fileheader = False
329         if self._arch == ARCHITECTURE_X86:
330             # Allow 4 bytes for the header pointer. That holds the
331             # twos-compliment negative offset of the master header in bytes
332             # measured from one byte past the end of the CBFS
333             self._base_address = self._size - max(self._bootblock_size,
334                                                   MIN_BOOTBLOCK_SIZE)
335             self._header_offset = self._base_address - HEADER_LEN
336             self._contents_offset = 0
337             self._hdr_at_start = False
338         else:
339             # For non-x86, different rules apply
340             self._base_address = 0
341             self._header_offset = align_int(self._base_address +
342                                             self._bootblock_size, 4)
343             self._contents_offset = align_int(self._header_offset +
344                                               FILE_HEADER_LEN +
345                                               self._bootblock_size, self._align)
346             self._hdr_at_start = True
347
348     def _skip_to(self, fd, offset):
349         """Write out pad bytes until a given offset
350
351         Args:
352             fd: File objext to write to
353             offset: Offset to write to
354         """
355         if fd.tell() > offset:
356             raise ValueError('No space for data before offset %#x (current offset %#x)' %
357                              (offset, fd.tell()))
358         fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
359
360     def _align_to(self, fd, align):
361         """Write out pad bytes until a given alignment is reached
362
363         This only aligns if the resulting output would not reach the end of the
364         CBFS, since we want to leave the last 4 bytes for the master-header
365         pointer.
366
367         Args:
368             fd: File objext to write to
369             align: Alignment to require (e.g. 4 means pad to next 4-byte
370                 boundary)
371         """
372         offset = align_int(fd.tell(), align)
373         if offset < self._size:
374             self._skip_to(fd, offset)
375
376     def add_file_stage(self, name, data):
377         """Add a new stage file to the CBFS
378
379         Args:
380             name: String file name to put in CBFS (does not need to correspond
381                 to the name that the file originally came from)
382             data: Contents of file
383
384         Returns:
385             CbfsFile object created
386         """
387         cfile = CbfsFile.stage(self._base_address, name, data)
388         self._files[name] = cfile
389         return cfile
390
391     def add_file_raw(self, name, data, compress=COMPRESS_NONE):
392         """Create a new raw file
393
394         Args:
395             name: String file name to put in CBFS (does not need to correspond
396                 to the name that the file originally came from)
397             data: Contents of file
398             compress: Compression algorithm to use (COMPRESS_...)
399
400         Returns:
401             CbfsFile object created
402         """
403         cfile = CbfsFile.raw(name, data, compress)
404         self._files[name] = cfile
405         return cfile
406
407     def _write_header(self, fd, add_fileheader):
408         """Write out the master header to a CBFS
409
410         Args:
411             fd: File object
412             add_fileheader: True to place the master header in a file header
413                 record
414         """
415         if fd.tell() > self._header_offset:
416             raise ValueError('No space for header at offset %#x (current offset %#x)' %
417                              (self._header_offset, fd.tell()))
418         if not add_fileheader:
419             self._skip_to(fd, self._header_offset)
420         hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
421                           self._size, self._bootblock_size, self._align,
422                           self._contents_offset, self._arch, 0xffffffff)
423         if add_fileheader:
424             name = _pack_string(self._master_name)
425             fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
426                                  TYPE_CBFSHEADER, 0,
427                                  FILE_HEADER_LEN + len(name)))
428             fd.write(name)
429             self._header_offset = fd.tell()
430             fd.write(hdr)
431             self._align_to(fd, self._align)
432         else:
433             fd.write(hdr)
434
435     def get_data(self):
436         """Obtain the full contents of the CBFS
437
438         Thhis builds the CBFS with headers and all required files.
439
440         Returns:
441             'bytes' type containing the data
442         """
443         fd = io.BytesIO()
444
445         # THe header can go at the start in some cases
446         if self._hdr_at_start:
447             self._write_header(fd, add_fileheader=self._add_fileheader)
448         self._skip_to(fd, self._contents_offset)
449
450         # Write out each file
451         for cbf in self._files.values():
452             fd.write(cbf.get_data())
453             self._align_to(fd, self._align)
454         if not self._hdr_at_start:
455             self._write_header(fd, add_fileheader=self._add_fileheader)
456
457         # Pad to the end and write a pointer to the CBFS master header
458         self._skip_to(fd, self._base_address or self._size - 4)
459         rel_offset = self._header_offset - self._size
460         fd.write(struct.pack('<I', rel_offset & 0xffffffff))
461
462         return fd.getvalue()
463
464
465 class CbfsReader(object):
466     """Class to handle reading a Coreboot File System (CBFS)
467
468     Usage is something like:
469         cbfs = cbfs_util.CbfsReader(data)
470         cfile = cbfs.files['u-boot']
471         self.WriteFile('u-boot.bin', cfile.data)
472
473     Attributes:
474         files: Ordered list of CbfsFile objects
475         align: Alignment to use for files, typically ENTRT_ALIGN
476         stage_base_address: Base address to use when mapping ELF files into the
477             CBFS for TYPE_STAGE files. If this is larger than the code address
478             of the ELF file, then data at the start of the ELF file will not
479             appear in the CBFS. Currently there are no tests for behaviour as
480             documentation is sparse
481         magic: Integer magic number from master header (HEADER_MAGIC)
482         version: Version number of CBFS (HEADER_VERSION2)
483         rom_size: Size of CBFS
484         boot_block_size: Size of boot block
485         cbfs_offset: Offset of the first file in bytes from start of CBFS
486         arch: Architecture of CBFS file (ARCHITECTURE_...)
487     """
488     def __init__(self, data, read=True):
489         self.align = ENTRY_ALIGN
490         self.arch = None
491         self.boot_block_size = None
492         self.cbfs_offset = None
493         self.files = OrderedDict()
494         self.magic = None
495         self.rom_size = None
496         self.stage_base_address = 0
497         self.version = None
498         self.data = data
499         if read:
500             self.read()
501
502     def read(self):
503         """Read all the files in the CBFS and add them to self.files"""
504         with io.BytesIO(self.data) as fd:
505             # First, get the master header
506             if not self._find_and_read_header(fd, len(self.data)):
507                 raise ValueError('Cannot find master header')
508             fd.seek(self.cbfs_offset)
509
510             # Now read in the files one at a time
511             while True:
512                 cfile = self._read_next_file(fd)
513                 if cfile:
514                     self.files[cfile.name] = cfile
515                 elif cfile is False:
516                     break
517
518     def _find_and_read_header(self, fd, size):
519         """Find and read the master header in the CBFS
520
521         This looks at the pointer word at the very end of the CBFS. This is an
522         offset to the header relative to the size of the CBFS, which is assumed
523         to be known. Note that the offset is in *little endian* format.
524
525         Args:
526             fd: File to read from
527             size: Size of file
528
529         Returns:
530             True if header was found, False if not
531         """
532         orig_pos = fd.tell()
533         fd.seek(size - 4)
534         rel_offset, = struct.unpack('<I', fd.read(4))
535         pos = (size + rel_offset) & 0xffffffff
536         fd.seek(pos)
537         found = self._read_header(fd)
538         if not found:
539             print('Relative offset seems wrong, scanning whole image')
540             for pos in range(0, size - HEADER_LEN, 4):
541                 fd.seek(pos)
542                 found = self._read_header(fd)
543                 if found:
544                     break
545         fd.seek(orig_pos)
546         return found
547
548     def _read_next_file(self, fd):
549         """Read the next file from a CBFS
550
551         Args:
552             fd: File to read from
553
554         Returns:
555             CbfsFile object, if found
556             None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
557             False if at end of CBFS and reading should stop
558         """
559         file_pos = fd.tell()
560         data = fd.read(FILE_HEADER_LEN)
561         if len(data) < FILE_HEADER_LEN:
562             print('File header at %x ran out of data' % file_pos)
563             return False
564         magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
565                                                          data)
566         if magic != FILE_MAGIC:
567             return False
568         pos = fd.tell()
569         name = self._read_string(fd)
570         if name is None:
571             print('String at %x ran out of data' % pos)
572             return False
573
574         if DEBUG:
575             print('name', name)
576
577         # If there are attribute headers present, read those
578         compress = self._read_attr(fd, file_pos, attr, offset)
579         if compress is None:
580             return False
581
582         # Create the correct CbfsFile object depending on the type
583         cfile = None
584         fd.seek(file_pos + offset, io.SEEK_SET)
585         if ftype == TYPE_CBFSHEADER:
586             self._read_header(fd)
587         elif ftype == TYPE_STAGE:
588             data = fd.read(STAGE_LEN)
589             cfile = CbfsFile.stage(self.stage_base_address, name, b'')
590             (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
591              cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
592             cfile.data = fd.read(cfile.data_len)
593         elif ftype == TYPE_RAW:
594             data = fd.read(size)
595             cfile = CbfsFile.raw(name, data, compress)
596             cfile.decompress()
597             if DEBUG:
598                 print('data', data)
599         else:
600             raise ValueError('Unknown type %#x when reading\n' % ftype)
601         if cfile:
602             cfile.offset = offset
603
604         # Move past the padding to the start of a possible next file. If we are
605         # already at an alignment boundary, then there is no padding.
606         pad = (self.align - fd.tell() % self.align) % self.align
607         fd.seek(pad, io.SEEK_CUR)
608         return cfile
609
610     @classmethod
611     def _read_attr(cls, fd, file_pos, attr, offset):
612         """Read attributes from the file
613
614         CBFS files can have attributes which are things that cannot fit into the
615         header. The only attribute currently supported is compression.
616
617         Args:
618             fd: File to read from
619             file_pos: Position of file in fd
620             attr: Offset of attributes, 0 if none
621             offset: Offset of file data (used to indicate the end of the
622                                          attributes)
623
624         Returns:
625             Compression to use for the file (COMPRESS_...)
626         """
627         compress = COMPRESS_NONE
628         if not attr:
629             return compress
630         attr_size = offset - attr
631         fd.seek(file_pos + attr, io.SEEK_SET)
632         while attr_size:
633             pos = fd.tell()
634             hdr = fd.read(8)
635             if len(hdr) < 8:
636                 print('Attribute tag at %x ran out of data' % pos)
637                 return None
638             atag, alen = struct.unpack(">II", hdr)
639             data = hdr + fd.read(alen - 8)
640             if atag == FILE_ATTR_TAG_COMPRESSION:
641                 # We don't currently use this information
642                 atag, alen, compress, _decomp_size = struct.unpack(
643                     ATTR_COMPRESSION_FORMAT, data)
644             else:
645                 print('Unknown attribute tag %x' % atag)
646             attr_size -= len(data)
647         return compress
648
649     def _read_header(self, fd):
650         """Read the master header
651
652         Reads the header and stores the information obtained into the member
653         variables.
654
655         Args:
656             fd: File to read from
657
658         Returns:
659             True if header was read OK, False if it is truncated or has the
660                 wrong magic or version
661         """
662         pos = fd.tell()
663         data = fd.read(HEADER_LEN)
664         if len(data) < HEADER_LEN:
665             print('Header at %x ran out of data' % pos)
666             return False
667         (self.magic, self.version, self.rom_size, self.boot_block_size,
668          self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
669              HEADER_FORMAT, data)
670         return self.magic == HEADER_MAGIC and (
671             self.version == HEADER_VERSION1 or
672             self.version == HEADER_VERSION2)
673
674     @classmethod
675     def _read_string(cls, fd):
676         """Read a string from a file
677
678         This reads a string and aligns the data to the next alignment boundary
679
680         Args:
681             fd: File to read from
682
683         Returns:
684             string read ('str' type) encoded to UTF-8, or None if we ran out of
685                 data
686         """
687         val = b''
688         while True:
689             data = fd.read(FILENAME_ALIGN)
690             if len(data) < FILENAME_ALIGN:
691                 return None
692             pos = data.find(b'\0')
693             if pos == -1:
694                 val += data
695             else:
696                 val += data[:pos]
697                 break
698         return val.decode('utf-8')
699
700
701 def cbfstool(fname, *cbfs_args):
702     """Run cbfstool with provided arguments
703
704     If the tool fails then this function raises an exception and prints out the
705     output and stderr.
706
707     Args:
708         fname: Filename of CBFS
709         *cbfs_args: List of arguments to pass to cbfstool
710
711     Returns:
712         CommandResult object containing the results
713     """
714     args = ('cbfstool', fname) + cbfs_args
715     result = command.RunPipe([args], capture=not VERBOSE,
716                              capture_stderr=not VERBOSE, raise_on_error=False)
717     if result.return_code:
718         print(result.stderr, file=sys.stderr)
719         raise Exception("Failed to run (error %d): '%s'" %
720                         (result.return_code, ' '.join(args)))