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