blob: 14387284f452e6e2c8a19b0feefe9bea95349711 [file] [log] [blame]
Mike Frysingera23e81f2019-07-01 11:55:50 -04001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Print signal table as markdown."""
8
9from __future__ import print_function
10
11import argparse
12import ctypes
13import ctypes.util
14import os
15import re
16import subprocess
17import sys
18
19import constants
20
21
22# The C library header to find symbols.
23HEADER = 'signal.h'
24
25# The markdown file where we store this table.
26MARKDOWN = 'signals.md'
27
28# The string we use to start the table header.
29START_OF_TABLE = '| number |'
30
31
32def strsignal(num):
33 """Until Python supports this, do it ourselves."""
34 # Handle internal glibc details.
35 if num == 32 or num == 33:
36 return ('Real-time signal reserved by the C library for NPTL; '
37 'see [signal(7)]')
38
39 libc = ctypes.CDLL(ctypes.util.find_library('c'))
40 proto = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_int)
41 func = proto(('strsignal', libc), ((1,),))
42 return func(num).decode('utf-8')
43
44
45def find_symbols(target):
46 """Find all the symbols using |target|."""
47 cc = '%s-clang' % (target,)
48 source = '#include <%s>\n' % (HEADER,)
49 ret = subprocess.run([cc, '-E', '-dD', '-P', '-'],
50 input=source,
51 stdout=subprocess.PIPE,
52 encoding='utf-8')
53
54 table = {}
55
56 # Find all the symbols that are known. We have to do two passes as the
57 # headers like to #undef & redefine names.
58 matcher = re.compile(r'^#define\s+(SIG[^_]+)\s+[0-9]')
59 symbols = set()
60 for line in ret.stdout.splitlines():
61 m = matcher.match(line)
62 if m:
63 sym = m.group(1)
64 if sym not in ('SIGSTKSZ',):
65 symbols.add(sym)
66
67 source += '\n'.join('_%s %s' % (x, x) for x in symbols)
68
69 # Pull out any aliases.
70 matcher = re.compile(r'#define\s+(SIG[^_]+)\s(SIG[A-Z]+)')
71 for line in ret.stdout.splitlines():
72 m = matcher.match(line)
73 if m:
74 table[m.group(1)] = m.group(2)
75
76 # Parse our custom code and extract the symbols.
77 ret = subprocess.run([cc, '-E', '-P', '-'],
78 input=source,
79 stdout=subprocess.PIPE,
80 encoding='utf-8')
81
82 for line in ret.stdout.splitlines():
83 if line.startswith('_SIG'):
84 sym, val = line.strip().split()
85 sym = sym[1:]
86 assert sym not in table, 'sym %s already found' % (sym,)
87 table[sym] = val
88 return table
89
90
91def load_table():
92 """Return a table of all the symbol values (and aliases)."""
93 all_tables = {}
94 for target in constants.TARGETS:
95 all_tables[target] = find_symbols(target)
96
Mike Frysinger2db7b852020-09-10 04:37:44 -040097 # Check that all the tables are the same.
Mike Frysingera23e81f2019-07-01 11:55:50 -040098 basetarget = constants.TARGETS[0]
99 baseline = all_tables[basetarget]
100 for target, table in all_tables.items():
101 assert baseline == table
102
103 # Sometimes values have multiple names.
104 aliases = {}
105 for sym, val in baseline.items():
106 try:
107 int(val)
108 except ValueError:
109 aliases.setdefault(val, []).append(sym)
110
111 # Deal with dynamic realtime signals.
112 baseline['SIGRTMIN-2'] = '32'
113 baseline['SIGRTMIN-1'] = '33'
114 assert 'SIGRTMIN' not in baseline
115 baseline['SIGRTMIN'] = '34'
116 assert 'SIGRTMAX' not in baseline
117 baseline['SIGRTMAX'] = '64'
118 for i in range(1, 16):
119 num = 34 + i
120 baseline['SIGRTMIN+%i' % (i,)] = str(num)
121 for i in range(1, 15):
122 num = 64 - i
123 baseline['SIGRTMAX-%i' % (i,)] = str(num)
124
125 return (baseline, aliases)
126
127
128def sort_table(table):
129 """Return a sorted table."""
130 def sorter(element):
131 try:
132 num = int(element[1])
133 except ValueError:
134 num = 0
135 return (num, element[0])
136 return sorted(table.items(), key=sorter)
137
138
139def get_md_table(table, aliases):
140 """Return the table in markdown format."""
141 ret = []
142 last_num = 0
143 for sym, val in sort_table(table):
144 try:
145 num = int(val)
146 except ValueError:
147 continue
148
149 # Fill in holes in the table so it's obvious to the user when searching.
150 for stub in range(last_num + 1, num):
151 ret.append('| %i | 0x%02x | | *not implemented* ||' % (stub, stub))
152 last_num = num
153
154 desc = strsignal(num)
155 ret.append('| %i | 0x%02x | %s | %s |' % (num, num, sym, desc))
156 for alias in aliases.get(sym, []):
157 ret.append('| %i | 0x%02x | %s | *(Same value as %s)* %s |' %
158 (num, num, alias, sym, desc))
159 return ret
160
161
162def get_parser():
163 """Return a command line parser."""
164 parser = argparse.ArgumentParser(description=__doc__)
165 parser.add_argument('-i', '--inplace', action='store_true',
166 help='Update the markdown file directly.')
167 return parser
168
169
170def main(argv):
171 """The main func!"""
172 parser = get_parser()
173 opts = parser.parse_args(argv)
174
175 baseline, aliases = load_table()
176 md_data = get_md_table(baseline, aliases)
177
178 if opts.inplace:
179 md_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
180 MARKDOWN)
181 with open(md_file) as fp:
182 old_data = fp.readlines()
183
184 i = None
185 for i, line in enumerate(old_data):
186 if line.startswith(START_OF_TABLE):
187 break
188 else:
189 print('ERROR: could not find table in %s' % (md_file,),
190 file=sys.stderr)
191 sys.exit(1)
192
193 old_data = old_data[0:i + 2]
194 with open(md_file, 'w') as fp:
195 fp.writelines(old_data)
196 fp.write('\n'.join(md_data) + '\n')
197 else:
198 print('\n'.join(md_data))
199
200
201if __name__ == '__main__':
202 sys.exit(main(sys.argv[1:]))