parse_fdinfo: Don't advance pointer twice [BZ #31798]
[glibc.git] / sysdeps / unix / sysv / linux / glibcsyscalls.py
blob1cf2fb9e8e252006f211452498cb236fb2e416da
1 #!/usr/bin/python3
2 # Helpers for glibc system call list processing.
3 # Copyright (C) 2018-2024 Free Software Foundation, Inc.
4 # This file is part of the GNU C Library.
6 # The GNU C Library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
11 # The GNU C Library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with the GNU C Library; if not, see
18 # <http://www.gnu.org/licenses/>.
20 import os
21 import re
23 if __name__ != '__main__':
24 # When called as a main program, this is not needed.
25 import glibcextract
27 def extract_system_call_name(macro):
28 """Convert the macro name (with __NR_) to a system call name."""
29 prefix = '__NR_'
30 if macro.startswith(prefix):
31 return macro[len(prefix):]
32 else:
33 raise ValueError('invalid system call name: {!r}'.format(macro))
35 # Matches macros for system call names.
36 RE_SYSCALL = re.compile('__NR_.*')
38 # Some __NR_ constants are not real
39 RE_PSEUDO_SYSCALL = re.compile(r"""__NR_(
40 # Reserved system call.
41 (unused|reserved)[0-9]+
43 # Pseudo-system call which describes a range.
44 |(syscalls|arch_specific_syscall|(OABI_)?SYSCALL_BASE|SYSCALL_MASK)
45 |(|64_|[NO]32_)Linux(_syscalls)?
46 )""", re.X)
48 def kernel_constants(cc):
49 """Return a dictionary with the kernel-defined system call numbers.
51 This comes from <asm/unistd.h>.
53 """
54 return {extract_system_call_name(name) : int(value)
55 for name, value in glibcextract.compute_macro_consts(
56 '#include <asm/unistd.h>\n'
57 # Regularize the kernel definitions if necessary.
58 '#include <fixup-asm-unistd.h>',
59 cc, macro_re=RE_SYSCALL, exclude_re=RE_PSEUDO_SYSCALL)
60 .items()}
62 class SyscallNamesList:
63 """The list of known system call names.
65 glibc keeps a list of system call names. The <sys/syscall.h>
66 header needs to provide a SYS_ name for each __NR_ macro,
67 and the generated <bits/syscall.h> header uses an
68 architecture-independent list, so that there is a chance that
69 system calls arriving late on certain architectures will automatically
70 get the expected SYS_ macro.
72 syscalls: list of strings with system call names
73 kernel_version: tuple of integers; the kernel version given in the file
75 """
76 def __init__(self, lines):
77 self.syscalls = []
78 old_name = None
79 self.kernel_version = None
80 self.__lines = tuple(lines)
81 for line in self.__lines:
82 line = line.strip()
83 if (not line) or line[0] == '#':
84 continue
85 comps = line.split()
86 if len(comps) == 1:
87 self.syscalls.append(comps[0])
88 if old_name is not None:
89 if comps[0] < old_name:
90 raise ValueError(
91 'name list is not sorted: {!r} < {!r}'.format(
92 comps[0], old_name))
93 old_name = comps[0]
94 continue
95 if len(comps) == 2 and comps[0] == "kernel":
96 if self.kernel_version is not None:
97 raise ValueError(
98 "multiple kernel versions: {!r} and !{r}".format(
99 kernel_version, comps[1]))
100 self.kernel_version = tuple(map(int, comps[1].split(".")))
101 continue
102 raise ValueError("invalid line: !r".format(line))
103 if self.kernel_version is None:
104 raise ValueError("missing kernel version")
106 def merge(self, names):
107 """Merge sequence NAMES and return the lines of the new file."""
108 names = list(set(names) - set(self.syscalls))
109 names.sort()
110 names.reverse()
111 result = []
112 def emit_name():
113 result.append(names[-1] + "\n")
114 del names[-1]
116 for line in self.__lines:
117 comps = line.strip().split()
118 if len(comps) == 1 and not comps[0].startswith("#"):
119 # File has a system call at this position. Insert all
120 # the names that come before the name in the file
121 # lexicographically.
122 while names and names[-1] < comps[0]:
123 emit_name()
124 result.append(line)
125 while names:
126 emit_name()
128 return result
130 def load_arch_syscall_header(path):
131 """"Load the system call header form the file PATH.
133 The file must consist of lines of this form:
135 #define __NR_exit 1
137 The file is parsed verbatim, without running it through a C
138 preprocessor or parser. The intent is that the file can be
139 readily processed by tools.
142 with open(path) as inp:
143 result = {}
144 old_name = None
145 for line in inp:
146 line = line.strip()
148 # Ignore the initial comment line.
149 if line.startswith("/*") and line.endswith("*/"):
150 continue
152 define, name, number = line.split(' ', 2)
153 if define != '#define':
154 raise ValueError("invalid syscall header line: {!r}".format(
155 line))
156 result[extract_system_call_name(name)] = int(number)
158 # Check list order.
159 if old_name is not None:
160 if name < old_name:
161 raise ValueError(
162 'system call list is not sorted: {!r} < {!r}'.format(
163 name, old_name))
164 old_name = name
165 return result
167 def linux_kernel_version(cc):
168 """Return the (major, minor) version of the Linux kernel headers."""
169 sym_data = ['#include <linux/version.h>', 'START',
170 ('LINUX_VERSION_CODE', 'LINUX_VERSION_CODE')]
171 val = glibcextract.compute_c_consts(sym_data, cc)['LINUX_VERSION_CODE']
172 val = int(val)
173 return ((val & 0xff0000) >> 16, (val & 0xff00) >> 8)
175 class ArchSyscall:
176 """Canonical name and location of a syscall header."""
178 def __init__(self, name, path):
179 self.name = name
180 self.path = path
182 def __repr__(self):
183 return 'ArchSyscall(name={!r}, patch={!r})'.format(
184 self.name, self.path)
186 def list_arch_syscall_headers(topdir):
187 """A generator which returns all the ArchSyscall objects in a tree."""
189 sysdeps = os.path.join(topdir, 'sysdeps', 'unix', 'sysv', 'linux')
190 for root, dirs, files in os.walk(sysdeps):
191 if root != sysdeps:
192 for filename in files:
193 if filename == 'arch-syscall.h':
194 yield ArchSyscall(
195 name=os.path.relpath(root, sysdeps),
196 path=os.path.join(root, filename))
198 def __main():
199 """Entry point when called as the main program."""
201 import argparse
202 import sys
204 # Top-level directory of the source tree.
205 topdir = os.path.realpath(os.path.join(
206 os.path.dirname(os.path.realpath(__file__)), *('..',) * 4))
208 def get_parser():
209 parser = argparse.ArgumentParser(description=__doc__)
210 subparsers = parser.add_subparsers(dest='command', required=True)
211 subparsers.add_parser('list-headers',
212 help='Print the absolute paths of all arch-syscall.h header files')
213 subparser = subparsers.add_parser('query-syscall',
214 help='Summarize the implementation status of system calls')
215 subparser.add_argument('syscalls', help='Which syscalls to check',
216 nargs='+')
217 return parser
218 parser = get_parser()
219 args = parser.parse_args()
221 if args.command == 'list-headers':
222 for header in sorted([syscall.path for syscall
223 in list_arch_syscall_headers(topdir)]):
224 print(header)
226 elif args.command == 'query-syscall':
227 # List of system call tables.
228 tables = sorted(list_arch_syscall_headers(topdir),
229 key=lambda syscall: syscall.name)
230 for table in tables:
231 table.numbers = load_arch_syscall_header(table.path)
233 for nr in args.syscalls:
234 defined = [table.name for table in tables
235 if nr in table.numbers]
236 undefined = [table.name for table in tables
237 if nr not in table.numbers]
238 if not defined:
239 print('{}: not defined on any architecture'.format(nr))
240 elif not undefined:
241 print('{}: defined on all architectures'.format(nr))
242 else:
243 print('{}:'.format(nr))
244 print(' defined: {}'.format(' '.join(defined)))
245 print(' undefined: {}'.format(' '.join(undefined)))
247 else:
248 # Unrecognized command.
249 usage(1)
251 if __name__ == '__main__':
252 __main()