| #!/usr/bin/env python3 |
| # Copyright 2019 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Print signal table as markdown.""" |
| |
| from __future__ import print_function |
| |
| import argparse |
| import ctypes |
| import ctypes.util |
| from pathlib import Path |
| import re |
| import subprocess |
| import sys |
| from typing import Dict, List, Optional, Tuple |
| |
| import constants |
| |
| |
| # The directory where all the code & markdown files live. |
| TOPDIR = Path(__file__).resolve().parent |
| |
| # The C library header to find symbols. |
| HEADER = "signal.h" |
| |
| # The markdown file where we store this table. |
| MARKDOWN = "signals.md" |
| |
| # The string we use to start the table header. |
| START_OF_TABLE = "| number |" |
| |
| |
| def strsignal(num: int) -> str: |
| """Until Python supports this, do it ourselves.""" |
| # Handle internal glibc details. |
| if num in (32, 33): |
| return ( |
| "Real-time signal reserved by the C library for " |
| "[NPTL][pthreads(7)]; see [signal(7)]" |
| ) |
| |
| libc = ctypes.CDLL(ctypes.util.find_library("c")) |
| proto = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_int) |
| func = proto(("strsignal", libc), ((1,),)) |
| return func(num).decode("utf-8") |
| |
| |
| def find_symbols(target: str) -> Dict[str, str]: |
| """Find all the symbols using |target|.""" |
| cc = "%s-clang" % (target,) |
| source = "#include <%s>\n" % (HEADER,) |
| ret = subprocess.run( |
| [cc, "-E", "-dD", "-P", "-"], |
| check=True, |
| input=source, |
| stdout=subprocess.PIPE, |
| encoding="utf-8", |
| ) |
| |
| table = {} |
| |
| # Find all the symbols that are known. We have to do two passes as the |
| # headers like to #undef & redefine names. |
| matcher = re.compile(r"^#define\s+(SIG[^_]+)\s+[0-9]") |
| symbols = set() |
| for line in ret.stdout.splitlines(): |
| m = matcher.match(line) |
| if m: |
| sym = m.group(1) |
| if sym not in ("SIGSTKSZ",): |
| symbols.add(sym) |
| |
| source += "\n".join("_%s %s" % (x, x) for x in symbols) |
| |
| # Pull out any aliases. |
| matcher = re.compile(r"#define\s+(SIG[^_]+)\s(SIG[A-Z]+)") |
| for line in ret.stdout.splitlines(): |
| m = matcher.match(line) |
| if m: |
| table[m.group(1)] = m.group(2) |
| |
| # Parse our custom code and extract the symbols. |
| ret = subprocess.run( |
| [cc, "-E", "-P", "-"], |
| check=True, |
| input=source, |
| stdout=subprocess.PIPE, |
| encoding="utf-8", |
| ) |
| |
| for line in ret.stdout.splitlines(): |
| if line.startswith("_SIG"): |
| sym, val = line.strip().split() |
| sym = sym[1:] |
| assert sym not in table, "sym %s already found" % (sym,) |
| table[sym] = val |
| return table |
| |
| |
| def load_table() -> Tuple[Dict[str, str], Dict[str, List[str]]]: |
| """Return a table of all the symbol values (and aliases).""" |
| all_tables = {} |
| for target in constants.TARGETS: |
| all_tables[target] = find_symbols(target) |
| |
| # Check that all the tables are the same. |
| basetarget = constants.TARGETS[0] |
| baseline = all_tables[basetarget] |
| for target, table in all_tables.items(): |
| assert baseline == table |
| |
| # Sometimes values have multiple names. |
| aliases = {} |
| for sym, val in baseline.items(): |
| try: |
| int(val) |
| except ValueError: |
| aliases.setdefault(val, []).append(sym) |
| |
| # Deal with dynamic realtime signals. |
| baseline["SIGRTMIN-2"] = "32" |
| baseline["SIGRTMIN-1"] = "33" |
| assert "SIGRTMIN" not in baseline |
| baseline["SIGRTMIN"] = "34" |
| assert "SIGRTMAX" not in baseline |
| baseline["SIGRTMAX"] = "64" |
| for i in range(1, 16): |
| num = 34 + i |
| baseline["SIGRTMIN+%i" % (i,)] = str(num) |
| for i in range(1, 15): |
| num = 64 - i |
| baseline["SIGRTMAX-%i" % (i,)] = str(num) |
| |
| return (baseline, aliases) |
| |
| |
| def sort_table(table: Dict[str, str]) -> List[Tuple[str, str]]: |
| """Return a sorted table.""" |
| |
| def sorter(element): |
| try: |
| num = int(element[1]) |
| except ValueError: |
| num = 0 |
| return (num, element[0]) |
| |
| return sorted(table.items(), key=sorter) |
| |
| |
| def get_md_table( |
| table: Dict[str, str], aliases: Dict[str, List[str]] |
| ) -> List[str]: |
| """Return the table in markdown format.""" |
| ret = [] |
| last_num = 0 |
| for sym, val in sort_table(table): |
| try: |
| num = int(val) |
| except ValueError: |
| continue |
| |
| # Fill in holes in the table so it's obvious to the user when searching. |
| for stub in range(last_num + 1, num): |
| ret.append("| %i | 0x%02x | | *not implemented* ||" % (stub, stub)) |
| last_num = num |
| |
| desc = strsignal(num) |
| ret.append("| %i | 0x%02x | %s | %s |" % (num, num, sym, desc)) |
| for alias in aliases.get(sym, []): |
| ret.append( |
| "| %i | 0x%02x | %s | *(Same value as %s)* %s |" |
| % (num, num, alias, sym, desc) |
| ) |
| return ret |
| |
| |
| def get_parser() -> argparse.ArgumentParser: |
| """Return a command line parser.""" |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| "-i", |
| "--inplace", |
| action="store_true", |
| help="Update the markdown file directly.", |
| ) |
| return parser |
| |
| |
| def main(argv: List[str]) -> Optional[int]: |
| """The main func!""" |
| parser = get_parser() |
| opts = parser.parse_args(argv) |
| |
| baseline, aliases = load_table() |
| md_data = get_md_table(baseline, aliases) |
| |
| if opts.inplace: |
| md_file = TOPDIR / MARKDOWN |
| old_data = md_file.read_text().splitlines(keepends=True) |
| |
| i = None |
| for i, line in enumerate(old_data): |
| if line.startswith(START_OF_TABLE): |
| break |
| else: |
| print( |
| "ERROR: could not find table in %s" % (md_file,), |
| file=sys.stderr, |
| ) |
| sys.exit(1) |
| |
| old_data = old_data[0 : i + 2] |
| with md_file.open("w") as fp: |
| fp.writelines(old_data) |
| fp.write("\n".join(md_data) + "\n") |
| else: |
| print("\n".join(md_data)) |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv[1:])) |