#!/bin/sh -eu
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (c) Linux Test Project, 2009-2024
# Copyright (c) Marcin Juszkiewicz, 2023-2024
#
# This is an adaptation of the update-tables.sh script, included in the
# syscalls-table project (https://github.com/hrw/syscalls-table) and released
# under the MIT license.
#
# Author: Andrea Cervesato <andrea.cervesato@suse.com>

if [ "$#" -eq "0" ]; then
	echo "Please provide kernel sources:"
	echo ""
	echo "$0 path/to/Linux/kernel/sources"
	echo ""
	exit 1
fi

KERNELSRC="$1"

# to keep sorting in order
export LC_ALL=C

if [ ! -d "${KERNELSRC}" ]; then
	echo "${KERNELSRC} is not a directory"
	exit 1
fi

if [ ! -e "${KERNELSRC}/Makefile" ]; then
	echo "No Makefile in ${KERNELSRC} directory"
	exit 1
fi

TEMP="$(mktemp -d)"
KVER="$(make -C ${KERNELSRC} kernelversion -s)"

SCRIPT_DIR="$(realpath $(dirname "$0"))"
SUPPORTED_ARCH="${SCRIPT_DIR}/supported-arch.txt"
LINUX_HEADERS="${TEMP}/headers"

grab_syscall_names_from_tables() {
	for tbl_file in $(find ${KERNELSRC}/arch -name syscall*.tbl); do
		grep -E -v "(^#|^$|sys_ni_syscall)" $tbl_file |
			awk '{ print $3 }' >>${TEMP}/syscall-names.tosort
	done

	drop_bad_entries
}

grab_syscall_names_from_unistd_h() {
	grep -E -h "^#define __NR_" \
		${LINUX_HEADERS}/usr/include/asm/unistd*.h \
		${LINUX_HEADERS}/usr/include/asm-generic/unistd.h \
		>${TEMP}/syscall-names.tosort

	drop_bad_entries
}

drop_bad_entries() {
	grep -E -v "(unistd.h|NR3264|__NR_syscall|__SC_COMP|__NR_.*Linux|__NR_FAST)" \
		${TEMP}/syscall-names.tosort |
		grep -E -v "(__SYSCALL|SYSCALL_BASE|SYSCALL_MASK)" |
		sed -e "s/#define\s*__NR_//g" -e "s/\s.*//g" |
		sort -u >${TEMP}/syscall-names.txt
}

generate_table() {
	echo "- $arch"

	if [ "$bits" -eq "32" ]; then
		extraflags="${extraflags} -D__BITS_PER_LONG=32"
	fi

	local uppercase_arch=$(echo "$arch" | tr '[:lower:]' '[:upper:]')

	# ignore any error generated by gcc. We want to obtain all the
	# available architecture syscalls for the current platform and to handle
	# only supported architectures later on
	gcc ${TEMP}/list-syscalls.c -U__LP64__ -U__ILP32__ -U__i386__ \
		-D${uppercase_arch} \
		-D__${arch}__ ${extraflags} \
		-I ${LINUX_HEADERS}/usr/include/ \
		-o ${TEMP}/list-syscalls || true

	${TEMP}/list-syscalls >"${TEMP}/${arch}.in.tosort"

	sort -k2,2n "${TEMP}/${arch}.in.tosort" >"${TEMP}/${arch}.in"
}

generate_list_syscalls_c() {
	(
		printf "
		#include <stdio.h>
		#include <asm/unistd.h>

		int main(void)
		{
		"
		for syscall in $(cat ${TEMP}/syscall-names.txt); do
			printf "
		#ifdef __NR_$syscall
			printf(\"$syscall %%d"
			# i know the following print is ugly, but dash and bash
			# treat double quoted strings in a different way and we
			# really need to inject '\n' character in the C code
			# rather than carriage return
			printf '\\n'
			printf "\", __NR_$syscall);
		#endif
		"
		done
		printf " return 0;
		}"
	) >${TEMP}/list-syscalls.c
}

export_headers() {
	make -s -C ${KERNELSRC} ARCH=${arch} O=${LINUX_HEADERS} \
		headers_install >/dev/null 2>&1
}

do_all_tables() {
	for archdir in ${KERNELSRC}/arch/*; do
		arch=$(basename $archdir)

		bits=64
		extraflags=

		case ${arch} in
		Kconfig)
			continue
			;;
		um)
			continue
			;;
		esac

		export_headers
		grab_syscall_names_from_unistd_h

		case ${arch} in
		arm)
			bits=32
			arch=armoabi extraflags= generate_table
			arch=arm extraflags=-D__ARM_EABI__ generate_table
			;;
		loongarch)
			# 32-bit variant of loongarch may appear
			arch=loongarch64 extraflags=-D_LOONGARCH_SZLONG=64 generate_table
			;;
		mips)
			arch=mips64 extraflags=-D_MIPS_SIM=_MIPS_SIM_ABI64 generate_table
			bits=32
			arch=mipso32 extraflags=-D_MIPS_SIM=_MIPS_SIM_ABI32 generate_table
			arch=mips64n32 extraflags=-D_MIPS_SIM=_MIPS_SIM_NABI32 generate_table
			;;
		powerpc)
			generate_table
			arch=powerpc64 generate_table
			;;
		riscv)
			arch=riscv64 extraflags=-D__LP64__ generate_table
			bits=32
			arch=riscv32 extraflags=-D__SIZEOF_POINTER__=4 generate_table
			;;
		s390)
			bits=32
			generate_table
			bits=64
			arch=s390x generate_table
			;;
		sparc)
			bits=32
			extraflags=-D__32bit_syscall_numbers__ generate_table
			bits=64
			arch=sparc64 extraflags=-D__arch64__ generate_table
			;;
		x86)
			arch=x86_64 extraflags=-D__LP64__ generate_table
			bits=32
			arch=i386 generate_table
			arch=x32 extraflags=-D__ILP32__ generate_table
			;;
		arc | csky | hexagon | m68k | microblaze | nios2 | openrisc | sh | xtensa)
			bits=32 generate_table
			;;
		*)
			generate_table
			;;
		esac
	done
}

copy_supported_arch() {
	while IFS= read -r arch; do
		if [ -f "${TEMP}/${arch}.in" ]; then
			echo "- ${arch}"
			cp "${TEMP}/${arch}.in" "${SCRIPT_DIR}/${arch}.in"
		fi
	done <${SUPPORTED_ARCH}
}

echo "Temporary directory ${TEMP}"
echo "Extracting syscalls"

grab_syscall_names_from_tables
generate_list_syscalls_c

do_all_tables

echo "Copying supported syscalls"
copy_supported_arch
