summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/.gitignore1
-rw-r--r--tools/Kconfig5
-rw-r--r--tools/Makefile5
-rw-r--r--tools/binman/binman.rst47
-rw-r--r--tools/binman/bintool.py17
-rw-r--r--tools/binman/bintool_test.py1
-rw-r--r--tools/binman/bintools.rst8
-rw-r--r--tools/binman/btool/cst.py37
-rw-r--r--tools/binman/entry.py5
-rw-r--r--tools/binman/etype/fmap.py4
-rw-r--r--tools/binman/etype/section.py8
-rw-r--r--tools/binman/ftest.py60
-rw-r--r--tools/buildman/boards.py4
-rw-r--r--tools/buildman/builder.py11
-rw-r--r--tools/buildman/builderthread.py31
-rw-r--r--tools/buildman/func_test.py16
-rw-r--r--tools/buildman/toolchain.py2
-rw-r--r--tools/image-host.c141
-rw-r--r--tools/mkimage.h4
-rw-r--r--tools/patman/patchstream.py2
-rw-r--r--tools/preload_check_sign.c160
-rwxr-xr-xtools/rmboard.py25
-rw-r--r--tools/u_boot_pylib/command.py161
-rw-r--r--tools/u_boot_pylib/gitutil.py58
-rw-r--r--tools/u_boot_pylib/tools.py2
25 files changed, 629 insertions, 186 deletions
diff --git a/tools/.gitignore b/tools/.gitignore
index 0108c567309..6a5c613f772 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -29,6 +29,7 @@
/mxsboot
/ncb
/prelink-riscv
+/preload_check_sign
/printinitialenv
/proftool
/relocate-rela
diff --git a/tools/Kconfig b/tools/Kconfig
index 01ff0fcf748..8e272ee99a8 100644
--- a/tools/Kconfig
+++ b/tools/Kconfig
@@ -9,6 +9,11 @@ config MKIMAGE_DTC_PATH
some cases the system dtc may not support all required features
and the path to a different version should be given here.
+config TOOLS_IMAGE_PRE_LOAD
+ def_bool y
+ help
+ Enable pre-load signature support in the tools builds.
+
config TOOLS_CRC16
def_bool y
help
diff --git a/tools/Makefile b/tools/Makefile
index 237fa900a24..e5f5eea47c7 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -66,6 +66,7 @@ mkenvimage-objs := mkenvimage.o os_support.o generated/lib/crc32.o
hostprogs-y += dumpimage mkimage
hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += fit_info fit_check_sign
hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += fdt_add_pubkey
+hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += preload_check_sign
ifneq ($(CONFIG_CMD_BOOTEFI_SELFTEST)$(CONFIG_FWU_MDATA_GPT_BLK),)
hostprogs-y += file2include
@@ -89,6 +90,8 @@ ECDSA_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := $(addprefix generated/lib/ecdsa/, ecdsa-
AES_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := $(addprefix generated/lib/aes/, \
aes-encrypt.o aes-decrypt.o)
+PRELOAD_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := generated/boot/image-pre-load.o
+
# Cryptographic helpers and image types that depend on openssl/libcrypto
LIBCRYPTO_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := \
generated/lib/fdt-libcrypto.o \
@@ -158,6 +161,7 @@ fit_info-objs := $(dumpimage-mkimage-objs) fit_info.o
fit_check_sign-objs := $(dumpimage-mkimage-objs) fit_check_sign.o
fdt_add_pubkey-objs := $(dumpimage-mkimage-objs) fdt_add_pubkey.o
file2include-objs := file2include.o
+preload_check_sign-objs := $(dumpimage-mkimage-objs) $(PRELOAD_OBJS-y) preload_check_sign.o
ifneq ($(CONFIG_MX23)$(CONFIG_MX28)$(CONFIG_TOOLS_LIBCRYPTO),)
# Add CFG_MXS into host CFLAGS, so we can check whether or not register
@@ -195,6 +199,7 @@ HOSTLDLIBS_dumpimage := $(HOSTLDLIBS_mkimage)
HOSTLDLIBS_fit_info := $(HOSTLDLIBS_mkimage)
HOSTLDLIBS_fit_check_sign := $(HOSTLDLIBS_mkimage)
HOSTLDLIBS_fdt_add_pubkey := $(HOSTLDLIBS_mkimage)
+HOSTLDLIBS_preload_check_sign := $(HOSTLDLIBS_mkimage)
hostprogs-$(CONFIG_EXYNOS5250) += mkexynosspl
hostprogs-$(CONFIG_EXYNOS5420) += mkexynosspl
diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst
index 990fc295770..84b1331df5c 100644
--- a/tools/binman/binman.rst
+++ b/tools/binman/binman.rst
@@ -823,24 +823,6 @@ multiple-images:
};
};
-end-at-4gb:
- For x86 machines the ROM offsets start just before 4GB and extend
- up so that the image finished at the 4GB boundary. This boolean
- option can be enabled to support this. The image size must be
- provided so that binman knows when the image should start. For an
- 8MB ROM, the offset of the first entry would be 0xfff80000 with
- this option, instead of 0 without this option.
-
-skip-at-start:
- This property specifies the entry offset of the first entry.
-
- For PowerPC mpc85xx based CPU, CONFIG_TEXT_BASE is the entry
- offset of the first entry. It can be 0xeff40000 or 0xfff40000 for
- nor flash boot, 0x201000 for sd boot etc.
-
- 'end-at-4gb' property is not applicable where CONFIG_TEXT_BASE +
- Image size != 4gb.
-
align-default:
Specifies the default alignment for entries in this section if they do
not specify an alignment. Note that this only applies to top-level entries
@@ -957,6 +939,35 @@ filename:
section in different image, since there is currently no way to share data
between images other than through files.
+end-at-4gb:
+ For x86 machines the ROM offsets start just before 4GB and extend
+ up so that the image finished at the 4GB boundary. This boolean
+ option can be enabled to support this. The image size must be
+ provided so that binman knows when the image should start. For an
+ 8MB ROM, the offset of the first entry would be 0xfff80000 with
+ this option, instead of 0 without this option.
+
+skip-at-start:
+ This property specifies the entry offset of the first entry in the section.
+ It is useful when the Binman image is written to a particular offset in the
+ media. It allows the offset of the first entry to be the media offset, even
+ though it is at the start of the image. It effectively creates a hole at the
+ start of the image, an implied, empty area.
+
+ For example, if the image is written to offset 4K on the media, set
+ skip-at-start to 0x1000. At runtime, the Binman image will assume that it
+ has be written at offset 4K and all symbols and offsets will take account of
+ that. The image-pos values will also be adjusted. The effect is similar to
+ adding an empty 4K region at the start, except that Binman does not actually
+ output it.
+
+ For PowerPC mpc85xx based CPU, CONFIG_TEXT_BASE is the entry
+ offset of the first entry. It can be 0xeff40000 or 0xfff40000 for
+ nor flash boot, 0x201000 for sd boot etc.
+
+ 'end-at-4gb' property is not applicable where CONFIG_TEXT_BASE +
+ Image size != 4gb.
+
Image Properties
----------------
diff --git a/tools/binman/bintool.py b/tools/binman/bintool.py
index 3c4ad1adbb9..81872db377f 100644
--- a/tools/binman/bintool.py
+++ b/tools/binman/bintool.py
@@ -328,7 +328,8 @@ class Bintool:
return result.stdout
@classmethod
- def build_from_git(cls, git_repo, make_targets, bintool_path, flags=None):
+ def build_from_git(cls, git_repo, make_targets, bintool_path,
+ flags=None, git_branch=None, make_path=None):
"""Build a bintool from a git repo
This clones the repo in a temporary directory, builds it with 'make',
@@ -341,6 +342,9 @@ class Bintool:
bintool_path (str): Relative path of the tool in the repo, after
build is complete
flags (list of str): Flags or variables to pass to make, or None
+ git_branch (str): Branch of git repo, or None to use the default
+ make_path (str): Relative path inside git repo containing the
+ Makefile, or None
Returns:
tuple:
@@ -350,10 +354,17 @@ class Bintool:
"""
tmpdir = tempfile.mkdtemp(prefix='binmanf.')
print(f"- clone git repo '{git_repo}' to '{tmpdir}'")
- tools.run('git', 'clone', '--depth', '1', git_repo, tmpdir)
+ if git_branch:
+ tools.run('git', 'clone', '--depth', '1', '--branch', git_branch,
+ git_repo, tmpdir)
+ else:
+ tools.run('git', 'clone', '--depth', '1', git_repo, tmpdir)
for target in make_targets:
print(f"- build target '{target}'")
- cmd = ['make', '-C', tmpdir, '-j', f'{multiprocessing.cpu_count()}',
+ makedir = tmpdir
+ if make_path:
+ makedir = os.path.join(tmpdir, make_path)
+ cmd = ['make', '-C', makedir, '-j', f'{multiprocessing.cpu_count()}',
target]
if flags:
cmd += flags
diff --git a/tools/binman/bintool_test.py b/tools/binman/bintool_test.py
index f9b16d4c73b..949d6f4c8a9 100644
--- a/tools/binman/bintool_test.py
+++ b/tools/binman/bintool_test.py
@@ -303,6 +303,7 @@ class TestBintool(unittest.TestCase):
# See Bintool.build_from_git()
tmpdir = cmd[2]
self.fname = os.path.join(tmpdir, 'pathname')
+ os.makedirs(os.path.dirname(tmpdir), exist_ok=True)
tools.write_file(self.fname, b'hello')
expected = b'this is a test'
diff --git a/tools/binman/bintools.rst b/tools/binman/bintools.rst
index cd05ad8cb26..9f6cab544a5 100644
--- a/tools/binman/bintools.rst
+++ b/tools/binman/bintools.rst
@@ -52,6 +52,14 @@ Bintool: cst: Image generation for U-Boot
This bintool supports running `cst` with some basic parameters as
needed by binman.
+cst (imx code signing tool) is used for sigining bootloader binaries for
+various i.MX SoCs.
+
+See `Code Signing Tool Users Guide`_ for more information.
+
+.. _`Code Signing Tool Users Guide`:
+ https://community.nxp.com/pwmxy87654/attachments/pwmxy87654/imx-processors/202591/1/CST_UG.pdf
+
Bintool: fdt_add_pubkey: Add public key to control dtb (spl or u-boot proper)
diff --git a/tools/binman/btool/cst.py b/tools/binman/btool/cst.py
index 30e78bdbbd9..8a3981adc89 100644
--- a/tools/binman/btool/cst.py
+++ b/tools/binman/btool/cst.py
@@ -12,6 +12,14 @@ class Bintoolcst(bintool.Bintool):
This bintool supports running `cst` with some basic parameters as
needed by binman.
+
+ cst (imx code signing tool) is used for sigining bootloader binaries for
+ various i.MX SoCs.
+
+ See `Code Signing Tool Users Guide`_ for more information.
+
+ .. _`Code Signing Tool Users Guide`:
+ https://community.nxp.com/pwmxy87654/attachments/pwmxy87654/imx-processors/202591/1/CST_UG.pdf
"""
def __init__(self, name):
super().__init__(name, 'Sign NXP i.MX image')
@@ -29,20 +37,17 @@ class Bintoolcst(bintool.Bintool):
return self.run_cmd(*args)
def fetch(self, method):
- """Fetch handler for cst
-
- This installs cst using the apt utility.
-
- Args:
- method (FETCH_...): Method to use
-
- Returns:
- True if the file was fetched and now installed, None if a method
- other than FETCH_BIN was requested
-
- Raises:
- Valuerror: Fetching could not be completed
- """
- if method != bintool.FETCH_BIN:
+ """Build cst from git"""
+ if method != bintool.FETCH_BUILD:
return None
- return self.apt_install('imx-code-signing-tool')
+
+ from platform import architecture
+ arch = 'linux64' if architecture()[0] == '64bit' else 'linux32'
+ result = self.build_from_git(
+ 'https://gitlab.apertis.org/pkg/imx-code-signing-tool',
+ ['all'],
+ f'code/obj.{arch}/cst',
+ flags=[f'OSTYPE={arch}', 'ENCRYPTION=yes'],
+ git_branch='debian/unstable',
+ make_path=f'code/obj.{arch}/')
+ return result
diff --git a/tools/binman/entry.py b/tools/binman/entry.py
index 68f8d62bba9..bdc60e47fca 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -392,9 +392,8 @@ class Entry(object):
"""Set the value of device-tree properties calculated by binman"""
state.SetInt(self._node, 'offset', self.offset)
state.SetInt(self._node, 'size', self.size)
- base = self.section.GetRootSkipAtStart() if self.section else 0
if self.image_pos is not None:
- state.SetInt(self._node, 'image-pos', self.image_pos - base)
+ state.SetInt(self._node, 'image-pos', self.image_pos)
if self.GetImage().allow_repack:
if self.orig_offset is not None:
state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
@@ -722,7 +721,7 @@ class Entry(object):
is_elf = self.GetDefaultFilename() == self.elf_fname
symbols_base = self.symbols_base
- if symbols_base is None and self.GetImage()._end_4gb:
+ if symbols_base is None and self.GetImage()._end_at_4gb:
symbols_base = 0
elf.LookupAndWriteSymbols(self.elf_fname, self, section.GetImage(),
diff --git a/tools/binman/etype/fmap.py b/tools/binman/etype/fmap.py
index 3669d91a0bc..35ca8490f79 100644
--- a/tools/binman/etype/fmap.py
+++ b/tools/binman/etype/fmap.py
@@ -65,7 +65,7 @@ class Entry_fmap(Entry):
if entry.image_pos is None:
pos = 0
else:
- pos = entry.image_pos - entry.GetRootSkipAtStart()
+ pos = entry.image_pos
# Drop @ symbols in name
name = entry.name.replace('@', '')
@@ -75,8 +75,6 @@ class Entry_fmap(Entry):
_AddEntries(areas, subentry)
else:
pos = entry.image_pos
- if pos is not None:
- pos -= entry.section.GetRootSkipAtStart()
areas.append(fmap_util.FmapArea(pos or 0, entry.size or 0,
entry.name, flags))
diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py
index f4f48c00e87..5e11cf58d28 100644
--- a/tools/binman/etype/section.py
+++ b/tools/binman/etype/section.py
@@ -165,7 +165,7 @@ class Entry_section(Entry):
self._pad_byte = 0
self._sort = False
self._skip_at_start = None
- self._end_4gb = False
+ self._end_at_4gb = False
self._ignore_missing = False
self._filename = None
self.align_default = 0
@@ -187,9 +187,9 @@ class Entry_section(Entry):
super().ReadNode()
self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
- self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
+ self._end_at_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
- if self._end_4gb:
+ if self._end_at_4gb:
if not self.size:
self.Raise("Section size must be provided when using end-at-4gb")
if self._skip_at_start is not None:
@@ -801,7 +801,7 @@ class Entry_section(Entry):
if not entry:
self._Raise("Unable to set offset/size for unknown entry '%s'" %
name)
- entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
+ entry.SetOffsetSize(offset + self._skip_at_start if offset is not None
else None, size)
def GetEntryOffsets(self):
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index d2802f67e2d..1a92a99b511 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -303,7 +303,7 @@ class TestFunctional(unittest.TestCase):
def setUp(self):
# Enable this to turn on debugging output
# tout.init(tout.DEBUG)
- command.test_result = None
+ command.TEST_RESULT = None
def tearDown(self):
"""Remove the temporary output directory"""
@@ -345,8 +345,9 @@ class TestFunctional(unittest.TestCase):
Arguments to pass, as a list of strings
kwargs: Arguments to pass to Command.RunPipe()
"""
- result = command.run_pipe([[self._binman_pathname] + list(args)],
- capture=True, capture_stderr=True, raise_on_error=False)
+ all_args = [self._binman_pathname] + list(args)
+ result = command.run_one(*all_args, capture=True, capture_stderr=True,
+ raise_on_error=False)
if result.return_code and kwargs.get('raise_on_error', True):
raise Exception("Error running '%s': %s" % (' '.join(args),
result.stdout + result.stderr))
@@ -762,6 +763,16 @@ class TestFunctional(unittest.TestCase):
return False
return True
+ def _CheckPreload(self, image, key, algo="sha256,rsa2048",
+ padding="pkcs-1.5"):
+ try:
+ tools.run('preload_check_sign', '-k', key, '-a', algo, '-p',
+ padding, '-f', image)
+ except:
+ self.fail('Expected image signed with a pre-load')
+ return False
+ return True
+
def testRun(self):
"""Test a basic run with valid args"""
result = self._RunBinman('-h')
@@ -780,11 +791,11 @@ class TestFunctional(unittest.TestCase):
def testFullHelpInternal(self):
"""Test that the full help is displayed with -H"""
try:
- command.test_result = command.CommandResult()
+ command.TEST_RESULT = command.CommandResult()
result = self._DoBinman('-H')
help_file = os.path.join(self._binman_dir, 'README.rst')
finally:
- command.test_result = None
+ command.TEST_RESULT = None
def testHelp(self):
"""Test that the basic help is displayed with -h"""
@@ -1872,7 +1883,7 @@ class TestFunctional(unittest.TestCase):
def testGbb(self):
"""Test for the Chromium OS Google Binary Block"""
- command.test_result = self._HandleGbbCommand
+ command.TEST_RESULT = self._HandleGbbCommand
entry_args = {
'keydir': 'devkeys',
'bmpblk': 'bmpblk.bin',
@@ -1941,7 +1952,7 @@ class TestFunctional(unittest.TestCase):
def testVblock(self):
"""Test for the Chromium OS Verified Boot Block"""
self._hash_data = False
- command.test_result = self._HandleVblockCommand
+ command.TEST_RESULT = self._HandleVblockCommand
entry_args = {
'keydir': 'devkeys',
}
@@ -1974,7 +1985,7 @@ class TestFunctional(unittest.TestCase):
def testVblockContent(self):
"""Test that the vblock signs the right data"""
self._hash_data = True
- command.test_result = self._HandleVblockCommand
+ command.TEST_RESULT = self._HandleVblockCommand
entry_args = {
'keydir': 'devkeys',
}
@@ -2297,16 +2308,17 @@ class TestFunctional(unittest.TestCase):
fhdr, fentries = fmap_util.DecodeFmap(data[32:])
self.assertEqual(0x100, fhdr.image_size)
+ base = (1 << 32) - 0x100
- self.assertEqual(0, fentries[0].offset)
+ self.assertEqual(base, fentries[0].offset)
self.assertEqual(4, fentries[0].size)
self.assertEqual(b'U_BOOT', fentries[0].name)
- self.assertEqual(4, fentries[1].offset)
+ self.assertEqual(base + 4, fentries[1].offset)
self.assertEqual(3, fentries[1].size)
self.assertEqual(b'INTEL_MRC', fentries[1].name)
- self.assertEqual(32, fentries[2].offset)
+ self.assertEqual(base + 32, fentries[2].offset)
self.assertEqual(fmap_util.FMAP_HEADER_LEN +
fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
self.assertEqual(b'FMAP', fentries[2].name)
@@ -2319,27 +2331,28 @@ class TestFunctional(unittest.TestCase):
fhdr, fentries = fmap_util.DecodeFmap(data[36:])
self.assertEqual(0x180, fhdr.image_size)
+ base = (1 << 32) - 0x180
expect_size = fmap_util.FMAP_HEADER_LEN + fmap_util.FMAP_AREA_LEN * 4
fiter = iter(fentries)
fentry = next(fiter)
self.assertEqual(b'U_BOOT', fentry.name)
- self.assertEqual(0, fentry.offset)
+ self.assertEqual(base, fentry.offset)
self.assertEqual(4, fentry.size)
fentry = next(fiter)
self.assertEqual(b'SECTION', fentry.name)
- self.assertEqual(4, fentry.offset)
+ self.assertEqual(base + 4, fentry.offset)
self.assertEqual(0x20 + expect_size, fentry.size)
fentry = next(fiter)
self.assertEqual(b'INTEL_MRC', fentry.name)
- self.assertEqual(4, fentry.offset)
+ self.assertEqual(base + 4, fentry.offset)
self.assertEqual(3, fentry.size)
fentry = next(fiter)
self.assertEqual(b'FMAP', fentry.name)
- self.assertEqual(36, fentry.offset)
+ self.assertEqual(base + 36, fentry.offset)
self.assertEqual(expect_size, fentry.size)
def testElf(self):
@@ -3535,8 +3548,8 @@ class TestFunctional(unittest.TestCase):
image = control.images['image']
entries = image.GetEntries()
desc = entries['intel-descriptor']
- self.assertEqual(0xff800000, desc.offset);
- self.assertEqual(0xff800000, desc.image_pos);
+ self.assertEqual(0xff800000, desc.offset)
+ self.assertEqual(0xff800000, desc.image_pos)
def testReplaceCbfs(self):
"""Test replacing a single file in CBFS without changing the size"""
@@ -3778,8 +3791,8 @@ class TestFunctional(unittest.TestCase):
image = control.images['image']
entries = image.GetEntries()
- expected_ptr = entries['intel-fit'].image_pos - (1 << 32)
- self.assertEqual(expected_ptr, ptr)
+ expected_ptr = entries['intel-fit'].image_pos #- (1 << 32)
+ self.assertEqual(expected_ptr, ptr + (1 << 32))
def testPackIntelFitMissing(self):
"""Test detection of a FIT pointer with not FIT region"""
@@ -4773,7 +4786,7 @@ class TestFunctional(unittest.TestCase):
entry = image.GetEntries()['fdtmap']
self.assertEqual(orig_entry.offset, entry.offset)
self.assertEqual(orig_entry.size, entry.size)
- self.assertEqual(16, entry.image_pos)
+ self.assertEqual((1 << 32) - 0x400 + 16, entry.image_pos)
u_boot = image.GetEntries()['section'].GetEntries()['u-boot']
@@ -5496,7 +5509,7 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
def testFitSubentryUsesBintool(self):
"""Test that binman FIT subentries can use bintools"""
- command.test_result = self._HandleGbbCommand
+ command.TEST_RESULT = self._HandleGbbCommand
entry_args = {
'keydir': 'devkeys',
'bmpblk': 'bmpblk.bin',
@@ -5781,9 +5794,14 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
data = self._DoReadFileDtb(
'230_pre_load.dts', entry_args=entry_args,
extra_indirs=[os.path.join(self._binman_dir, 'test')])[0]
+
+ image_fname = tools.get_output_filename('image.bin')
+ is_signed = self._CheckPreload(image_fname, self.TestFile("dev.key"))
+
self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)])
self.assertEqual(PRE_LOAD_VERSION, data[4:4 + len(PRE_LOAD_VERSION)])
self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8 + len(PRE_LOAD_HDR_SIZE)])
+ self.assertEqual(is_signed, True)
def testPreLoadNoKey(self):
"""Test an image with a pre-load heade0r with missing key"""
diff --git a/tools/buildman/boards.py b/tools/buildman/boards.py
index e7aa0d85a58..2fe43c3fc89 100644
--- a/tools/buildman/boards.py
+++ b/tools/buildman/boards.py
@@ -251,9 +251,9 @@ class KconfigScanner:
'-undef',
'-x', 'assembler-with-cpp',
defconfig]
- result = command.run_pipe([cmd], capture=True, capture_stderr=True)
+ stdout = command.output(*cmd, capture_stderr=True)
temp = tempfile.NamedTemporaryFile(prefix='buildman-')
- tools.write_file(temp.name, result.stdout, False)
+ tools.write_file(temp.name, stdout, False)
fname = temp.name
tout.info(f'Processing #include to produce {defconfig}')
else:
diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py
index 23b1016d0f9..4bea0a02b78 100644
--- a/tools/buildman/builder.py
+++ b/tools/buildman/builder.py
@@ -510,7 +510,7 @@ class Builder:
stage: Stage that we are at (mrproper, config, oldconfig, build)
cwd: Directory where make should be run
args: Arguments to pass to make
- kwargs: Arguments to pass to command.run_pipe()
+ kwargs: Arguments to pass to command.run_one()
"""
def check_output(stream, data):
@@ -531,11 +531,12 @@ class Builder:
return False
self._restarting_config = False
- self._terminated = False
+ self._terminated = False
cmd = [self.gnu_make] + list(args)
- result = command.run_pipe([cmd], capture=True, capture_stderr=True,
- cwd=cwd, raise_on_error=False, infile='/dev/null',
- output_func=check_output, **kwargs)
+ result = command.run_one(*cmd, capture=True, capture_stderr=True,
+ cwd=cwd, raise_on_error=False,
+ infile='/dev/null', output_func=check_output,
+ **kwargs)
if self._terminated:
# Try to be helpful
diff --git a/tools/buildman/builderthread.py b/tools/buildman/builderthread.py
index 78c95a67095..b8578d5b97b 100644
--- a/tools/buildman/builderthread.py
+++ b/tools/buildman/builderthread.py
@@ -179,13 +179,12 @@ class BuilderThread(threading.Thread):
cwd (str): Working directory to set, or None to leave it alone
*args (list of str): Arguments to pass to 'make'
**kwargs (dict): A list of keyword arguments to pass to
- command.run_pipe()
+ command.run_one()
Returns:
CommandResult object
"""
- return self.builder.do_make(commit, brd, stage, cwd, *args,
- **kwargs)
+ return self.builder.do_make(commit, brd, stage, cwd, *args, **kwargs)
def _build_args(self, brd, out_dir, out_rel_dir, work_dir, commit_upto):
"""Set up arguments to the args list based on the settings
@@ -588,9 +587,10 @@ class BuilderThread(threading.Thread):
lines = []
for fname in BASE_ELF_FILENAMES:
cmd = [f'{self.toolchain.cross}nm', '--size-sort', fname]
- nm_result = command.run_pipe([cmd], capture=True,
- capture_stderr=True, cwd=result.out_dir,
- raise_on_error=False, env=env)
+ nm_result = command.run_one(*cmd, capture=True,
+ capture_stderr=True,
+ cwd=result.out_dir,
+ raise_on_error=False, env=env)
if nm_result.stdout:
nm_fname = self.builder.get_func_sizes_file(
result.commit_upto, result.brd.target, fname)
@@ -598,9 +598,10 @@ class BuilderThread(threading.Thread):
print(nm_result.stdout, end=' ', file=outf)
cmd = [f'{self.toolchain.cross}objdump', '-h', fname]
- dump_result = command.run_pipe([cmd], capture=True,
- capture_stderr=True, cwd=result.out_dir,
- raise_on_error=False, env=env)
+ dump_result = command.run_one(*cmd, capture=True,
+ capture_stderr=True,
+ cwd=result.out_dir,
+ raise_on_error=False, env=env)
rodata_size = ''
if dump_result.stdout:
objdump = self.builder.get_objdump_file(result.commit_upto,
@@ -613,9 +614,10 @@ class BuilderThread(threading.Thread):
rodata_size = fields[2]
cmd = [f'{self.toolchain.cross}size', fname]
- size_result = command.run_pipe([cmd], capture=True,
- capture_stderr=True, cwd=result.out_dir,
- raise_on_error=False, env=env)
+ size_result = command.run_one(*cmd, capture=True,
+ capture_stderr=True,
+ cwd=result.out_dir,
+ raise_on_error=False, env=env)
if size_result.stdout:
lines.append(size_result.stdout.splitlines()[1] + ' ' +
rodata_size)
@@ -624,9 +626,8 @@ class BuilderThread(threading.Thread):
cmd = [f'{self.toolchain.cross}objcopy', '-O', 'binary',
'-j', '.rodata.default_environment',
'env/built-in.o', 'uboot.env']
- command.run_pipe([cmd], capture=True,
- capture_stderr=True, cwd=result.out_dir,
- raise_on_error=False, env=env)
+ command.run_one(*cmd, capture=True, capture_stderr=True,
+ cwd=result.out_dir, raise_on_error=False, env=env)
if not work_in_output:
copy_files(result.out_dir, build_dir, '', ['uboot.env'])
diff --git a/tools/buildman/func_test.py b/tools/buildman/func_test.py
index c7c4f506a65..d779040c74e 100644
--- a/tools/buildman/func_test.py
+++ b/tools/buildman/func_test.py
@@ -187,7 +187,7 @@ class TestFunctional(unittest.TestCase):
self._git_dir = os.path.join(self._base_dir, 'src')
self._buildman_pathname = sys.argv[0]
self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
- command.test_result = self._HandleCommand
+ command.TEST_RESULT = self._HandleCommand
bsettings.setup(None)
bsettings.add_file(settings_data)
self.setupToolchains()
@@ -232,8 +232,8 @@ class TestFunctional(unittest.TestCase):
self._toolchains.Add('gcc', test=False)
def _RunBuildman(self, *args):
- return command.run_pipe([[self._buildman_pathname] + list(args)],
- capture=True, capture_stderr=True)
+ all_args = [self._buildman_pathname] + list(args)
+ return command.run_one(*all_args, capture=True, capture_stderr=True)
def _RunControl(self, *args, brds=False, clean_dir=False,
test_thread_exceptions=False, get_builder=True):
@@ -266,7 +266,7 @@ class TestFunctional(unittest.TestCase):
return result
def testFullHelp(self):
- command.test_result = None
+ command.TEST_RESULT = None
result = self._RunBuildman('-H')
help_file = os.path.join(self._buildman_dir, 'README.rst')
# Remove possible extraneous strings
@@ -277,7 +277,7 @@ class TestFunctional(unittest.TestCase):
self.assertEqual(0, result.return_code)
def testHelp(self):
- command.test_result = None
+ command.TEST_RESULT = None
result = self._RunBuildman('-h')
help_file = os.path.join(self._buildman_dir, 'README.rst')
self.assertTrue(len(result.stdout) > 1000)
@@ -286,11 +286,11 @@ class TestFunctional(unittest.TestCase):
def testGitSetup(self):
"""Test gitutils.Setup(), from outside the module itself"""
- command.test_result = command.CommandResult(return_code=1)
+ command.TEST_RESULT = command.CommandResult(return_code=1)
gitutil.setup()
self.assertEqual(gitutil.use_no_decorate, False)
- command.test_result = command.CommandResult(return_code=0)
+ command.TEST_RESULT = command.CommandResult(return_code=0)
gitutil.setup()
self.assertEqual(gitutil.use_no_decorate, True)
@@ -445,7 +445,7 @@ class TestFunctional(unittest.TestCase):
stage: Stage that we are at (mrproper, config, build)
cwd: Directory where make should be run
args: Arguments to pass to make
- kwargs: Arguments to pass to command.run_pipe()
+ kwargs: Arguments to pass to command.run_one()
"""
self._make_calls += 1
out_dir = ''
diff --git a/tools/buildman/toolchain.py b/tools/buildman/toolchain.py
index 958f36f9f61..5d051e005da 100644
--- a/tools/buildman/toolchain.py
+++ b/tools/buildman/toolchain.py
@@ -100,7 +100,7 @@ class Toolchain:
else:
self.priority = priority
if test:
- result = command.run_pipe([cmd], capture=True, env=env,
+ result = command.run_one(*cmd, capture=True, env=env,
raise_on_error=False)
self.ok = result.return_code == 0
if verbose:
diff --git a/tools/image-host.c b/tools/image-host.c
index 14e8bd52a65..a9b86902763 100644
--- a/tools/image-host.c
+++ b/tools/image-host.c
@@ -19,6 +19,11 @@
#include <openssl/evp.h>
#endif
+#if CONFIG_IS_ENABLED(IMAGE_PRE_LOAD)
+#include <openssl/rsa.h>
+#include <openssl/err.h>
+#endif
+
/**
* fit_set_hash_value - set hash value in requested has node
* @fit: pointer to the FIT format image header
@@ -1410,3 +1415,139 @@ int fit_check_sign(const void *fit, const void *key,
return ret;
}
#endif
+
+#if CONFIG_IS_ENABLED(IMAGE_PRE_LOAD)
+/**
+ * rsa_verify_openssl() - Verify a signature against some data with openssl API
+ *
+ * Verify a RSA PKCS1.5/PSS signature against an expected hash.
+ *
+ * @info: Specifies the key and algorithms
+ * @region: Pointer to the input data
+ * @region_count: Number of region
+ * @sig: Signature
+ * @sig_len: Number of bytes in the signature
+ * Return: 0 if verified, -ve on error
+ */
+int rsa_verify_openssl(struct image_sign_info *info,
+ const struct image_region region[], int region_count,
+ uint8_t *sig, uint sig_len)
+{
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY_CTX *ckey = NULL;
+ EVP_MD_CTX *ctx = NULL;
+ int pad;
+ int size;
+ int i;
+ int ret = 0;
+
+ if (!info) {
+ fprintf(stderr, "No info provided\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!info->key) {
+ fprintf(stderr, "No key provided\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!info->checksum) {
+ fprintf(stderr, "No checksum information\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!info->padding) {
+ fprintf(stderr, "No padding information\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (region_count < 1) {
+ fprintf(stderr, "Invalid value for region_count: %d\n", region_count);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ pkey = (EVP_PKEY *)info->key;
+
+ ckey = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ckey) {
+ ret = -ENOMEM;
+ fprintf(stderr, "EVK key context setup failed: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto out;
+ }
+
+ size = EVP_PKEY_size(pkey);
+ if (size > sig_len) {
+ fprintf(stderr, "Invalid signature size (%d bytes)\n",
+ size);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ctx = EVP_MD_CTX_new();
+ if (!ctx) {
+ ret = -ENOMEM;
+ fprintf(stderr, "EVP context creation failed: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto out;
+ }
+ EVP_MD_CTX_init(ctx);
+
+ if (EVP_DigestVerifyInit(ctx, &ckey,
+ EVP_get_digestbyname(info->checksum->name),
+ NULL, pkey) <= 0) {
+ ret = -EINVAL;
+ fprintf(stderr, "Verifier setup failed: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto out;
+ }
+
+ if (!strcmp(info->padding->name, "pkcs-1.5")) {
+ pad = RSA_PKCS1_PADDING;
+ } else if (!strcmp(info->padding->name, "pss")) {
+ pad = RSA_PKCS1_PSS_PADDING;
+ } else {
+ ret = -ENOMSG;
+ fprintf(stderr, "Unsupported padding: %s\n",
+ info->padding->name);
+ goto out;
+ }
+
+ if (EVP_PKEY_CTX_set_rsa_padding(ckey, pad) <= 0) {
+ ret = -EINVAL;
+ fprintf(stderr, "padding setup has failed: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto out;
+ }
+
+ for (i=0 ; i < region_count ; ++i) {
+ if (EVP_DigestVerifyUpdate(ctx, region[i].data,
+ region[i].size) <= 0) {
+ ret = -EINVAL;
+ fprintf(stderr, "Hashing data failed: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto out;
+ }
+ }
+
+ if (EVP_DigestVerifyFinal(ctx, sig, sig_len) <= 0) {
+ ret = -EINVAL;
+ fprintf(stderr, "Verifying digest failed: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto out;
+ }
+out:
+ if (ctx)
+ EVP_MD_CTX_free(ctx);
+
+ if (ret)
+ fprintf(stderr, "Failed to verify signature\n");
+
+ return ret;
+}
+#endif
diff --git a/tools/mkimage.h b/tools/mkimage.h
index 15741f250fd..5d6bcc9301a 100644
--- a/tools/mkimage.h
+++ b/tools/mkimage.h
@@ -37,6 +37,10 @@ static inline void *map_sysmem(ulong paddr, unsigned long len)
return (void *)(uintptr_t)paddr;
}
+static inline void unmap_sysmem(const void *vaddr)
+{
+}
+
static inline ulong map_to_sysmem(const void *ptr)
{
return (ulong)(uintptr_t)ptr;
diff --git a/tools/patman/patchstream.py b/tools/patman/patchstream.py
index 08795c4a0a8..490d382045b 100644
--- a/tools/patman/patchstream.py
+++ b/tools/patman/patchstream.py
@@ -711,7 +711,7 @@ def get_list(commit_range, git_dir=None, count=None):
"""
params = gitutil.log_cmd(commit_range, reverse=True, count=count,
git_dir=git_dir)
- return command.run_pipe([params], capture=True).stdout
+ return command.run_one(*params, capture=True).stdout
def get_metadata_for_list(commit_range, git_dir=None, count=None,
series=None, allow_overwrite=False):
diff --git a/tools/preload_check_sign.c b/tools/preload_check_sign.c
new file mode 100644
index 00000000000..ebead459273
--- /dev/null
+++ b/tools/preload_check_sign.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Check a file including a preload header including a signature
+ *
+ * Copyright (c) 2025 Paul HENRYS <paul.henrys_ext@softathome.com>
+ *
+ * Binman makes it possible to generate a preload header signing part or the
+ * complete file. The tool preload_check_sign allows to verify and authenticate
+ * a file starting with a preload header.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <openssl/pem.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <image.h>
+
+extern void image_pre_load_sig_set_info(struct image_sig_info *info);
+extern int image_pre_load_sig(ulong addr);
+
+static void usage(char *cmdname)
+{
+ fprintf(stderr, "Usage: %s -f file -k PEM key file\n"
+ " -f ==> set file which should be checked\n"
+ " -k ==> PEM key file\n"
+ " -a ==> algo (default: sha256,rsa2048)\n"
+ " -p ==> padding (default: pkcs-1.5)\n"
+ " -h ==> help\n",
+ cmdname);
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv)
+{
+ int ret = 0;
+ char cmdname[256];
+ char *file = NULL;
+ char *keyfile = NULL;
+ int c;
+ FILE *fp = NULL;
+ FILE *fp_key = NULL;
+ size_t bytes;
+ long filesize;
+ void *buffer = NULL;
+ EVP_PKEY *pkey = NULL;
+ char *algo = "sha256,rsa2048";
+ char *padding = "pkcs-1.5";
+ struct image_sig_info info = {0};
+
+ strncpy(cmdname, *argv, sizeof(cmdname) - 1);
+ cmdname[sizeof(cmdname) - 1] = '\0';
+ while ((c = getopt(argc, argv, "f:k:a:p:h")) != -1)
+ switch (c) {
+ case 'f':
+ file = optarg;
+ break;
+ case 'k':
+ keyfile = optarg;
+ break;
+ case 'a':
+ algo = optarg;
+ break;
+ case 'p':
+ padding = optarg;
+ break;
+ default:
+ usage(cmdname);
+ break;
+ }
+
+ if (!file) {
+ fprintf(stderr, "%s: Missing file\n", *argv);
+ usage(*argv);
+ }
+
+ if (!keyfile) {
+ fprintf(stderr, "%s: Missing key file\n", *argv);
+ usage(*argv);
+ }
+
+ fp = fopen(file, "r");
+ if (!fp) {
+ fprintf(stderr, "Error opening file: %s\n", file);
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ fseek(fp, 0, SEEK_END);
+ filesize = ftell(fp);
+ rewind(fp);
+
+ buffer = malloc(filesize);
+ if (!buffer) {
+ fprintf(stderr, "Memory allocation failed");
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ bytes = fread(buffer, 1, filesize, fp);
+ if (bytes != filesize) {
+ fprintf(stderr, "Error reading file\n");
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ fp_key = fopen(keyfile, "r");
+ if (!fp_key) {
+ fprintf(stderr, "Error opening file: %s\n", keyfile);
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ /* Attempt to read the private key */
+ pkey = PEM_read_PrivateKey(fp_key, NULL, NULL, NULL);
+ if (!pkey) {
+ /* If private key reading fails, try reading as a public key */
+ fseek(fp_key, 0, SEEK_SET);
+ pkey = PEM_read_PUBKEY(fp_key, NULL, NULL, NULL);
+ }
+ if (!pkey) {
+ fprintf(stderr, "Unable to retrieve the public key: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ info.algo_name = algo;
+ info.padding_name = padding;
+ info.key = (uint8_t *)pkey;
+ info.mandatory = 1;
+ info.sig_size = EVP_PKEY_size(pkey);
+ if (info.sig_size < 0) {
+ fprintf(stderr, "Fail to retrieve the signature size: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ /* Compute signature information */
+ info.sig_info.name = info.algo_name;
+ info.sig_info.padding = image_get_padding_algo(info.padding_name);
+ info.sig_info.checksum = image_get_checksum_algo(info.sig_info.name);
+ info.sig_info.crypto = image_get_crypto_algo(info.sig_info.name);
+ info.sig_info.key = info.key;
+ info.sig_info.keylen = info.key_len;
+
+ /* Check the signature */
+ image_pre_load_sig_set_info(&info);
+ ret = image_pre_load_sig((ulong)buffer);
+out:
+ if (fp)
+ fclose(fp);
+ if (fp_key)
+ fclose(fp_key);
+ if (info.key)
+ EVP_PKEY_free(pkey);
+ free(buffer);
+
+ exit(ret);
+}
diff --git a/tools/rmboard.py b/tools/rmboard.py
index 0c56b149e0f..594fd89b8d7 100755
--- a/tools/rmboard.py
+++ b/tools/rmboard.py
@@ -43,18 +43,16 @@ def rm_kconfig_include(path):
Args:
path: Path to search for and remove
"""
- cmd = ['git', 'grep', path]
- stdout = command.run_pipe([cmd], capture=True, raise_on_error=False).stdout
+ stdout = command.output('git', 'grep', path, raise_on_error=False)
if not stdout:
return
fname = stdout.split(':')[0]
print("Fixing up '%s' to remove reference to '%s'" % (fname, path))
- cmd = ['sed', '-i', '\|%s|d' % path, fname]
- stdout = command.run_pipe([cmd], capture=True).stdout
+ stdout = command.run_one('sed', '-i', rf'\|{path}|d', fname,
+ capture=True).stdout
- cmd = ['git', 'add', fname]
- stdout = command.run_pipe([cmd], capture=True).stdout
+ stdout = command.output('git', 'add', fname)
def rm_board(board):
"""Create a commit which removes a single board
@@ -68,8 +66,7 @@ def rm_board(board):
"""
# Find all MAINTAINERS and Kconfig files which mention the board
- cmd = ['git', 'grep', '-l', board]
- stdout = command.run_pipe([cmd], capture=True).stdout
+ stdout = command.output('git', 'grep', '-l', board)
maintain = []
kconfig = []
for line in stdout.splitlines():
@@ -109,16 +106,14 @@ def rm_board(board):
# Search for Kconfig files in the resulting list. Remove any 'source' lines
# which reference Kconfig files we want to remove
for path in real:
- cmd = ['find', path]
- stdout = (command.run_pipe([cmd], capture=True, raise_on_error=False).
- stdout)
+ stdout = command.output('find', path, raise_on_error=False)
for fname in stdout.splitlines():
if fname.endswith('Kconfig'):
rm_kconfig_include(fname)
# Remove unwanted files
cmd = ['git', 'rm', '-r'] + real
- stdout = command.run_pipe([cmd], capture=True).stdout
+ stdout = command.output(*cmd, capture=True)
## Change the messages as needed
msg = '''arm: Remove %s board
@@ -131,13 +126,11 @@ Remove it.
msg += 'Patch-cc: %s\n' % name
# Create the commit
- cmd = ['git', 'commit', '-s', '-m', msg]
- stdout = command.run_pipe([cmd], capture=True).stdout
+ stdout = command.output('git', 'commit', '-s', '-m', msg)
# Check if the board is mentioned anywhere else. The user will need to deal
# with this
- cmd = ['git', 'grep', '-il', board]
- print(command.run_pipe([cmd], capture=True, raise_on_error=False).stdout)
+ print(command.output('git', 'grep', '-il', board, raise_on_error=False))
print(' '.join(cmd))
for board in sys.argv[1:]:
diff --git a/tools/u_boot_pylib/command.py b/tools/u_boot_pylib/command.py
index bbe95d86122..0e247355ef6 100644
--- a/tools/u_boot_pylib/command.py
+++ b/tools/u_boot_pylib/command.py
@@ -1,21 +1,44 @@
# SPDX-License-Identifier: GPL-2.0+
-# Copyright (c) 2011 The Chromium OS Authors.
-#
+"""
+Shell command ease-ups for Python
-import os
+Copyright (c) 2011 The Chromium OS Authors.
+"""
+
+import subprocess
from u_boot_pylib import cros_subprocess
-"""Shell command ease-ups for Python."""
+# This permits interception of RunPipe for test purposes. If it is set to
+# a function, then that function is called with the pipe list being
+# executed. Otherwise, it is assumed to be a CommandResult object, and is
+# returned as the result for every run_pipe() call.
+# When this value is None, commands are executed as normal.
+TEST_RESULT = None
+
+
+class CommandExc(Exception):
+ """Reports an exception to the caller"""
+ def __init__(self, msg, result):
+ """Set up a new exception object
+
+ Args:
+ result (CommandResult): Execution result so far
+ """
+ super().__init__(msg)
+ self.result = result
+
class CommandResult:
"""A class which captures the result of executing a command.
Members:
- stdout: stdout obtained from command, as a string
- stderr: stderr obtained from command, as a string
- return_code: Return code from command
- exception: Exception received, or None if all ok
+ stdout (bytes): stdout obtained from command, as a string
+ stderr (bytes): stderr obtained from command, as a string
+ combined (bytes): stdout and stderr interleaved
+ return_code (int): Return code from command
+ exception (Exception): Exception received, or None if all ok
+ output (str or None): Returns output as a single line if requested
"""
def __init__(self, stdout='', stderr='', combined='', return_code=0,
exception=None):
@@ -24,8 +47,16 @@ class CommandResult:
self.combined = combined
self.return_code = return_code
self.exception = exception
+ self.output = None
def to_output(self, binary):
+ """Converts binary output to its final form
+
+ Args:
+ binary (bool): True to report binary output, False to use strings
+ Returns:
+ self
+ """
if not binary:
self.stdout = self.stdout.decode('utf-8')
self.stderr = self.stderr.decode('utf-8')
@@ -33,49 +64,47 @@ class CommandResult:
return self
-# This permits interception of RunPipe for test purposes. If it is set to
-# a function, then that function is called with the pipe list being
-# executed. Otherwise, it is assumed to be a CommandResult object, and is
-# returned as the result for every run_pipe() call.
-# When this value is None, commands are executed as normal.
-test_result = None
-
-def run_pipe(pipe_list, infile=None, outfile=None,
- capture=False, capture_stderr=False, oneline=False,
- raise_on_error=True, cwd=None, binary=False,
- output_func=None, **kwargs):
+def run_pipe(pipe_list, infile=None, outfile=None, capture=False,
+ capture_stderr=False, oneline=False, raise_on_error=True, cwd=None,
+ binary=False, output_func=None, **kwargs):
"""
Perform a command pipeline, with optional input/output filenames.
Args:
- pipe_list: List of command lines to execute. Each command line is
- piped into the next, and is itself a list of strings. For
+ pipe_list (list of list): List of command lines to execute. Each command
+ line is piped into the next, and is itself a list of strings. For
example [ ['ls', '.git'] ['wc'] ] will pipe the output of
'ls .git' into 'wc'.
- infile: File to provide stdin to the pipeline
- outfile: File to store stdout
- capture: True to capture output
- capture_stderr: True to capture stderr
- oneline: True to strip newline chars from output
- output_func: Output function to call with each output fragment
- (if it returns True the function terminates)
- kwargs: Additional keyword arguments to cros_subprocess.Popen()
+ infile (str): File to provide stdin to the pipeline
+ outfile (str): File to store stdout
+ capture (bool): True to capture output
+ capture_stderr (bool): True to capture stderr
+ oneline (bool): True to strip newline chars from output
+ raise_on_error (bool): True to raise on an error, False to return it in
+ the CommandResult
+ cwd (str or None): Directory to run the command in
+ binary (bool): True to report binary output, False to use strings
+ output_func (function): Output function to call with each output
+ fragment (if it returns True the function terminates)
+ **kwargs: Additional keyword arguments to cros_subprocess.Popen()
Returns:
CommandResult object
+ Raises:
+ CommandExc if an exception happens
"""
- if test_result:
- if hasattr(test_result, '__call__'):
+ if TEST_RESULT:
+ if hasattr(TEST_RESULT, '__call__'):
# pylint: disable=E1102
- result = test_result(pipe_list=pipe_list)
+ result = TEST_RESULT(pipe_list=pipe_list)
if result:
return result
else:
- return test_result
+ return TEST_RESULT
# No result: fall through to normal processing
result = CommandResult(b'', b'', b'')
last_pipe = None
pipeline = list(pipe_list)
- user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list])
+ user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list])
kwargs['stdout'] = None
kwargs['stderr'] = None
while pipeline:
@@ -96,7 +125,8 @@ def run_pipe(pipe_list, infile=None, outfile=None,
except Exception as err:
result.exception = err
if raise_on_error:
- raise Exception("Error running '%s': %s" % (user_pipestr, str))
+ raise CommandExc(f"Error running '{user_pipestr}': {err}",
+ result) from err
result.return_code = 255
return result.to_output(binary)
@@ -107,31 +137,84 @@ def run_pipe(pipe_list, infile=None, outfile=None,
result.output = result.stdout.rstrip(b'\r\n')
result.return_code = last_pipe.wait()
if raise_on_error and result.return_code:
- raise Exception("Error running '%s'" % user_pipestr)
+ raise CommandExc(f"Error running '{user_pipestr}'", result)
return result.to_output(binary)
+
def output(*cmd, **kwargs):
+ """Run a command and return its output
+
+ Args:
+ *cmd (list of str): Command to run
+ **kwargs (dict of args): Extra arguments to pass in
+
+ Returns:
+ str: command output
+ """
kwargs['raise_on_error'] = kwargs.get('raise_on_error', True)
return run_pipe([cmd], capture=True, **kwargs).stdout
+
def output_one_line(*cmd, **kwargs):
"""Run a command and output it as a single-line string
- The command us expected to produce a single line of output
+ The command is expected to produce a single line of output
+
+ Args:
+ *cmd (list of str): Command to run
+ **kwargs (dict of args): Extra arguments to pass in
Returns:
- String containing output of command
+ str: output of command with all newlines removed
"""
raise_on_error = kwargs.pop('raise_on_error', True)
result = run_pipe([cmd], capture=True, oneline=True,
- raise_on_error=raise_on_error, **kwargs).stdout.strip()
+ raise_on_error=raise_on_error, **kwargs).stdout.strip()
return result
+
def run(*cmd, **kwargs):
+ """Run a command
+
+ Note that you must add 'capture' to kwargs to obtain non-empty output
+
+ Args:
+ *cmd (list of str): Command to run
+ **kwargs (dict of args): Extra arguments to pass in
+
+ Returns:
+ str: output of command
+ """
return run_pipe([cmd], **kwargs).stdout
+
+def run_one(*cmd, **kwargs):
+ """Run a single command
+
+ Note that you must add 'capture' to kwargs to obtain non-empty output
+
+ Args:
+ *cmd (list of str): Command to run
+ **kwargs (dict of args): Extra arguments to pass in
+
+ Returns:
+ CommandResult: output of command
+ """
+ return run_pipe([cmd], **kwargs)
+
+
def run_list(cmd):
+ """Run a command and return its output
+
+ Args:
+ cmd (list of str): Command to run
+
+ Returns:
+ str: output of command
+ """
return run_pipe([cmd], capture=True).stdout
+
def stop_all():
+ """Stop all subprocesses initiated with cros_subprocess"""
cros_subprocess.stay_alive = False
diff --git a/tools/u_boot_pylib/gitutil.py b/tools/u_boot_pylib/gitutil.py
index 10ea5ff39f5..6d6a7eedecc 100644
--- a/tools/u_boot_pylib/gitutil.py
+++ b/tools/u_boot_pylib/gitutil.py
@@ -65,9 +65,9 @@ def count_commits_to_branch(branch):
rev_range = '%s..%s' % (us, branch)
else:
rev_range = '@{upstream}..'
- pipe = [log_cmd(rev_range, oneline=True)]
- result = command.run_pipe(pipe, capture=True, capture_stderr=True,
- oneline=True, raise_on_error=False)
+ cmd = log_cmd(rev_range, oneline=True)
+ result = command.run_one(*cmd, capture=True, capture_stderr=True,
+ oneline=True, raise_on_error=False)
if result.return_code:
raise ValueError('Failed to determine upstream: %s' %
result.stderr.strip())
@@ -84,8 +84,7 @@ def name_revision(commit_hash):
Return:
Name of revision, if any, else None
"""
- pipe = ['git', 'name-rev', commit_hash]
- stdout = command.run_pipe([pipe], capture=True, oneline=True).stdout
+ stdout = command.output_one_line('git', 'name-rev', commit_hash)
# We expect a commit, a space, then a revision name
name = stdout.split(' ')[1].strip()
@@ -108,9 +107,9 @@ def guess_upstream(git_dir, branch):
Name of upstream branch (e.g. 'upstream/master') or None if none
Warning/error message, or None if none
"""
- pipe = [log_cmd(branch, git_dir=git_dir, oneline=True, count=100)]
- result = command.run_pipe(pipe, capture=True, capture_stderr=True,
- raise_on_error=False)
+ cmd = log_cmd(branch, git_dir=git_dir, oneline=True, count=100)
+ result = command.run_one(*cmd, capture=True, capture_stderr=True,
+ raise_on_error=False)
if result.return_code:
return None, "Branch '%s' not found" % branch
for line in result.stdout.splitlines()[1:]:
@@ -140,7 +139,7 @@ def get_upstream(git_dir, branch):
'branch.%s.remote' % branch)
merge = command.output_one_line('git', '--git-dir', git_dir, 'config',
'branch.%s.merge' % branch)
- except Exception:
+ except command.CommandExc:
upstream, msg = guess_upstream(git_dir, branch)
return upstream, msg
@@ -183,9 +182,9 @@ def count_commits_in_range(git_dir, range_expr):
Number of patches that exist in the supplied range or None if none
were found
"""
- pipe = [log_cmd(range_expr, git_dir=git_dir, oneline=True)]
- result = command.run_pipe(pipe, capture=True, capture_stderr=True,
- raise_on_error=False)
+ cmd = log_cmd(range_expr, git_dir=git_dir, oneline=True)
+ result = command.run_one(*cmd, capture=True, capture_stderr=True,
+ raise_on_error=False)
if result.return_code:
return None, "Range '%s' not found or is invalid" % range_expr
patch_count = len(result.stdout.splitlines())
@@ -250,9 +249,8 @@ def clone(git_dir, output_dir):
Args:
commit_hash: Commit hash to check out
"""
- pipe = ['git', 'clone', git_dir, '.']
- result = command.run_pipe([pipe], capture=True, cwd=output_dir,
- capture_stderr=True)
+ result = command.run_one('git', 'clone', git_dir, '.', capture=True,
+ cwd=output_dir, capture_stderr=True)
if result.return_code != 0:
raise OSError('git clone: %s' % result.stderr)
@@ -263,13 +261,13 @@ def fetch(git_dir=None, work_tree=None):
Args:
commit_hash: Commit hash to check out
"""
- pipe = ['git']
+ cmd = ['git']
if git_dir:
- pipe.extend(['--git-dir', git_dir])
+ cmd.extend(['--git-dir', git_dir])
if work_tree:
- pipe.extend(['--work-tree', work_tree])
- pipe.append('fetch')
- result = command.run_pipe([pipe], capture=True, capture_stderr=True)
+ cmd.extend(['--work-tree', work_tree])
+ cmd.append('fetch')
+ result = command.run_one(*cmd, capture=True, capture_stderr=True)
if result.return_code != 0:
raise OSError('git fetch: %s' % result.stderr)
@@ -283,9 +281,9 @@ def check_worktree_is_available(git_dir):
Returns:
True if git-worktree commands will work, False otherwise.
"""
- pipe = ['git', '--git-dir', git_dir, 'worktree', 'list']
- result = command.run_pipe([pipe], capture=True, capture_stderr=True,
- raise_on_error=False)
+ result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'list',
+ capture=True, capture_stderr=True,
+ raise_on_error=False)
return result.return_code == 0
@@ -298,11 +296,11 @@ def add_worktree(git_dir, output_dir, commit_hash=None):
commit_hash: Commit hash to checkout
"""
# We need to pass --detach to avoid creating a new branch
- pipe = ['git', '--git-dir', git_dir, 'worktree', 'add', '.', '--detach']
+ cmd = ['git', '--git-dir', git_dir, 'worktree', 'add', '.', '--detach']
if commit_hash:
- pipe.append(commit_hash)
- result = command.run_pipe([pipe], capture=True, cwd=output_dir,
- capture_stderr=True)
+ cmd.append(commit_hash)
+ result = command.run_one(*cmd, capture=True, cwd=output_dir,
+ capture_stderr=True)
if result.return_code != 0:
raise OSError('git worktree add: %s' % result.stderr)
@@ -313,8 +311,8 @@ def prune_worktrees(git_dir):
Args:
git_dir: The repository whose deleted worktrees should be pruned
"""
- pipe = ['git', '--git-dir', git_dir, 'worktree', 'prune']
- result = command.run_pipe([pipe], capture=True, capture_stderr=True)
+ result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'prune',
+ capture=True, capture_stderr=True)
if result.return_code != 0:
raise OSError('git worktree prune: %s' % result.stderr)
@@ -687,7 +685,7 @@ def setup():
if alias_fname:
settings.ReadGitAliases(alias_fname)
cmd = log_cmd(None, count=0)
- use_no_decorate = (command.run_pipe([cmd], raise_on_error=False)
+ use_no_decorate = (command.run_one(*cmd, raise_on_error=False)
.return_code == 0)
diff --git a/tools/u_boot_pylib/tools.py b/tools/u_boot_pylib/tools.py
index 0499a75526f..1afd289eadd 100644
--- a/tools/u_boot_pylib/tools.py
+++ b/tools/u_boot_pylib/tools.py
@@ -376,7 +376,7 @@ def run_result(name, *args, **kwargs):
args = tuple(extra_args) + args
name = os.path.expanduser(name) # Expand paths containing ~
all_args = (name,) + args
- result = command.run_pipe([all_args], capture=True, capture_stderr=True,
+ result = command.run_one(*all_args, capture=True, capture_stderr=True,
env=env, raise_on_error=False, binary=binary)
if result.return_code:
if raise_on_error: