diff --git a/.gitignore b/.gitignore index 7fee136..f95d7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.cmd *.gcda *.gcno +*.pyc .config .config.old scripts/basic/docproc diff --git a/OWNERS b/OWNERS index e7ca0e2..e525dff 100644 --- a/OWNERS +++ b/OWNERS @@ -1,2 +1,2 @@ -dhendrix@chromium.org dlaurie@chromium.org +dlaurie@google.com diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg new file mode 100644 index 0000000..2150455 --- /dev/null +++ b/PRESUBMIT.cfg @@ -0,0 +1,10 @@ +# This project uses BSD/GPLv2 for the license and tabs for indentation. + +[Hook Scripts] +fmap_unittest py2 = python2 ./fmap_unittest.py +fmap_unittest py3 = python3 ./fmap_unittest.py +cros lint = cros lint ${PRESUBMIT_FILES} + +[Hook Overrides] +cros_license_check: false +tab_check: false diff --git a/fmap.py b/fmap.py old mode 100644 new mode 100755 index 3f26b54..0ef9f40 --- a/fmap.py +++ b/fmap.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- # # Copyright 2010, Google Inc. # All rights reserved. @@ -33,9 +34,7 @@ # GNU General Public License ("GPL") version 2 as published by the Free # Software Foundation. -""" -This module provides basic encode and decode functionality to the flashrom -memory map (FMAP) structure. +"""Basic encode/decode functionality of flashrom memory map (FMAP) structures. Usage: (decode) @@ -51,18 +50,29 @@ tuple of decoded area flags. """ +from __future__ import print_function + +import argparse +import copy +import logging +import pprint import struct import sys + # constants imported from lib/fmap.h -FMAP_SIGNATURE = "__FMAP__" +FMAP_SIGNATURE = b'__FMAP__' FMAP_VER_MAJOR = 1 -FMAP_VER_MINOR = 0 +FMAP_VER_MINOR_MIN = 0 +FMAP_VER_MINOR_MAX = 1 FMAP_STRLEN = 32 +FMAP_SEARCH_STRIDE = 4 FMAP_FLAGS = { 'FMAP_AREA_STATIC': 1 << 0, 'FMAP_AREA_COMPRESSED': 1 << 1, + 'FMAP_AREA_RO': 1 << 2, + 'FMAP_AREA_PRESERVE': 1 << 3, } FMAP_HEADER_NAMES = ( @@ -82,13 +92,14 @@ 'flags', ) + # format string -FMAP_HEADER_FORMAT = "<8sBBQI%dsH" % (FMAP_STRLEN) -FMAP_AREA_FORMAT = " FMAP_VER_MINOR_MAX): raise struct.error('Incompatible version') # convert null-terminated names - header['name'] = header['name'].strip(chr(0)) + header['name'] = header['name'].strip(b'\x00') + + # In Python 2, binary==string, so we don't need to convert. + if sys.version_info.major >= 3: + # Do the decode after verifying it to avoid decode errors due to corruption. + for name in FMAP_HEADER_NAMES: + if hasattr(header[name], 'decode'): + header[name] = header[name].decode('utf-8') + return (header, struct.calcsize(FMAP_HEADER_FORMAT)) def _fmap_decode_area(blob, offset): - """ (internal) Decodes a FMAP area record from blob by offset """ + """(internal) Decodes a FMAP area record from blob by offset""" area = {} for (name, value) in zip(FMAP_AREA_NAMES, struct.unpack_from(FMAP_AREA_FORMAT, blob, offset)): area[name] = value # convert null-terminated names - area['name'] = area['name'].strip(chr(0)) + area['name'] = area['name'].strip(b'\x00') # add a (readonly) readable FLAGS area['FLAGS'] = _fmap_decode_area_flags(area['flags']) + + # In Python 2, binary==string, so we don't need to convert. + if sys.version_info.major >= 3: + for name in FMAP_AREA_NAMES: + if hasattr(area[name], 'decode'): + area[name] = area[name].decode('utf-8') + return (area, struct.calcsize(FMAP_AREA_FORMAT)) def _fmap_decode_area_flags(area_flags): - """ (internal) Decodes a FMAP flags property """ - return tuple([name for name in FMAP_FLAGS if area_flags & FMAP_FLAGS[name]]) + """(internal) Decodes a FMAP flags property""" + # Since FMAP_FLAGS is a dict with arbitrary ordering, sort the list so the + # output is stable. Also sorting is nicer for humans. + return tuple(sorted(x for x in FMAP_FLAGS if area_flags & FMAP_FLAGS[x])) -def fmap_decode(blob, offset=None): - """ Decodes a blob to FMAP dictionary object. +def _fmap_check_name(fmap, name): + """Checks if the FMAP structure has correct name. - Arguments: + Args: + fmap: A decoded FMAP structure. + name: A string to specify expected FMAP name. + + Raises: + struct.error if the name does not match. + """ + if fmap['name'] != name: + raise struct.error('Incorrect FMAP (found: "%s", expected: "%s")' % + (fmap['name'], name)) + + +def _fmap_search_header(blob, fmap_name=None): + """Searches FMAP headers in given blob. + + Uses same logic from vboot_reference/host/lib/fmap.c. + + Args: + blob: A string containing FMAP data. + fmap_name: A string to specify target FMAP name. + + Returns: + A tuple of (fmap, size, offset). + """ + lim = len(blob) - struct.calcsize(FMAP_HEADER_FORMAT) + align = FMAP_SEARCH_STRIDE + + # Search large alignments before small ones to find "right" FMAP. + while align <= lim: + align *= 2 + + while align >= FMAP_SEARCH_STRIDE: + for offset in range(align, lim + 1, align * 2): + if not blob.startswith(FMAP_SIGNATURE, offset): + continue + try: + (fmap, size) = _fmap_decode_header(blob, offset) + if fmap_name is not None: + _fmap_check_name(fmap, fmap_name) + return (fmap, size, offset) + except struct.error as e: + # Search for next FMAP candidate. + logging.debug('Continue searching FMAP due to exception %r', e) + align //= 2 + raise struct.error('No valid FMAP signatures.') + + +def fmap_decode(blob, offset=None, fmap_name=None): + """Decodes a blob to FMAP dictionary object. + + Args: blob: a binary data containing FMAP structure. offset: starting offset of FMAP. When omitted, fmap_decode will search in the blob. + fmap_name: A string to specify target FMAP name. """ fmap = {} - if offset == None: - # try search magic in fmap - offset = blob.find(FMAP_SIGNATURE) - (fmap, size) = _fmap_decode_header(blob, offset) + + if offset is None: + (fmap, size, offset) = _fmap_search_header(blob, fmap_name) + else: + (fmap, size) = _fmap_decode_header(blob, offset) + if fmap_name is not None: + _fmap_check_name(fmap, fmap_name) fmap['areas'] = [] offset = offset + size - for i in range(fmap['nareas']): + for _ in range(fmap['nareas']): (area, size) = _fmap_decode_area(blob, offset) offset = offset + size fmap['areas'].append(area) @@ -148,21 +231,33 @@ def fmap_decode(blob, offset=None): def _fmap_encode_header(obj): - """ (internal) Encodes a FMAP header """ + """(internal) Encodes a FMAP header""" + # Convert strings to bytes. + obj = copy.deepcopy(obj) + for name in FMAP_HEADER_NAMES: + if hasattr(obj[name], 'encode'): + obj[name] = obj[name].encode('utf-8') + values = [obj[name] for name in FMAP_HEADER_NAMES] return struct.pack(FMAP_HEADER_FORMAT, *values) def _fmap_encode_area(obj): - """ (internal) Encodes a FMAP area entry """ + """(internal) Encodes a FMAP area entry""" + # Convert strings to bytes. + obj = copy.deepcopy(obj) + for name in FMAP_AREA_NAMES: + if hasattr(obj[name], 'encode'): + obj[name] = obj[name].encode('utf-8') + values = [obj[name] for name in FMAP_AREA_NAMES] return struct.pack(FMAP_AREA_FORMAT, *values) def fmap_encode(obj): - """ Encodes a FMAP dictionary object to blob. + """Encodes a FMAP dictionary object to blob. - Arguments + Args: obj: a FMAP dictionary object. """ # fix up values @@ -174,13 +269,32 @@ def fmap_encode(obj): return blob -if __name__ == '__main__': - # main entry, do a unit test - blob = open('bin/example.bin').read() +def get_parser(): + """Return a command line parser.""" + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('file', help='The file to decode & print.') + parser.add_argument('--raw', action='store_true', + help='Dump the object output for scripts.') + return parser + + +def main(argv): + """Decode FMAP from supplied file and print.""" + parser = get_parser() + opts = parser.parse_args(argv) + + if not opts.raw: + print('Decoding FMAP from: %s' % opts.file) + blob = open(opts.file, 'rb').read() obj = fmap_decode(blob) - print obj - blob2 = fmap_encode(obj) - obj2 = fmap_decode(blob2) - print obj2 - assert obj == obj2 + if opts.raw: + print(obj) + else: + pp = pprint.PrettyPrinter(indent=2) + pp.pprint(obj) + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/fmap_unittest.py b/fmap_unittest.py new file mode 100755 index 0000000..91b346b --- /dev/null +++ b/fmap_unittest.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright 2017 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit test for fmap module.""" + +from __future__ import print_function + +import struct +import unittest + +import fmap + +# Expected decoded fmap structure from bin/example.bin +_EXAMPLE_BIN_FMAP = { + 'ver_major': 1, + 'ver_minor': 0, + 'name': 'example', + 'nareas': 4, + 'base': 0, + 'signature': '__FMAP__', + 'areas': [{ + 'FLAGS': ('FMAP_AREA_STATIC',), + 'size': 128, + 'flags': 1, + 'name': 'bootblock', + 'offset': 0 + }, { + 'FLAGS': ('FMAP_AREA_COMPRESSED', 'FMAP_AREA_STATIC'), + 'size': 128, + 'flags': 3, + 'name': 'normal', + 'offset': 128 + }, { + 'FLAGS': ('FMAP_AREA_COMPRESSED', 'FMAP_AREA_STATIC'), + 'size': 256, + 'flags': 3, + 'name': 'fallback', + 'offset': 256 + }, { + 'FLAGS': (), + 'size': 512, + 'flags': 0, + 'name': 'data', + 'offset': 512 + }], + 'size': 1024 +} + + +class FmapTest(unittest.TestCase): + """Unit test for fmap module.""" + + # All failures to diff the entire struct. + maxDiff = None + + def setUp(self): + with open('bin/example.bin', 'rb') as f: + self.example_blob = f.read() + + def testDecode(self): + decoded = fmap.fmap_decode(self.example_blob) + self.assertEqual(_EXAMPLE_BIN_FMAP, decoded) + + def testDecodeWithOffset(self): + decoded = fmap.fmap_decode(self.example_blob, 512) + self.assertEqual(_EXAMPLE_BIN_FMAP, decoded) + + def testDecodeWithName(self): + decoded = fmap.fmap_decode(self.example_blob, fmap_name='example') + self.assertEqual(_EXAMPLE_BIN_FMAP, decoded) + decoded = fmap.fmap_decode(self.example_blob, 512, 'example') + self.assertEqual(_EXAMPLE_BIN_FMAP, decoded) + + def testDecodeWithWrongName(self): + with self.assertRaises(struct.error): + fmap.fmap_decode(self.example_blob, fmap_name='banana') + with self.assertRaises(struct.error): + fmap.fmap_decode(self.example_blob, 512, 'banana') + + def testDecodeWithWrongOffset(self): + with self.assertRaises(struct.error): + fmap.fmap_decode(self.example_blob, 42) + + def testEncode(self): + encoded = fmap.fmap_encode(_EXAMPLE_BIN_FMAP) + # example.bin contains other binary data besides the fmap + self.assertIn(encoded, self.example_blob) + + +if __name__ == '__main__': + unittest.main() diff --git a/lib/fmap.c b/lib/fmap.c index 5022434..c62735d 100644 --- a/lib/fmap.c +++ b/lib/fmap.c @@ -57,6 +57,7 @@ const struct valstr flag_lut[] = { { FMAP_AREA_STATIC, "static" }, { FMAP_AREA_COMPRESSED, "compressed" }, { FMAP_AREA_RO, "ro" }, + { FMAP_AREA_PRESERVE, "preserve" }, }; /* returns size of fmap data structure if successful, <0 to indicate error */ @@ -349,10 +350,11 @@ int fmap_append_area(struct fmap **fmap, return new_size; } -struct fmap_area *fmap_find_area(struct fmap *fmap, const char *name) +const struct fmap_area *fmap_find_area(const struct fmap *fmap, + const char *name) { int i; - struct fmap_area *area = NULL; + const struct fmap_area *area = NULL; if (!fmap || !name) return NULL; @@ -532,10 +534,10 @@ static int fmap_append_area_test(struct fmap **fmap) return status; } -static int fmap_find_area_test(struct fmap *fmap) +static int fmap_find_area_test(const struct fmap *fmap) { status = fail; - char area_name[] = "test_area_1"; + const char area_name[] = "test_area_1"; if (fmap_find_area(NULL, area_name) || fmap_find_area(fmap, NULL)) { diff --git a/lib/fmap.h b/lib/fmap.h index 9cea22d..05993fd 100644 --- a/lib/fmap.h +++ b/lib/fmap.h @@ -36,6 +36,10 @@ #ifndef FLASHMAP_LIB_FMAP_H__ #define FLASHMAP_LIB_FMAP_H__ +#ifdef __cplusplus +extern "C" { +#endif + #include #include @@ -50,6 +54,7 @@ enum fmap_flags { FMAP_AREA_STATIC = 1 << 0, FMAP_AREA_COMPRESSED = 1 << 1, FMAP_AREA_RO = 1 << 2, + FMAP_AREA_PRESERVE = 1 << 3, /* Should be preserved on update. */ }; /* Mapping of volatile and static regions in firmware binary */ @@ -184,9 +189,14 @@ extern int fmap_append_area(struct fmap **fmap, * returns a pointer to the entry in the fmap structure if successful * returns NULL to indicate failure or if no matching area entry is found */ -extern struct fmap_area *fmap_find_area(struct fmap *fmap, const char *name); +extern const struct fmap_area *fmap_find_area(const struct fmap *fmap, + const char *name); /* unit testing stuff */ extern int fmap_test(); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* FLASHMAP_LIB_FMAP_H__*/