Running 'west blobs fetch' does not verify the digest of downloaded files: 1. if the checksum of the previously downloaded file does match that in the blob metadata (status BLOB_PRESENT), do nothing 2. if the checksum of the previously downloaded file does not match that in the blob metadata (status BLOB_OUTDATED), download the "up to date" file 3. if the blob has not yet been downloaded (status BLOB_NOT_PRESENT), download it None of the 2) and 3) code paths will verify that the checksum of the file just downloaded actually matches the digest in the blob's metadata. In the event that the metadata of a module is incorrect, then the user will not notice anything, and may rely on an unexpected binary, e.g. a static library for a different architecture. According to the Binary Blobs documentation [1], the expected behavior is to check the blob digest after downloading. [1] Fetching blobs, Zephyr 3.6.0 (still applies to Zephyr 3.7.0rc3) docs.zephyrproject.org/3.6.0/contribute/bin_blobs.html#fetching-blobs Signed-off-by: Christophe Dufaza <chris@openmarl.org>
172 lines
6 KiB
Python
172 lines
6 KiB
Python
# Copyright (c) 2022 Nordic Semiconductor ASA
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
import textwrap
|
|
from urllib.parse import urlparse
|
|
|
|
from west import log
|
|
from west.commands import WestCommand
|
|
|
|
from zephyr_ext_common import ZEPHYR_BASE
|
|
|
|
sys.path.append(os.fspath(Path(__file__).parent.parent))
|
|
import zephyr_module
|
|
|
|
class Blobs(WestCommand):
|
|
|
|
DEFAULT_LIST_FMT = '{module} {status} {path} {type} {abspath}'
|
|
|
|
def __init__(self):
|
|
super().__init__(
|
|
'blobs',
|
|
# Keep this in sync with the string in west-commands.yml.
|
|
'work with binary blobs',
|
|
'Work with binary blobs',
|
|
accepts_unknown_args=False)
|
|
|
|
def do_add_parser(self, parser_adder):
|
|
parser = parser_adder.add_parser(
|
|
self.name,
|
|
help=self.help,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
description=self.description,
|
|
epilog=textwrap.dedent(f'''\
|
|
FORMAT STRINGS
|
|
--------------
|
|
|
|
Blobs are listed using a Python 3 format string. Arguments
|
|
to the format string are accessed by name.
|
|
|
|
The default format string is:
|
|
|
|
"{self.DEFAULT_LIST_FMT}"
|
|
|
|
The following arguments are available:
|
|
|
|
- module: name of the module that contains this blob
|
|
- abspath: blob absolute path
|
|
- status: short status (A: present, M: hash failure, D: not present)
|
|
- path: blob local path from <module>/zephyr/blobs/
|
|
- sha256: blob SHA256 hash in hex
|
|
- type: type of blob
|
|
- version: version string
|
|
- license_path: path to the license file for the blob
|
|
- uri: URI to the remote location of the blob
|
|
- description: blob text description
|
|
- doc-url: URL to the documentation for this blob
|
|
'''))
|
|
|
|
# Remember to update west-completion.bash if you add or remove
|
|
# flags
|
|
parser.add_argument('subcmd', nargs=1,
|
|
choices=['list', 'fetch', 'clean'],
|
|
help='sub-command to execute')
|
|
|
|
parser.add_argument('modules', metavar='MODULE', nargs='*',
|
|
help='''zephyr modules to operate on;
|
|
all modules will be used if not given''')
|
|
|
|
group = parser.add_argument_group('west blob list options')
|
|
group.add_argument('-f', '--format',
|
|
help='''format string to use to list each blob;
|
|
see FORMAT STRINGS below''')
|
|
|
|
return parser
|
|
|
|
def get_blobs(self, args):
|
|
blobs = []
|
|
modules = args.modules
|
|
all_modules = zephyr_module.parse_modules(ZEPHYR_BASE, self.manifest)
|
|
all_names = [m.meta.get('name', None) for m in all_modules]
|
|
|
|
unknown = set(modules) - set(all_names)
|
|
|
|
if len(unknown):
|
|
log.die(f'Unknown module(s): {unknown}')
|
|
|
|
for module in all_modules:
|
|
# Filter by module
|
|
module_name = module.meta.get('name', None)
|
|
if len(modules) and module_name not in modules:
|
|
continue
|
|
|
|
blobs += zephyr_module.process_blobs(module.project, module.meta)
|
|
|
|
return blobs
|
|
|
|
def list(self, args):
|
|
blobs = self.get_blobs(args)
|
|
fmt = args.format or self.DEFAULT_LIST_FMT
|
|
for blob in blobs:
|
|
log.inf(fmt.format(**blob))
|
|
|
|
def ensure_folder(self, path):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
def fetch_blob(self, url, path):
|
|
scheme = urlparse(url).scheme
|
|
log.dbg(f'Fetching {path} with {scheme}')
|
|
import fetchers
|
|
fetcher = fetchers.get_fetcher_cls(scheme)
|
|
|
|
log.dbg(f'Found fetcher: {fetcher}')
|
|
inst = fetcher()
|
|
self.ensure_folder(path)
|
|
inst.fetch(url, path)
|
|
|
|
# Compare the checksum of a file we've just downloaded
|
|
# to the digest in blob metadata, warn user if they differ.
|
|
def verify_blob(self, blob):
|
|
log.dbg('Verifying blob {module}: {abspath}'.format(**blob))
|
|
|
|
status = zephyr_module.get_blob_status(blob['abspath'], blob['sha256'])
|
|
if status == zephyr_module.BLOB_OUTDATED:
|
|
log.err(textwrap.dedent(
|
|
f'''\
|
|
The checksum of the downloaded file does not match that
|
|
in the blob metadata:
|
|
- if it is not certain that the download was successful,
|
|
try running 'west blobs fetch {blob['module']}'
|
|
to re-download the file
|
|
- if the error persists, please consider contacting
|
|
the maintainers of the module so that they can check
|
|
the corresponding blob metadata
|
|
|
|
Module: {blob['module']}
|
|
Blob: {blob['path']}
|
|
URL: {blob['url']}
|
|
Info: {blob['description']}'''))
|
|
|
|
def fetch(self, args):
|
|
blobs = self.get_blobs(args)
|
|
for blob in blobs:
|
|
if blob['status'] == zephyr_module.BLOB_PRESENT:
|
|
log.dbg('Blob {module}: {abspath} is up to date'.format(**blob))
|
|
continue
|
|
log.inf('Fetching blob {module}: {abspath}'.format(**blob))
|
|
self.fetch_blob(blob['url'], blob['abspath'])
|
|
self.verify_blob(blob)
|
|
|
|
def clean(self, args):
|
|
blobs = self.get_blobs(args)
|
|
for blob in blobs:
|
|
if blob['status'] == zephyr_module.BLOB_NOT_PRESENT:
|
|
log.dbg('Blob {module}: {abspath} not in filesystem'.format(**blob))
|
|
continue
|
|
log.inf('Deleting blob {module}: {status} {abspath}'.format(**blob))
|
|
blob['abspath'].unlink()
|
|
|
|
def do_run(self, args, _):
|
|
log.dbg(f'subcmd: \'{args.subcmd[0]}\' modules: {args.modules}')
|
|
|
|
subcmd = getattr(self, args.subcmd[0])
|
|
|
|
if args.subcmd[0] != 'list' and args.format is not None:
|
|
log.die(f'unexpected --format argument; this is a "west blobs list" option')
|
|
|
|
subcmd(args)
|