From a23fa14fde9e054529d2763e070e9c98859b653b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Stehl=C3=A9?= Date: Thu, 10 Feb 2022 14:21:05 +0100 Subject: [PATCH 1/6] check-sr-results: identify EBBR.seq files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recognize the ACS-IR version and SystemReady IR version from the EBBR.seq file. This should allow to perform conditional checks, depending on those versions. Update the documentation and schema accordingly. Signed-off-by: Vincent Stehlé --- README.md | 8 ++++- check-sr-results.py | 46 ++++++++++++++++++++++++++++ check-sr-results.yaml | 10 ++++++ schemas/check-sr-results-schema.yaml | 18 +++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 39cec13..41bd083 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,19 @@ template]. ### Configuration file The `check-sr-results.yaml` configuration describes the verifications to -perform. +perform. It also contains some data to identify the ACS-IR that was used and to +deduce the certification version. YAML file format: ```{.yaml} --- check-sr-results-configuration: # Mandatory +ebbr_seq_files: # A list of known EBBR.seq sequence files + - sha256: 6b83dbfb... # sha256 of the sequence file to recognize + name: ACS-IR vX # Corresponding ACS-IR identifier + version: IR vY # Corresponding cert version + - ... tree: - file: optional: # If present, the file can be missing diff --git a/check-sr-results.py b/check-sr-results.py index c6a8670..d75e0f5 100755 --- a/check-sr-results.py +++ b/check-sr-results.py @@ -8,6 +8,7 @@ import os import curses from chardet.universaldetector import UniversalDetector import glob +import hashlib try: from packaging import version @@ -298,6 +299,47 @@ def is_glob(x): return x != e +# Compute the sha256 of a file. +# Return the hash or None. +def hash_file(filename): + logging.debug(f"Hash `{filename}'") + hm = 'sha256' + hl = hashlib.new(hm) + + with open(filename, 'rb') as f: + hl.update(f.read()) + + h = hl.hexdigest() + logging.debug(f"{hm} {h} {filename}") + return h + + +# Try to identify the EBBR.seq file using its sha256 in a list of known +# versions in conf. +# Return the identifier and SystemReady version or None, None. +def identify_ebbr_seq(conf, dirname): + logging.debug(f"Identify EBBR.seq in `{dirname}/'") + ebbr_seq = f"{dirname}/acs_results/sct_results/Sequence/EBBR.seq" + + if not os.path.isfile(ebbr_seq): + logging.warning( + f"{yellow}Missing{normal} `{ebbr_seq}' sequence file...") + return None, None + + h = hash_file(ebbr_seq) + + # Try to identify the seq file + for x in conf['ebbr_seq_files']: + if x['sha256'] == h: + logging.info( + f"""{green}Identified{normal} `{ebbr_seq}'""" + f""" as "{x['name']}" (SystemReady {x['version']}).""") + return x['name'], x['version'] + + logging.warning(f"{yellow}Could not identify{normal} `{ebbr_seq}'...") + return None, None + + # Recursively check a tree # We return a Stats object. # Files and directories names may be glob patterns. We need to handle the case @@ -357,6 +399,10 @@ if __name__ == '__main__': me = os.path.realpath(__file__) here = os.path.dirname(me) + + # Identify EBBR.seq to detect SystemReady version. conf = load_config(f'{here}/check-sr-results.yaml') + seq_id, ver = identify_ebbr_seq(conf, args.dir) + stats = check_tree(conf['tree'], args.dir) logging.info(stats) diff --git a/check-sr-results.yaml b/check-sr-results.yaml index af4f4ad..60d7951 100644 --- a/check-sr-results.yaml +++ b/check-sr-results.yaml @@ -6,6 +6,16 @@ --- check-sr-results-configuration: +ebbr_seq_files: + - sha256: 6b83dbfbd1f07fc61a918297f02f449591a72131b64ac746f969a4210f97aee8 + name: ACS-IR v21.05_0.8_BETA-0 + version: IR v1.0 + - sha256: c06684b3f8b35871e37b9447f609f9aab6070a7ca1c4ba63a52e029c018c9b73 + name: ACS-IR v21.07_0.9_BETA + version: IR v1.0 + - sha256: d66485b5e436409ef8c0667baf5250e784cbf292f2b9ef1b3893d474a0585fae + name: ACS-IR v21.09_1.0 + version: IR v1.1 tree: - file: acs-console.log must-contain: diff --git a/schemas/check-sr-results-schema.yaml b/schemas/check-sr-results-schema.yaml index f517f00..de0d365 100644 --- a/schemas/check-sr-results-schema.yaml +++ b/schemas/check-sr-results-schema.yaml @@ -65,9 +65,27 @@ type: object properties: check-sr-results-configuration: const: null + ebbr_seq_files: + type: array + items: + type: object + properties: + sha256: + type: string + pattern: '[0-9a-f]{64}' + name: + type: string + version: + type: string + pattern: 'IR v1.[01]' + required: + - sha256 + - name + additionalProperties: false tree: '$ref': '#/definitions/tree' required: - check-sr-results-configuration + - ebbr_seq_files - tree additionalProperties: false -- GitLab From eefa66e39807be25a956c585d912af9d59e678b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Stehl=C3=A9?= Date: Thu, 10 Feb 2022 15:37:17 +0100 Subject: [PATCH 2/6] check-sr-results: check archives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automatically check the integrity of archives we know about. Update the documentation accordingly. Signed-off-by: Vincent Stehlé --- README.md | 4 ++++ check-sr-results.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/README.md b/README.md index 41bd083..3b8f9e8 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A collection of scripts to help with SystemReady compliance. The `check-sr-results.py` SystemReady results checker needs the [chardet] python3 module. On some Linux distros it is available as `python3-chardet`. +The `tar` program must be installed. If you want to generate the pdf version of this documentation, you need to install [pandoc]. @@ -69,6 +70,9 @@ parent directory is scanned and all matching entries are considered. If the file or dir entry with the pattern is not marked `optional', there must be at least one match. +When a file is detected as a tar archive (according to its filename), its +integrity is automatically checked using `tar`. + The `schemas/check-sr-results-schema.yaml` file describes this configuration file format and can be used with the `validate.py` script to validate the configuration. This is run during [Sanity checks]. diff --git a/check-sr-results.py b/check-sr-results.py index d75e0f5..e15a253 100755 --- a/check-sr-results.py +++ b/check-sr-results.py @@ -9,6 +9,8 @@ import curses from chardet.universaldetector import UniversalDetector import glob import hashlib +import re +import subprocess try: from packaging import version @@ -212,6 +214,37 @@ def warn_if_contains(warn_if, filename): return stats +# subprocess.run() wrapper +def run(*args, **kwargs): + logging.debug(f"Running {args} {kwargs}") + cp = subprocess.run(*args, **kwargs) + logging.debug(f"{cp}") + return cp + + +# If a file is an archive we know of, check its integrity. +# We know about tar(-gz) for now. +# TODO! More archives types. +# We return a Stats object. +def maybe_check_archive(filename): + stats = Stats() + + if re.match(r'.*\.(tar|tar\.gz|tgz)$', filename): + logging.debug(f"Checking archive `{filename}'") + + cp = run( + f"tar tf {filename} >/dev/null", shell=True, + capture_output=True) + + if cp.returncode: + logging.error(f"{red}Bad archive{normal} `{filename}'") + stats.inc_error() + else: + stats.inc_pass() + + return stats + + # Check a file # We check if a file exists and is not empty. # The following properties in the yaml configuration can relax the check: @@ -219,6 +252,7 @@ def warn_if_contains(warn_if, filename): # - can-be-empty # If the file has a 'must-contain' property, we look for all signatures in its # contents in order. +# We perform some more checks on archives. # We return a Stats object. def check_file(conffile, filename): logging.debug(f"Check `{filename}'") @@ -240,6 +274,9 @@ def check_file(conffile, filename): stats.add( warn_if_contains(conffile['warn-if-contains'], filename)) + # Check archives integrity. + stats.add(maybe_check_archive(filename)) + elif 'can-be-empty' in conffile: logging.warning(f"`{filename}' {yellow}empty (allowed){normal}") stats.inc_warning() @@ -375,6 +412,18 @@ def check_tree(conftree, dirname): return stats +# Check that we have all we need. +def check_prerequisites(): + logging.debug('Checking prerequisites') + + # Check that we have tar. + cp = run('tar --version', shell=True, capture_output=True) + + if cp.returncode: + logging.error(f"{red}tar not found{normal}") + sys.exit(1) + + if __name__ == '__main__': parser = argparse.ArgumentParser( description='Perform a number of verifications on a SystemReady' @@ -397,6 +446,8 @@ if __name__ == '__main__': ln = logging.getLevelName(logging.ERROR) logging.addLevelName(logging.ERROR, f"{red}{ln}{normal}") + check_prerequisites() + me = os.path.realpath(__file__) here = os.path.dirname(me) -- GitLab From b70742ee3dd35327e0591962ec5d545261853d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Stehl=C3=A9?= Date: Thu, 10 Feb 2022 17:45:37 +0100 Subject: [PATCH 3/6] check-sr-results: new --detect-file-encoding-limit option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `--detect-file-encoding-limit' command line option to allow setting the file encoding detection limit. Signed-off-by: Vincent Stehlé --- check-sr-results.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/check-sr-results.py b/check-sr-results.py index e15a253..77bac4e 100755 --- a/check-sr-results.py +++ b/check-sr-results.py @@ -42,6 +42,10 @@ if os.isatty(sys.stdout.fileno()): except Exception: pass +# Maximum number of lines to examine for file encoding detection. +# This will be set by the command line argument parser. +detect_file_encoding_limit = None + # Compute the plural of a word. def maybe_plural(n, word): @@ -132,7 +136,7 @@ def detect_file_encoding(filename): enc = detector.result['encoding'] break - if i > 999: + if i > detect_file_encoding_limit: logging.debug('Giving up') break @@ -430,11 +434,15 @@ if __name__ == '__main__': ' results tree.', epilog='We expect the tree to be layout as described in the' ' SystemReady template' - ' (https://gitlab.arm.com/systemready/systemready-template).') + ' (https://gitlab.arm.com/systemready/systemready-template).', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( '--debug', action='store_true', help='Turn on debug messages') parser.add_argument( '--dir', help='Specify directory to check', default='.') + parser.add_argument( + '--detect-file-encoding-limit', type=int, default=999, + help='Specify file encoding detection limit, in number of lines') args = parser.parse_args() logging.basicConfig( @@ -446,6 +454,8 @@ if __name__ == '__main__': ln = logging.getLevelName(logging.ERROR) logging.addLevelName(logging.ERROR, f"{red}{ln}{normal}") + detect_file_encoding_limit = args.detect_file_encoding_limit + check_prerequisites() me = os.path.realpath(__file__) -- GitLab From 961a33dd0279b2de6b3db8ce7ac85f66aeb08531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Stehl=C3=A9?= Date: Thu, 17 Feb 2022 17:33:52 +0100 Subject: [PATCH 4/6] check-sr-results: reduce some warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not warn anymore on: - missing optional files or dirs - empty files or dirs, which are allowed to be empty Signed-off-by: Vincent Stehlé --- check-sr-results.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/check-sr-results.py b/check-sr-results.py index 77bac4e..a2f9582 100755 --- a/check-sr-results.py +++ b/check-sr-results.py @@ -282,14 +282,12 @@ def check_file(conffile, filename): stats.add(maybe_check_archive(filename)) elif 'can-be-empty' in conffile: - logging.warning(f"`{filename}' {yellow}empty (allowed){normal}") - stats.inc_warning() + logging.debug(f"`{filename}' {yellow}empty (allowed){normal}") else: logging.error(f"`{filename}' {red}empty{normal}") stats.inc_error() elif 'optional' in conffile: - logging.warning(f"`{filename}' {yellow}missing (optional){normal}") - stats.inc_warning() + logging.debug(f"`{filename}' {yellow}missing (optional){normal}") else: logging.error(f"`{filename}' {red}missing{normal}") stats.inc_error() @@ -319,14 +317,12 @@ def check_dir(confdir, dirname): if 'tree' in confdir: stats.add(check_tree(confdir['tree'], dirname)) elif 'can-be-empty' in confdir: - logging.warning(f"`{dirname}/' {yellow}empty (allowed){normal}") - stats.inc_warning() + logging.debug(f"`{dirname}/' {yellow}empty (allowed){normal}") else: logging.error(f"`{dirname}/' {red}empty{normal}") stats.inc_error() elif 'optional' in confdir: - logging.warning(f"`{dirname}/' {yellow}missing (optional){normal}") - stats.inc_warning() + logging.debug(f"`{dirname}/' {yellow}missing (optional){normal}") else: logging.error(f"`{dirname}/' {red}missing{normal}") stats.inc_error() -- GitLab From d8feffc0cd4681da3ed3aae5f59cb2574b416d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Stehl=C3=A9?= Date: Thu, 17 Feb 2022 15:33:00 +0100 Subject: [PATCH 5/6] check-sr-results: add overlays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a mechanism to selectively overlay bits of the configuration depending on the ACS version. Use that mechanism right away, to express some properties, which depend on the actual ACS-IR version. Signed-off-by: Vincent Stehlé --- README.md | 40 ++++++++++++---- check-sr-results.py | 70 ++++++++++++++++++++++++++++ check-sr-results.yaml | 46 ++++++++++++++---- schemas/check-sr-results-schema.yaml | 17 +++++++ 4 files changed, 156 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3b8f9e8..948a441 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,31 @@ The `check-sr-results.yaml` configuration describes the verifications to perform. It also contains some data to identify the ACS-IR that was used and to deduce the certification version. -YAML file format: +The `schemas/check-sr-results-schema.yaml` file describes this configuration +file format and can be used with the `validate.py` script to validate the +configuration. This is run during [Sanity checks]. + +The YAML configuration file starts with: ```{.yaml} --- check-sr-results-configuration: # Mandatory -ebbr_seq_files: # A list of known EBBR.seq sequence files - - sha256: 6b83dbfb... # sha256 of the sequence file to recognize - name: ACS-IR vX # Corresponding ACS-IR identifier - version: IR vY # Corresponding cert version +``` + +A section contains data, which allow to determine the ACS-IR version and IR +certification version from the EBBR.seq sequence file hash: + +```{.yaml} +ebbr_seq_files: # A list of known EBBR.seq sequence files + - sha256: 6b83dbfb... # sha256 of the sequence file to recognize + name: ACS-IR vX # Corresponding ACS-IR identifier + version: IR vY # Corresponding cert version - ... +``` + +The main section describes the file and dirs tree, with checks: + +```{.yaml} tree: - file: optional: # If present, the file can be missing @@ -73,9 +88,18 @@ least one match. When a file is detected as a tar archive (according to its filename), its integrity is automatically checked using `tar`. -The `schemas/check-sr-results-schema.yaml` file describes this configuration -file format and can be used with the `validate.py` script to validate the -configuration. This is run during [Sanity checks]. +The overlays section describes trees definitions, which can be overlayed to the +main tree section, depending on the detected ACS-IR version: + +```{.yaml} +overlays: + - ebbr_seq_files: [, ...] + tree: # If the detected seq file id matches, this tree + ... # will be overlayed. +``` + +All keys are overwritten violently except `tree`, which is overlayed +recursively. ## SystemReady results formatter diff --git a/check-sr-results.py b/check-sr-results.py index a2f9582..dc881bb 100755 --- a/check-sr-results.py +++ b/check-sr-results.py @@ -424,6 +424,72 @@ def check_prerequisites(): sys.exit(1) +# Overlay the src file over the dst file, in-place. +def overlay_file(src, dst): + logging.debug(f"Overlay file {src['file']}") + + for k, v in src.items(): + logging.debug(f"Overlay {k}") + dst[k] = v + + +# Overlay the src dir over the dst dir, in-place. +def overlay_dir(src, dst): + logging.debug(f"Overlay dir {src['dir']}") + + for k, v in src.items(): + logging.debug(f"Overlay {k}") + + # We have a special case when "merging" tree. + if k == 'tree' and 'tree' in dst: + overlay_tree(src['tree'], dst['tree']) + continue + + dst[k] = v + + +# Overlay the src tree over the dst tree, in-place. +def overlay_tree(src, dst): + # Prepare two LUTs. + files = {} + dirs = {} + + for x in dst: + if 'file' in x: + files[x['file']] = x + elif 'dir' in x: + dirs[x['dir']] = x + else: + raise + + # Overlay each entry. + for x in src: + if 'file' in x: + if x['file'] in files: + overlay_file(x, files[x['file']]) + else: + logging.debug(f"Adding file {x['file']}") + dst.append(x) + elif 'dir' in x: + if x['dir'] in dirs: + overlay_dir(x, dirs[x['dir']]) + else: + logging.debug(f"Adding dir {x['dir']}") + dst.append(x) + else: + raise + + +# Apply all the overlays matching the seq_id to the main tree. +def apply_overlays(conf, seq_id): + for i, o in enumerate(conf['overlays']): + if seq_id not in o['ebbr_seq_files']: + continue + + logging.debug(f"Applying overlay {i}") + overlay_tree(o['tree'], conf['tree']) + + if __name__ == '__main__': parser = argparse.ArgumentParser( description='Perform a number of verifications on a SystemReady' @@ -461,5 +527,9 @@ if __name__ == '__main__': conf = load_config(f'{here}/check-sr-results.yaml') seq_id, ver = identify_ebbr_seq(conf, args.dir) + if 'overlays' in conf: + apply_overlays(conf, seq_id) + del conf['overlays'] + stats = check_tree(conf['tree'], args.dir) logging.info(stats) diff --git a/check-sr-results.yaml b/check-sr-results.yaml index 60d7951..6f2ef65 100644 --- a/check-sr-results.yaml +++ b/check-sr-results.yaml @@ -16,6 +16,8 @@ ebbr_seq_files: - sha256: d66485b5e436409ef8c0667baf5250e784cbf292f2b9ef1b3893d474a0585fae name: ACS-IR v21.09_1.0 version: IR v1.1 + +# The following tree applies to all ACS-IR versions. tree: - file: acs-console.log must-contain: @@ -47,9 +49,6 @@ tree: - Ignored by group - Warning by group - dir: app_output - # This directory was not present when using ACS-IR BETA 0 so make it - # optional. - optional: tree: - file: CapsuleApp_ESRT_table_info.log must-contain: @@ -91,9 +90,6 @@ tree: - dir: Sequence tree: - file: EBBR_manual.seq - # ACS-IR BETA 0 only has EBBR.seq, not EBBR_manual.seq so make - # it optional for now. - optional: - file: EBBR.seq must-contain: - '[Test Case]' @@ -117,9 +113,6 @@ tree: - file: acpiview.log - file: acpiview_r.log - file: bcfg.log - # This file was not present when using ACS-IR BETA 0 so make it - # optional. - optional: - file: devices.log must-contain: - Device Name @@ -220,3 +213,38 @@ tree: must-contain: - General information - Test Logs collected + +overlays: + # A few files were not present in ACS-IR v21.05_0.8_BETA-0; make them + # optional. + - ebbr_seq_files: [ACS-IR v21.05_0.8_BETA-0] + tree: + - dir: acs_results + tree: + - dir: uefi_dump + tree: + - file: bcfg.log + optional: + - dir: sct_results + tree: + - dir: Sequence + tree: + - file: EBBR_manual.seq + optional: + + # A few files were only present in ACS-IR v21.05_0.8_BETA-0; make them + # optional for the subsequent ACS-IR versions. + - ebbr_seq_files: [ACS-IR v21.07_0.9_BETA, ACS-IR v21.09_1.0] + tree: + - dir: acs_results + tree: + - dir: uefi_dump + tree: + - file: acpiview_l.log + optional: + - file: acpiview.log + optional: + - file: acpiview_r.log + optional: + - file: smbiosview.log + optional: diff --git a/schemas/check-sr-results-schema.yaml b/schemas/check-sr-results-schema.yaml index de0d365..f78b865 100644 --- a/schemas/check-sr-results-schema.yaml +++ b/schemas/check-sr-results-schema.yaml @@ -84,8 +84,25 @@ properties: additionalProperties: false tree: '$ref': '#/definitions/tree' + overlays: + type: array + items: + type: object + properties: + ebbr_seq_files: + type: array + minItems: 1 + items: + type: string + tree: + '$ref': '#/definitions/tree' + required: + - ebbr_seq_files + - tree + additionalProperties: false required: - check-sr-results-configuration - ebbr_seq_files - tree + # overlays is optional additionalProperties: false -- GitLab From 53ba770ec9d62b778b5f4380117c12061156a787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Stehl=C3=A9?= Date: Thu, 17 Feb 2022 17:09:36 +0100 Subject: [PATCH 6/6] check-sr-results: option to dump config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is useful to debug how overlays are applied. Signed-off-by: Vincent Stehlé --- check-sr-results.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/check-sr-results.py b/check-sr-results.py index dc881bb..ef0b777 100755 --- a/check-sr-results.py +++ b/check-sr-results.py @@ -490,6 +490,13 @@ def apply_overlays(conf, seq_id): overlay_tree(o['tree'], conf['tree']) +def dump_config(conf, filename): + logging.debug(f'Dump {filename}') + + with open(filename, 'w') as yamlfile: + yaml.dump(conf, yamlfile, Dumper=yaml.CDumper) + + if __name__ == '__main__': parser = argparse.ArgumentParser( description='Perform a number of verifications on a SystemReady' @@ -505,6 +512,7 @@ if __name__ == '__main__': parser.add_argument( '--detect-file-encoding-limit', type=int, default=999, help='Specify file encoding detection limit, in number of lines') + parser.add_argument('--dump-config', help='Output yaml config filename') args = parser.parse_args() logging.basicConfig( @@ -531,5 +539,8 @@ if __name__ == '__main__': apply_overlays(conf, seq_id) del conf['overlays'] + if args.dump_config is not None: + dump_config(conf, args.dump_config) + stats = check_tree(conf['tree'], args.dir) logging.info(stats) -- GitLab