constants: add reference tables for developers

Change-Id: I6a47a28c903c19feda9c161dba10f39ebd453b84
Reviewed-on: https://chromium-review.googlesource.com/1682879
Tested-by: Mike Frysinger <vapier@chromium.org>
Commit-Ready: Mike Frysinger <vapier@chromium.org>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Brian Norris <briannorris@chromium.org>
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
diff --git a/constants/errnos.py b/constants/errnos.py
new file mode 100755
index 0000000..6848c60
--- /dev/null
+++ b/constants/errnos.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright 2019 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.
+
+"""Print errno table as markdown."""
+
+from __future__ import print_function
+
+import argparse
+import os
+import subprocess
+import sys
+
+import constants
+
+
+# The C library header to find symbols.
+HEADER = 'errno.h'
+
+# The markdown file where we store this table.
+MARKDOWN = 'errnos.md'
+
+# The string we use to start the table header.
+START_OF_TABLE = '| number |'
+
+
+def find_symbols(target):
+    """Find all the symbols using |target|."""
+    cc = '%s-clang' % (target,)
+    ret = subprocess.run([cc, '-E', '-dD', '-P', '-'],
+                         input='#include <%s>\n' % (HEADER,),
+                         stdout=subprocess.PIPE,
+                         encoding='utf-8')
+    table = {}
+    for line in ret.stdout.splitlines():
+        # If we need to make this smarter, signals.py handles things.
+        assert not line.startswith('#undef E'), '#undef not handled'
+        if line.startswith('#define E'):
+            sym, val = line.strip().split()[1:3]
+            assert sym not in table, 'sym %s already found' % (sym,)
+            table[sym] = val
+    return table
+
+
+def load_table():
+    """Return a table of all the symbol values (and aliases)."""
+    all_tables = {}
+    for target in constants.TARGETS:
+        all_tables[target] = find_symbols(target)
+
+    # Sanity 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)
+
+    return (baseline, aliases)
+
+
+def sort_table(table):
+    """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, aliases):
+    """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 = os.strerror(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():
+    """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):
+    """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 = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                               MARKDOWN)
+        with open(md_file) as fp:
+            old_data = fp.readlines()
+
+        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 open(md_file, '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:]))