| #!/usr/bin/env python3 |
| |
| # Copyright 2021 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| #### CATEGORY=Run, inspect and debug |
| ### Extract disk image from serial logs. |
| ## |
| ## When filesystems are corrupted, it may happen that we lose abiility to ssh into the system |
| ## to debug. In such cases, we may want access to filesystem metadata to debug the problem. A |
| ## board or product may write filesystem metadata to serial. |
| ## This script helps to identify and extract metadata written to serial. |
| ## |
| ## Since scraping logs is prone to error, this script may fail if format of log changes. For |
| ## reference, |logs| under function test() contains a real example log with which the script |
| ## works. |
| ## |
| ## There are multiple ways to get serial log including `fx serial`. The output of serial log |
| ## should be stored in input file. |
| ## |
| ## usage: fx disk-extract-serial-log --input FILE --output FILE |
| ## |
| ## --input Extract disk image from serial logs. |
| ## --output Path where extracted image will be created. |
| ## |
| |
| import argparse |
| import os |
| import struct |
| import sys |
| import re |
| import zlib |
| import tempfile |
| |
| |
| def test(): |
| logs = ( |
| '[00018.241] 02331:02333> [00018.241126][2331][2333][fshost.cm] ERROR: [src/storage/fshost/extract-metadata.cc(140)]\n' |
| '[00018.241] 02331:02333> EIL: Extracting minfs to serial.\n' |
| '[00018.241] 02331:02333> EIL: Following lines that start with "EIL" are from extractor.\n' |
| '[00018.241] 02331:02333> EIL: Successful extraction ends with "EIL: Done extracting minfs to serial."\n' |
| '[00018.241] 02331:02333> EIL: Compression:off Checksum:on Offset:on bytes_per_line:64\n' |
| '[00018.241] 02331:02333>\n' |
| '[00027.513] 02331:02333> [00027.513786][2331][2333][fshost.cm] ERROR: [src/storage/fshost/extract-metadata.cc(76)]\n' |
| '[00027.513] 02331:02333> EIL 0-63:643e34d64b1f76e3c57b6f9af52ba08a0100000000000000002000000000000000200000000000006d39c50c0000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 64-127:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 128-8191:*\n' |
| '[00027.513] 02331:02333> EIL 8192-8255:fb7db2cd9b1dcc8e654d025bf38116450d0000000000000000000000000000000000000000000000f75470420000000000000000000000000020000000000000\n' |
| '[00027.513] 02331:02333> EIL 8256-8319:02020000000000000020000000000000000000200000000000000000000000000000002000000000000010200000000002020000000000000000102000000000\n' |
| '[00027.513] 02331:02333> EIL 8320-8383:00000040000000000000000000000000000000400000000000001040000000000202000000000000000010400000000000000060000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 8384-8447:00000060000000000000106000000000020200000000000000001060000000000000008000000000000000000000000000000080000000000000208000000000\n' |
| '[00027.513] 02331:02333> EIL 8448-8511:02020000000000000000208000000000000000a0000000000000000000000000000000a000000000002000a0000000000100000000000000002000a000000000\n' |
| '[00027.513] 02331:02333> EIL 8512-8575:004000a0000000000202000000000000004000a000000000000010a0000000000100000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 8576-8639:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 8640-16383:*\n' |
| '[00027.513] 02331:02333> EIL 16384-16447:214d696e4653210004d3d3d3d30050380900000000000000615f933e020000000300000000200000000100008000000000100000020000000200000000000100\n' |
| '[00027.513] 02331:02333> EIL 16448-16511:00000200000003000000040000000500000010000000000001000000010000000100000002000000010000000000000000000000020000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 16512-16575:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 16576-24575:*\n' |
| '[00027.513] 02331:02333> EIL 24576-24639:03000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 24640-24703:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 24704-1073151:*\n' |
| '[00027.513] 02331:02333> EIL 1073152-1073215:03000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 1073216-1073279:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 1073280-2121983:*\n' |
| '[00027.513] 02331:02333> EIL 2121984-2122047:046e6faa00200000010000000200000021317689d5355a1600000000000000000000000000000000020000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 2122048-2122111:01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 2122112-2122175:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 2122176-3170303:*\n' |
| '[00027.513] 02331:02333> EIL 3170304-3170367:214d696e4653210004d3d3d3d30050380900000000000000615f933e020000000300000000200000000100008000000000100000020000000200000000000100\n' |
| '[00027.513] 02331:02333> EIL 3170368-3170431:00000200000003000000040000000500000010000000000001000000010000000100000002000000010000000000000000000000020000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 3170432-3170495:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.513] 02331:02333> EIL 3170496-3178495:*\n' |
| '[00027.513] 02331:02333> EIL 3178496-3178559:6c6e726a626f6c620000000000000000000000000000000000000000000000009b11533a00000000000000000000000000000000000000000000000000000000\n' |
| '[00027.514] 02331:02333> EIL 3178560-3178623:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.514] 02331:02333> EIL 3178624-5267455:*\n' |
| '[00027.514] 02331:02333> EIL 5267456-5267519:010000001000000001042e0000000000010000001000008002042e2e000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.514] 02331:02333> EIL 5267520-5267583:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n' |
| '[00027.514] 02331:02333> EIL 5267584-5275647:*\n' |
| '[00027.514] 02331:02333> EIL 0-5275647:checksum: 2019026790\n' |
| '[00027.514] 02331:02333>\n' |
| '[00027.514] 02331:02333> [00027.514051][2331][2333][fshost.cm] ERROR: [src/storage/fshost/extract-metadata.cc(151)]\n' |
| '[00027.514] 02331:02333> EIL: Done extracting minfs to serial\n' |
| ) |
| |
| assert extract(logs, '', 'EIL', tempfile.NamedTemporaryFile().name) == 0 |
| |
| |
| def trim_log(logs, prefix_regex, tag): |
| |
| lines = '' |
| |
| # Remove non-tag lines and all the test that preceeds tag. |
| for line in logs.splitlines(): |
| if tag in line: |
| lines = lines + re.sub('.*> ' + tag, tag, line) + '\n' |
| |
| # Remove header and footer messages |
| lines = lines.replace(prefix_regex + tag + ': Extracting minfs to serial.\n', |
| '') |
| lines = lines.replace( |
| prefix_regex + tag + ': Following lines that start with "' + tag + |
| '" are from extractor.\n', '') |
| lines = lines.replace( |
| prefix_regex + tag + ': Successful extraction ends with "' + tag + |
| ': Done extracting minfs to serial."\n', '') |
| lines = lines.replace( |
| prefix_regex + tag + ': Done extracting minfs to serial\n', '') |
| return lines |
| |
| |
| # Removes config with which disk image was written to logs. |
| # Returns configs and a copy of trimmed logs containing only hex string lines. |
| def get_configs(logs, prefix_regex, tag): |
| bytes_per_line = int( |
| re.match( |
| 'EIL: Compression:(\S+) Checksum:(\S+) Offset:(\S+) bytes_per_line:(\d+)\n', |
| logs).group(4)) |
| compression = re.match( |
| 'EIL: Compression:(\S+) Checksum:(\S+) Offset:(\S+) bytes_per_line:(\d+)\n', |
| logs).group(1) |
| checksum = int(re.search('EIL (\d+)-(\d+):checksum: (\d+)', logs).group(3)) |
| logs = re.sub( |
| 'EIL: Compression:(\S+) Checksum:(\S+) Offset:(\S+) bytes_per_line:(\d+)\n', |
| '', logs) |
| logs = re.sub('EIL (\d+)-(\d+):checksum: (\d+)\n', '', logs) |
| |
| return logs, bytes_per_line, checksum, compression |
| |
| |
| # Extracts disk image from the logs and writes it to outfile. |
| def extract(logs, prefix_regex, tag, outfile): |
| logs = trim_log(logs, prefix_regex, tag) |
| if logs == None: |
| return 1 |
| |
| r = get_configs(logs, prefix_regex, tag) |
| if r == None: |
| return 1 |
| logs, bytes_per_line, checksum, compression = r |
| |
| print('compression: ' + compression) |
| print('bytes_per_line: ' + str(bytes_per_line)) |
| print('checksum: ' + str(checksum)) |
| |
| end = -1 |
| data = bytearray() |
| bin = bytearray() |
| last_bin = bytearray() |
| |
| with open(outfile, 'wb') as fout: |
| for line in logs.splitlines(): |
| match = re.match('EIL (\d+)-(\d+):(\S+)', line) |
| start = int(match.group(1)) |
| if start != end + 1: |
| print('Invalid offset. start:' + str(start) + ' end: ' + str(end)) |
| exit(1) |
| end = int(match.group(2)) |
| hex = match.group(3) |
| if hex == '*': |
| bin = last_bin |
| count = (1 + end - start) // len(bin) |
| for x in range(0, count): |
| data.extend(bin) |
| fout.write(bin) |
| else: |
| if len(hex) != (end - start + 1) * 2: |
| print('Invalid hex string. start:' + str(start) + ' end: ' + |
| str(end) + ': ' + str(len(hex))) |
| exit(1) |
| bin = bytes.fromhex(hex) |
| last_bin = bin |
| data.extend(bin) |
| fout.write(bin) |
| |
| found = zlib.crc32(data) |
| if checksum != found: |
| print('Output file created but crc did not match: Expected: ' + |
| str(checksum) + ' found: ' + str(found)) |
| return 1 |
| return 0 |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description='Extract disk image from serial logs') |
| parser.add_argument('--input', required=True, help='Path to log file.') |
| parser.add_argument( |
| '--output', |
| required=True, |
| help='Path where extracted image will be created.') |
| args = parser.parse_args() |
| |
| logs = open(args.input, mode='r').read() |
| return (extract(logs, '', 'EIL', args.output)) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |