Skip to content

Commit 2bc7189

Browse files
drakenclimberpcmoore
authored andcommitted
arch: Add a script to update syscalls.csv
Add a script that will update syscalls.csv with syscalls and syscall numbers from newer kernel versions. For example, say that syscalls.csv is current up to kernel version v6.13. To update it to v6.16, then the following command would be run: $ time ./src/arch-build-kver-tables.py -d {syscalls_path} -k {kernel path} -V 6.14,6.15,6.16 Building version table for kernel 6.14 Building version table for kernel 6.15 Building version table for kernel 6.16 real 1m5.221s user 0m30.809s sys 0m33.508s $ ./src/arch-update-syscalls-csv.py -a -d {path to tables} -k {kernel path} -c src/syscalls.csv -V 6.14,6.15,6.16 Signed-off-by: Tom Hromatka <[email protected]> Signed-off-by: Paul Moore <[email protected]>
1 parent 5955f12 commit 2bc7189

File tree

1 file changed

+330
-0
lines changed

1 file changed

+330
-0
lines changed

src/arch-update-syscalls-csv.py

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
#!/usr/bin/env python3
2+
3+
#
4+
# Seccomp Library program to update the syscalls.csv file
5+
#
6+
# Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
7+
# Author: Tom Hromatka <[email protected]>
8+
#
9+
10+
#
11+
# This library is free software; you can redistribute it and/or modify it
12+
# under the terms of version 2.1 of the GNU Lesser General Public License as
13+
# published by the Free Software Foundation.
14+
#
15+
# This library is distributed in the hope that it will be useful, but WITHOUT
16+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
18+
# for more details.
19+
#
20+
# You should have received a copy of the GNU Lesser General Public License
21+
# along with this library; if not, see <http://www.gnu.org/licenses>.
22+
#
23+
24+
import subprocess
25+
import datetime
26+
import argparse
27+
import sys
28+
import os
29+
30+
arch_list = [
31+
'i386', 'x86_64', 'x32', 'arm', 'arm64', 'loongarch64', 'm68k',
32+
'mipso32', 'mips64', 'mips64n32', 'parisc', 'parisc64', 'powerpc',
33+
'powerpc64', 'riscv64', 's390', 's390x', 'sh'
34+
]
35+
36+
ignore_syscall_list = [
37+
'arc_gettls', 'arc_settls', 'arc_usr_cmpxchg', 'bfin_spinlock',
38+
'cache_sync', 'clone2', 'cmpxchg_badaddr', 'dipc', 'dma_memcpy',
39+
'exec_with_loader', 'execv', 'file_getattr', 'file_setattr',
40+
'flush_cache', 'fp_udfiex_crtl', 'getdomainname', 'getdtablesize',
41+
'gethostname', 'getunwind', 'getxgid', 'getxpid', 'getxuid',
42+
'kern_features', 'llseek', 'madvise1', 'memory_ordering', 'metag_get_tls',
43+
'metag_set_fpu_flags', 'metag_set_tls', 'metag_setglobalbit',
44+
'mq_getsetaddr', 'old_adjtimex', 'old_getpagesize', 'oldumount',
45+
'or1k_atomic', 'osf_fstat', 'osf_fstatfs', 'osf_fstatfs64',
46+
'osf_getdirentries', 'osf_getdomainname', 'osf_getitimer',
47+
'osf_getrusage', 'osf_getsysinfo', 'osf_gettimeofday', 'osf_lstat',
48+
'osf_mount', 'osf_proplist_syscall', 'osf_select',
49+
'osf_set_program_attributes', 'osf_setitimer', 'osf_setsysinfo',
50+
'osf_settimeofday', 'osf_shmat', 'osf_sigprocmask', 'osf_sigstack',
51+
'osf_stat', 'osf_statfs', 'osf_statfs64', 'osf_swapon', 'osf_syscall',
52+
'osf_sysinfo', 'osf_usleep_thread', 'osf_utimes', 'osf_utsname',
53+
'osf_wait4', 'perfctr', 'perfmonctl', 'pread', 'pwrite',
54+
'sched_get_affinity', 'sched_set_affinity', 'sethae', 'setpgrp',
55+
'shmatcall', 'sram_alloc', 'sram_free', 'streams1', 'streams2',
56+
'sys_epoll_create', 'sys_epoll_ctl', 'sys_epoll_wait', 'tas', 'udftrap',
57+
'utrap_install'
58+
]
59+
60+
def parse_args():
61+
parser = argparse.ArgumentParser('Script to update the syscalls.csv kernel versions',
62+
formatter_class=argparse.RawTextHelpFormatter)
63+
parser.add_argument('-d', '--datapath', required=True, type=str, default=None,
64+
help="Path to the directory where arch-build-kver-tables.py "
65+
'output the version tables')
66+
parser.add_argument('-k', '--kernelpath', required=True, type=str, default=None,
67+
help="Path to the kernel source directory")
68+
parser.add_argument('-c', '--csv', required=False, type=str,
69+
default='src/syscalls.csv',
70+
help='Path to the the syscalls csv file')
71+
parser.add_argument('-V', '--versions', required=True, type=str, default=None,
72+
help="Comma-separated list of kernel versions to update, e.g "
73+
"6.17,6.18")
74+
parser.add_argument('-v', '--verbose', action='store_true',
75+
help='Show verbose warnings')
76+
parser.add_argument('-a', '--add', action='store_true',
77+
help='Add newly discovered syscalls to the csv')
78+
79+
args = parser.parse_args()
80+
args.versions = args.versions.split(',')
81+
82+
return args
83+
84+
def run(command, verbose=False, shell=False, timeout=None):
85+
if shell:
86+
if isinstance(command, str):
87+
# nothing to do. command is already formatted as a string
88+
pass
89+
elif isinstance(command, list):
90+
command = ' '.join(command)
91+
else:
92+
raise ValueError('Unsupported command type')
93+
94+
subproc = subprocess.Popen(command, shell=shell,
95+
stdout=subprocess.PIPE,
96+
stderr=subprocess.PIPE)
97+
98+
if timeout:
99+
try:
100+
out, err = subproc.communicate(timeout=timeout)
101+
ret = subproc.returncode
102+
103+
out = out.strip().decode('UTF-8')
104+
err = err.strip().decode('UTF-8')
105+
except TimeoutExpired as timeout:
106+
if timeout.stdout:
107+
out = timeout.stdout.strip().decode('UTF-8')
108+
else:
109+
out = ''
110+
if timeout.stderr:
111+
err = timeout.stderr.strip().decode('UTF-8')
112+
else:
113+
err = ''
114+
115+
if len(err):
116+
ret = -1
117+
else:
118+
ret = 0
119+
else:
120+
out, err = subproc.communicate()
121+
ret = subproc.returncode
122+
123+
out = out.strip().decode('UTF-8')
124+
err = err.strip().decode('UTF-8')
125+
126+
if verbose:
127+
if not shell:
128+
command = ' '.join(command)
129+
print('run:\n\tcmd = {}\n\tret = {}\n\tstdout = {}\n\tstderr = {}\n'.format(
130+
command, ret, out, err))
131+
132+
return ret, out, err
133+
134+
def get_kernel_ver(args):
135+
makefile = os.path.join(args.kernelpath, 'Makefile')
136+
137+
with open(makefile, 'r') as mkf:
138+
for line in mkf:
139+
140+
if line.startswith('VERSION'):
141+
maj = int(line.split('=')[1].strip())
142+
elif line.startswith('PATCHLEVEL'):
143+
mnr = int(line.split('=')[1].strip())
144+
elif line.startswith('SUBLEVEL'):
145+
sub = int(line.split('=')[1].strip())
146+
elif line.startswith('EXTRAVERSION'):
147+
xtr = line.split('=')[1].strip()
148+
149+
return maj, mnr, sub, xtr
150+
151+
def build_header(args, columns):
152+
maj, mnr, sub, xtr = get_kernel_ver(args)
153+
date = datetime.datetime.now().strftime("%Y-%m-%d")
154+
header = '#syscall (v{}.{}.{}{} {})'.format(maj, mnr, sub, xtr, date)
155+
156+
for col in columns:
157+
header = header + ',{}'.format(col)
158+
159+
return header
160+
161+
def parse_syscalls_csv(args):
162+
column_order = list()
163+
syscalls = dict()
164+
165+
with open(args.csv, 'r') as csvf:
166+
for line_idx, line in enumerate(csvf):
167+
if line_idx == 0:
168+
for col_idx, col_name in enumerate(line.split(',')):
169+
if col_idx == 0:
170+
continue
171+
else:
172+
column_order.append(col_name.strip())
173+
else:
174+
for col_idx, col_value in enumerate(line.split(',')):
175+
if col_idx == 0:
176+
syscall_name = col_value
177+
syscalls[syscall_name] = list()
178+
else:
179+
syscalls[syscall_name].append(col_value.strip())
180+
181+
return column_order, syscalls
182+
183+
def insert_new_syscall(syscalls, syscall_name, column_cnt):
184+
inserted = False
185+
186+
for syscall in syscalls:
187+
if syscall_name < syscall:
188+
idx = list(syscalls.keys()).index(syscall)
189+
syscalls_list = list(syscalls.items())
190+
syscalls_list.insert(idx, (syscall_name, ['PNR'] * column_cnt))
191+
syscalls = dict(syscalls_list)
192+
inserted = True
193+
break
194+
195+
if not inserted:
196+
syscalls[syscall_name] = ['PNR'] * column_cnt
197+
198+
return syscalls
199+
200+
def update_syscalls_dict(args, columns, syscalls, kver):
201+
for col_idx, column in enumerate(columns):
202+
if 'kver' in column:
203+
# Only operate on the columns with syscall numbers. The
204+
# kernel version columns always immediately follow the syscall
205+
# number columns
206+
continue
207+
208+
if column == 'x86':
209+
arch = 'i386'
210+
elif column == 'aarch64':
211+
arch = 'arm64'
212+
elif column == 'mips':
213+
arch = 'mipso32'
214+
elif column == 'ppc':
215+
arch = 'powerpc'
216+
elif column == 'ppc64':
217+
arch = 'powerpc64'
218+
else:
219+
arch = column
220+
221+
table_path = os.path.join(args.datapath, 'tables-{}'.format(kver),
222+
'syscalls-{}'.format(arch))
223+
224+
with open(table_path, 'r') as tblf:
225+
for line in tblf:
226+
if line.startswith('HPUX_'):
227+
continue
228+
229+
if len(line.split()) == 1:
230+
syscall_name = line.strip()
231+
if syscall_name.startswith('HPUX'):
232+
continue
233+
if syscall_name in ignore_syscall_list:
234+
continue
235+
236+
if syscall_name not in syscalls:
237+
if args.verbose:
238+
print('syscall {} is not in csv'.format(
239+
syscall_name))
240+
241+
if args.verbose:
242+
print('syscall {} is undefined in {} for kernel v{}'.
243+
format(line.strip(), column, kver))
244+
245+
if syscall_name in syscalls and \
246+
not syscalls[syscall_name][col_idx] == 'PNR':
247+
# This syscall had a syscall number in an earlier
248+
# table, but this table doesn't have one. Don't
249+
# remove the previous number
250+
continue
251+
252+
if args.add:
253+
if not syscall_name in syscalls:
254+
# This is a new syscall for this kernel version
255+
syscalls = insert_new_syscall(syscalls,
256+
syscall_name, len(columns))
257+
258+
syscalls[syscall_name][col_idx] = 'PNR'
259+
syscalls[syscall_name][col_idx + 1] = 'SCMP_KV_UNDEF'
260+
else:
261+
syscall_name = line.split()[0].strip()
262+
syscall_num = int(line.split()[1].strip())
263+
264+
if arch == 'mipso32':
265+
syscall_num -= 4000
266+
elif arch == 'mips64':
267+
syscall_num -= 5000
268+
elif arch == 'mips64n32':
269+
syscall_num -= 6000
270+
elif arch == 'x32' and syscall_num >= 0x40000000:
271+
syscall_num = syscall_num - 0x40000000
272+
273+
if syscall_name in ignore_syscall_list:
274+
continue
275+
276+
if syscall_name not in syscalls:
277+
if args.verbose:
278+
print('syscall {} is not in csv'.format(
279+
syscall_name))
280+
281+
if args.add:
282+
syscalls = insert_new_syscall(syscalls,
283+
syscall_name, len(columns))
284+
else:
285+
continue
286+
287+
if syscalls[syscall_name][col_idx] == 'PNR':
288+
if args.verbose:
289+
print('adding syscall {} to {} in kernel v{}'.
290+
format(syscall_name, column, kver))
291+
292+
syscalls[syscall_name][col_idx] = str(syscall_num)
293+
maj = kver.split('.')[0]
294+
mnr = kver.split('.')[1]
295+
syscalls[syscall_name][col_idx + 1] = \
296+
'SCMP_KV_{}_{}'.format(maj, mnr)
297+
298+
return syscalls
299+
300+
def write_csv(args, columns, syscalls):
301+
with open(args.csv, 'w') as csvf:
302+
csvf.write(build_header(args, columns))
303+
csvf.write('\n')
304+
305+
for syscall in syscalls:
306+
csvf.write('{},'.format(syscall))
307+
csvf.write(','.join(syscalls[syscall]))
308+
csvf.write('\n')
309+
310+
def main(args):
311+
for kver in args.versions:
312+
print('Updating {} version table for kernel {}'.format(args.csv, kver))
313+
314+
checkout_cmd = 'cd {};git checkout v{}'.format(args.kernelpath, kver)
315+
ret, out, err = run(checkout_cmd, shell=True)
316+
if ret != 0:
317+
raise KeyError('Failed to checkout v{}: {}'.format(kver, ret))
318+
319+
columns, syscalls = parse_syscalls_csv(args)
320+
syscalls = update_syscalls_dict(args, columns, syscalls, kver)
321+
write_csv(args, columns, syscalls)
322+
323+
if __name__ == '__main__':
324+
if sys.version_info < (3, 7):
325+
# Guaranteed dictionary ordering was added in python 3.7
326+
print("This script requires Python 3.7 or higher.")
327+
sys.exit(1)
328+
329+
args = parse_args()
330+
main(args)

0 commit comments

Comments
 (0)