#!/bin/bash

#
# libseccomp syscall validation script
#
# Copyright (c) 2014 Red Hat <pmoore@redhat.com>
# Copyright (c) 2020 Cisco Systems, Inc. <pmoore2@cisco.com>
# Copyright (c) 2022 Microsoft Corporation. <paulmoore@microsoft.com>
#
# Author: Paul Moore <paul@paul-moore.com>
#

#
# This library is free software; you can redistribute it and/or modify it
# under the terms of version 2.1 of the GNU Lesser General Public License as
# published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, see <http://www.gnu.org/licenses>.
#

LIB_SYS_DUMP="./arch-syscall-dump"

####
# functions

#
# Dependency check
#
# Arguments:
#     1    Dependency to check for
#
function check_deps() {
	[[ -z "$1" ]] && return
	which "$1" >& /dev/null
	return $?
}

#
# Dependency verification
#
# Arguments:
#     1    Dependency to check for
#
function verify_deps() {
	[[ -z "$1" ]] && return
	if ! check_deps "$1"; then
		echo "error: install \"$1\" and include it in your \$PATH"
		exit 1
	fi
}

#
# Print out script usage details
#
function usage() {
cat << EOF
usage: arch-syscall-validate [-h] [-c <file>] [-a <arch>] <kernel_directory>

libseccomp syscall validation script
optional arguments:
  -h             show this help message and exit
  -a             architecture
  -l             output the library's syscall definitions
  -s             output the kernel's syscall definitions
  -c <file>      generate a CSV of the kernel's syscall definitions
EOF
}

#
# Dump the kernel version
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the kernel's version information to stdout.
#
function kernel_version() {
	local maj=$(cat $1/Makefile | \
		grep "^VERSION =" | awk -F "= " '{ print $2 }')
	local min=$(cat $1/Makefile | \
		grep "^PATCHLEVEL =" | awk -F "= " '{ print $2 }')
	local sub=$(cat $1/Makefile | \
		grep "^SUBLEVEL =" | awk -F "= " '{ print $2 }')
	local xtr=$(cat $1/Makefile | \
		grep "^EXTRAVERSION =" | awk -F "= " '{ print $2 }')
	echo "${maj}.${min}.${sub}${xtr}"
}

#
# Dump the library syscall table for a given architecture
#
# Arguments:
#     1    architecture
#     2    offset (optional)
#
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_arch() {
	local offset_str=""

	[[ -z $1 ]] && return

	[[ -n $2 ]] && offset_str="-o $2"
	$LIB_SYS_DUMP -a $1 $offset_str | sed 's/\t/,/' | sort
}

#
# Mangle the library pseudo syscall values
#
# Arguments:
#     1    architecture
#
#  Mangle the supplied pseudo syscall to match the system values
#
function mangle_lib_syscall() {
	local sed_filter=""

	sed_filter+='s/accept4,-118/accept4,364/;'
	sed_filter+='s/bind,-102/bind,361/;'
	sed_filter+='s/connect,-103/connect,362/;'
	sed_filter+='s/getpeername,-107/getpeername,368/;'
	sed_filter+='s/getsockname,-106/getsockname,367/;'
	sed_filter+='s/getsockopt,-115/getsockopt,365/;'
	sed_filter+='s/listen,-104/listen,363/;'
	sed_filter+='s/msgctl,-214/msgctl,402/;'
	sed_filter+='s/msgget,-213/msgget,399/;'
	sed_filter+='s/msgrcv,-212/msgrcv,401/;'
	sed_filter+='s/msgsnd,-211/msgsnd,400/;'
	sed_filter+='s/recvfrom,-112/recvfrom,371/;'
	sed_filter+='s/recvmsg,-117/recvmsg,372/;'
	sed_filter+='s/semctl,-203/semctl,394/;'
	sed_filter+='s/semget,-202/semget,393/;'
	sed_filter+='s/sendmsg,-116/sendmsg,370/;'
	sed_filter+='s/sendto,-111/sendto,369/;'
	sed_filter+='s/setsockopt,-114/setsockopt,366/;'
	sed_filter+='s/shmat,-221/shmat,397/;'
	sed_filter+='s/shmctl,-224/shmctl,396/;'
	sed_filter+='s/shmdt,-222/shmdt,398/;'
	sed_filter+='s/shmget,-223/shmget,395/;'
	sed_filter+='s/shutdown,-113/shutdown,373/;'
	sed_filter+='s/socket,-101/socket,359/;'
	sed_filter+='s/socketpair,-108/socketpair,360/;'

	case $1 in
	s390|s390x)
		sed_filter+='s/recvmmsg,-119/recvmmsg,357/;'
		sed_filter+='s/sendmmsg,-120/sendmmsg,358/;'
		;;
	*)
		sed_filter+='s/recvmmsg,-119/recvmmsg,337/;'
		sed_filter+='s/sendmmsg,-120/sendmmsg,345/;'
		;;
	esac

	sed $sed_filter | sed '/,-[0-9]\+$/d'
}

#
# Dump syscalls matching specified tags from the given syscall.tbl file
#
# Arguments:
#     1        path to the syscall.tbl file to dump
#     (rest)   tags to match (except "common" which is always included)
#
#  Dump the matched syscall table entries to stdout.
#
function dump_from_syscall_tbl() {
	local file="$1"
	shift

	local tag
	local tag_regexp='^(common'
	for tag in "$@"; do
		tag_regexp="${tag_regexp}|${tag}"
	done
	tag_regexp="${tag_regexp}) "

	cat "$file" | grep -v '^#\|^$' | awk '{ print $2,$3,$1 }' | \
		grep -E "$tag_regexp" | awk '{ print $2","$3 }' | sort | \
		grep -Ev '^(reserved|unused)[0-9]+,'
}

#
# Dump the x86 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_x86() {
	dump_from_syscall_tbl "$1/arch/x86/entry/syscalls/syscall_32.tbl" i386
}

#
# Dump the x86 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_x86() {
	dump_lib_arch x86 | mangle_lib_syscall x86
}

#
# Dump the x86_64 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_x86_64() {
	dump_from_syscall_tbl "$1/arch/x86/entry/syscalls/syscall_64.tbl" 64
}

#
# Dump the x86_64 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_x86_64() {
	dump_lib_arch x86_64 | mangle_lib_syscall x86_64
}

#
# Dump the x32 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_x32() {
	dump_from_syscall_tbl "$1/arch/x86/entry/syscalls/syscall_64.tbl" x32
}

#
# Dump the x32 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_x32() {
	dump_lib_arch x32 | mangle_lib_syscall x32
}

#
# Dump the arm system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_arm() {
	dump_from_syscall_tbl "$1/arch/arm/tools/syscall.tbl" eabi | (cat -; \
		(cat $1/arch/arm/include/uapi/asm/unistd.h | \
		grep "^#define __ARM_NR_" | \
		grep -v "^#define __ARM_NR_BASE" | \
		sed 's/#define __ARM_NR_\([a-z0-9_]*\)[ \t]\+(__ARM_NR_BASE+\(.*\))/\1 983040 + \2/' | \
		awk '{ print $1","$2+$4 }')) | sort
}

#
# Dump the arm library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_arm() {
	# NOTE: arm_sync_file_range() and sync_file_range2() share values
	dump_lib_arch arm | sed '/sync_file_range2,\+341/d' | \
		mangle_lib_syscall arm
}

#
# Dump the aarch64 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_aarch64() {
	local syscall_tbl_file="$1/arch/arm64/tools/syscall_64.tbl"
	if [[ -e $syscall_tbl_file ]]; then
		dump_from_syscall_tbl "$syscall_tbl_file" \
			64 renameat rlimit memfd_secret
		return
	fi

	local sed_filter=""

	sed_filter+='s/__NR3264_statfs/43/;'
	sed_filter+='s/__NR3264_ftruncate/46/;'
	sed_filter+='s/__NR3264_truncate/45/;'
	sed_filter+='s/__NR3264_lseek/62/;'
	sed_filter+='s/__NR3264_sendfile/71/;'
	sed_filter+='s/__NR3264_fstatat/79/;'
	sed_filter+='s/__NR3264_fstatfs/44/;'
	sed_filter+='s/__NR3264_fcntl/25/;'
	sed_filter+='s/__NR3264_fadvise64/223/;'
	sed_filter+='s/__NR3264_mmap/222/;'
	sed_filter+='s/__NR3264_fstat/80/;'
	sed_filter+='s/__NR3264_lstat/1039/;'
	sed_filter+='s/__NR3264_stat/1038/;'

	gcc -E -dM -I$1/include/uapi \
		-D__BITS_PER_LONG=64 -D__ARCH_WANT_RENAMEAT \
		-D__ARCH_WANT_NEW_STAT \
		$1/arch/arm64/include/uapi/asm/unistd.h | \
		grep "^#define __NR_" | \
		sed '/__NR_syscalls/d' | \
		sed '/__NR_arch_specific_syscall/d' | \
		sed 's/#define[ \t]\+__NR_\([^ \t]\+\)[ \t]\+\(.*\)/\1,\2/' | \
		sed $sed_filter | sort
}

#
# Dump the aarch64 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_aarch64() {
	dump_lib_arch aarch64 | mangle_lib_syscall aarch64
}

#
# Dump the loongarch64 syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_loongarch64() {
	if [[ -e $1/arch/loongarch/kernel/Makefile.syscalls ]]; then
		dump_from_syscall_tbl "$1/scripts/syscall.tbl" 64
		return
	fi

	local sed_filter=""

	sed_filter+='s/__NR3264_fadvise64/223/;'
	sed_filter+='s/__NR3264_fcntl/25/;'
	sed_filter+='s/__NR3264_fstatfs/44/;'
	sed_filter+='s/__NR3264_ftruncate/46/;'
	sed_filter+='s/__NR3264_lseek/62/;'
	sed_filter+='s/__NR3264_mmap/222/;'
	sed_filter+='s/__NR3264_sendfile/71/;'
	sed_filter+='s/__NR3264_statfs/43/;'
	sed_filter+='s/__NR3264_truncate/45/;'

	gcc -E -dM -I$1/include/uapi \
		-D__BITS_PER_LONG=64 \
		-D__ARCH_WANT_SYS_CLONE \
		-D__ARCH_WANT_SYS_CLONE3 \
		$1/arch/loongarch/include/uapi/asm/unistd.h | \
		grep "^#define __NR_" | \
		sed '/__NR_syscalls/d' | \
		sed '/__NR_arch_specific_syscall/d' | \
		sed 's/#define[ \t]\+__NR_\([^ \t]\+\)[ \t]\+\(.*\)/\1,\2/' | \
		sed $sed_filter | sort
}

#
# Dump the loongarch64 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_loongarch64() {
	dump_lib_arch loongarch64 | mangle_lib_syscall loongarch64
}

# Dump the m68k system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_m68k() {
	dump_from_syscall_tbl "$1/arch/m68k/kernel/syscalls/syscall.tbl"
}

#
# Dump the m68k library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_m68k() {
	dump_lib_arch m68k | mangle_lib_syscall m68k
}

#
# Dump the mips system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_mips() {
	dump_from_syscall_tbl "$1/arch/mips/kernel/syscalls/syscall_o32.tbl" o32
}

#
# Dump the mips library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_mips() {
	dump_lib_arch mips | mangle_lib_syscall mips
}

#
# Dump the mips64 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_mips64() {
	dump_from_syscall_tbl "$1/arch/mips/kernel/syscalls/syscall_n64.tbl" n64
}

#
# Dump the mips64 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_mips64() {
	dump_lib_arch mips64 | mangle_lib_syscall mips64
}

#
# Dump the mips64n32 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_mips64n32() {
	dump_from_syscall_tbl "$1/arch/mips/kernel/syscalls/syscall_n32.tbl" n32
}

#
# Dump the mips64n32 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_mips64n32() {
	dump_lib_arch mips64n32 | mangle_lib_syscall mips64n32
}

#
# Dump the parisc system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_parisc() {
	dump_from_syscall_tbl "$1/arch/parisc/kernel/syscalls/syscall.tbl" 32
}

#
# Dump the parisc library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_parisc() {
	dump_lib_arch parisc | mangle_lib_syscall parisc
}

#
# Dump the parisc64 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_parisc64() {
	dump_from_syscall_tbl "$1/arch/parisc/kernel/syscalls/syscall.tbl" 64
}

#
# Dump the parisc64 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_parisc64() {
	dump_lib_arch parisc64 | mangle_lib_syscall parisc64
}

#
# Dump the ppc system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_ppc() {
	dump_from_syscall_tbl "$1/arch/powerpc/kernel/syscalls/syscall.tbl" \
		nospu 32
}

#
# Dump the ppc library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_ppc() {
	dump_lib_arch ppc | mangle_lib_syscall ppc
}

#
# Dump the ppc64 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_ppc64() {
	dump_from_syscall_tbl "$1/arch/powerpc/kernel/syscalls/syscall.tbl" \
		nospu 64
}

#
# Dump the ppc64 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_ppc64() {
	dump_lib_arch ppc64 | mangle_lib_syscall ppc64
}

#
# Dump the riscv64 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_riscv64() {
	if [[ -e $1/arch/riscv/kernel/Makefile.syscalls ]]; then
		dump_from_syscall_tbl "$1/scripts/syscall.tbl" \
			64 riscv rlimit memfd_secret
		return
	fi

	local sed_filter=""

	sed_filter+='s/__NR3264_fadvise64/223/;'
	sed_filter+='s/__NR3264_fcntl/25/;'
	sed_filter+='s/__NR3264_fstatat/79/;'
	sed_filter+='s/__NR3264_fstatfs/44/;'
	sed_filter+='s/__NR3264_ftruncate/46/;'
	sed_filter+='s/__NR3264_lseek/62/;'
	sed_filter+='s/__NR3264_mmap/222/;'
	sed_filter+='s/__NR3264_sendfile/71/;'
	sed_filter+='s/__NR3264_statfs/43/;'
	sed_filter+='s/__NR3264_truncate/45/;'
	sed_filter+='s/__NR3264_fstat/80/;'

	gcc -E -dM -I$1/include/uapi \
		-D__BITS_PER_LONG=64 -D__ARCH_WANT_NEW_STAT \
		$1/arch/riscv/include/uapi/asm/unistd.h | \
		grep "^#define __NR_" | \
		sed '/__NR_syscalls/d' | \
		sed 's/(__NR_arch_specific_syscall + 15)/259/' | \
		sed '/__NR_arch_specific_syscall/d' | \
		sed 's/#define[ \t]\+__NR_\([^ \t]\+\)[ \t]\+\(.*\)/\1,\2/' | \
		sed $sed_filter | sort
}

#
# Dump the riscv64 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_riscv64() {
	dump_lib_arch riscv64 | mangle_lib_syscall riscv64
}

#
# Dump the s390 system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_s390() {
	dump_from_syscall_tbl "$1/arch/s390/kernel/syscalls/syscall.tbl" 32
}

#
# Dump the s390 library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_s390() {
	dump_lib_arch s390 | mangle_lib_syscall s390
}

#
# Dump the s390x system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_s390x() {
	dump_from_syscall_tbl "$1/arch/s390/kernel/syscalls/syscall.tbl" 64
}

#
# Dump the s390x library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_s390x() {
	dump_lib_arch s390x | mangle_lib_syscall s390x
}

#
# Dump the sh system syscall table
#
# Arguments:
#     1    path to the kernel source
#
#  Dump the architecture's syscall table to stdout.
#
function dump_sys_sh() {
	dump_from_syscall_tbl "$1/arch/sh/kernel/syscalls/syscall.tbl"
}

#
# Dump the sh library syscall table
#
#  Dump the library's syscall table to stdout.
#
function dump_lib_sh() {
	dump_lib_arch sh | mangle_lib_syscall sh
}

#
# Dump the system syscall table
#
# Arguments:
#     1    architecture
#     2    path to the kernel source
#
#  Dump the system's syscall table to stdout using the given architecture.
#
function dump_sys() {
	case $1 in
	x86)
		dump_sys_x86 "$2"
		;;
	x86_64)
		dump_sys_x86_64 "$2"
		;;
	x32)
		dump_sys_x32 "$2"
		;;
	arm)
		dump_sys_arm "$2"
		;;
	aarch64)
		dump_sys_aarch64 "$2"
		;;
	loongarch64)
		dump_sys_loongarch64 "$2"
		;;
	m68k)
		dump_sys_m68k "$2"
		;;
	mips)
		dump_sys_mips "$2"
		;;
	mips64)
		dump_sys_mips64 "$2"
		;;
	mips64n32)
		dump_sys_mips64n32 "$2"
		;;
	parisc)
		dump_sys_parisc "$2"
		;;
	parisc64)
		dump_sys_parisc64 "$2"
		;;
	ppc)
		dump_sys_ppc "$2"
		;;
	ppc64)
		dump_sys_ppc64 "$2"
		;;
	riscv64)
		dump_sys_riscv64 "$2"
		;;
	s390)
		dump_sys_s390 "$2"
		;;
	s390x)
		dump_sys_s390x "$2"
		;;
	sh)
		dump_sys_sh "$2"
		;;
	*)
		echo ""
		return 1
		;;
	esac

	return 0
}

#
# Dump the library syscall table
#
# Arguments:
#     1    architecture
#
#  Dump the library's syscall table to stdout using the given architecture.
#
function dump_lib() {
	case $1 in
	x86)
		dump_lib_x86
		;;
	x86_64)
		dump_lib_x86_64
		;;
	x32)
		dump_lib_x32
		;;
	arm)
		dump_lib_arm
		;;
	aarch64)
		dump_lib_aarch64
		;;
	loongarch64)
		dump_lib_loongarch64
		;;
	m68k)
		dump_lib_m68k
		;;
	mips)
		dump_lib_mips
		;;
	mips64)
		dump_lib_mips64
		;;
	mips64n32)
		dump_lib_mips64n32
		;;
	parisc)
		dump_lib_parisc
		;;
	parisc64)
		dump_lib_parisc64
		;;
	ppc)
		dump_lib_ppc
		;;
	ppc64)
		dump_lib_ppc64
		;;
	riscv64)
		dump_lib_riscv64
		;;
	s390)
		dump_lib_s390
		;;
	s390x)
		dump_lib_s390x
		;;
	sh)
		dump_lib_sh
		;;
	*)
		echo ""
		return 1
		;;
	esac

	return 0
}

#
# Generate the syscall csv file
#
# Arguments:
#     1    path to the kernel source
#     2    "sys" or "lib" depending on the syscall list to use
#     3    csv file to use as input for saved fields
#
#   Generate a syscall csv file from the given kernel sources.
#
function gen_csv() {

	# sanity checks
	[[ -z $1 ]] && return
	[[ $2 != "sys" && $2 != "lib" ]] && return

	# abi list
	# NOTE: the ordering here is dependent on the layout of the
	#       arch_syscall_table struct in syscalls.h - BEWARE!
	abi_list=""
	abi_list+=" x86 x86_64 x32"
	abi_list+=" arm aarch64"
	abi_list+=" loongarch64"
	abi_list+=" m68k"
	abi_list+=" mips mips64 mips64n32"
	abi_list+=" parisc parisc64"
	abi_list+=" ppc ppc64"
	abi_list+=" riscv64"
	abi_list+=" s390 s390x"
	abi_list+=" sh"

	# read the csv to get the existing data
	local -A csv
	local csv_has_kver=0
	local csv_input="$3"
	[[ ! -e "$3" ]] && csv_input=/dev/null
	grep -q "KV_" "$csv_input" && csv_has_kver=1
	while read line; do
		sc=$(echo $line | cut -d, -f 1)
		local field=2
		for abi in $abi_list; do
			csv[$sc,$abi]=$(echo $line | cut -d, -f $field)
			((field++))
			if [[ $csv_has_kver -eq 1 ]]; then
				csv[$sc,${abi}_KVER]=$(echo $line | \
						       cut -d, -f $field)
				((field++))
			fi
		done
	done < <(sed 's/#.*//;/^[ \t]*$/d' "$csv_input")

	# get the full syscall list
	for abi in $abi_list; do
		eval output_$abi=$(mktemp -t syscall_validate_XXXXXX)
		dump_$2_$abi "$1" > $(eval echo $`eval echo output_$abi`)
	done
	sc_list=$( (for abi in $abi_list; do
			cat $(eval echo $`eval echo output_$abi`);
		    done) | awk -F "," '{ print $1 }' | sort -u)

	# redirect the subshell to the csv file
	(
		# output a simple header
		printf "#syscall (v%s %s)" \
			"$(kernel_version "$1")" "$(TZ=UTC date "+%Y-%m-%d")"
		for abi in $abi_list; do
			printf ",%s,%s_kver" $abi $abi
		done
		printf "\n"

		# output the syscall csv details
		for sc in $sc_list; do
			printf "%s" $sc
			for abi in $abi_list; do
				num=$(grep "^$sc," \
				      $(eval echo $`eval echo output_$abi`) | \
				awk -F "," '{ print $2 }' )
				kver=${csv[$sc,${abi}_KVER]}
				[[ -z $num ]] && num="PNR"
				[[ -z $kver ]] && kver="KV_UNDEF"
				printf ",%s,%s" $num $kver
			done
			printf "\n"
		done
	) > "$3"

	# cleanup
	for abi in $abi_list; do
		rm -f $(eval echo $`eval echo output_$abi`)
	done
}

####
# main

verify_deps diff
verify_deps gcc
verify_deps grep
verify_deps mktemp
verify_deps sed

opt_arches=""
opt_csv=""
opt_sys=0
opt_lib=0

while getopts "a:c:slh" opt; do
	case $opt in
	a)
		opt_arches+="$OPTARG "
		;;
	c)
		opt_csv="$OPTARG"
		;;
	s)
		opt_sys=1
		opt_lib=0
		;;
	l)
		opt_sys=0
		opt_lib=1
		;;
	h|*)
		usage
		exit 1
		;;
	esac
done
shift $(($OPTIND - 1))

# defaults
if [[ $opt_arches == "" ]]; then
	opt_arches=" \
		x86 x86_64 x32 \
		arm aarch64 \
		loongarch64 \
		m68k \
		mips mips64 mips64n32 \
		parisc parisc64 \
		ppc ppc64 \
		s390 s390x \
		sh"
fi

# sanity checks
kernel_dir="$1"
if [[ -z $kernel_dir ]]; then
	usage
	exit 1
fi
if [[ ! -d $kernel_dir ]]; then
	echo "error: \"$1\" is not a valid directory"
	exit 1
fi

if [[ ! -x "$LIB_SYS_DUMP" ]]; then
	echo "error: \"$LIB_SYS_DUMP\" is not present"
	exit 1
fi

# generate some temp files
tmp_lib=$(mktemp -t syscall_validate_XXXXXX)
tmp_sys=$(mktemp -t syscall_validate_XXXXXX)

if [[ -n $opt_csv ]]; then
	# generate the syscall csv file
	if [[ $opt_lib -eq 1 ]]; then
		gen_csv $kernel_dir "lib" $opt_csv
	else
		gen_csv $kernel_dir "sys" $opt_csv
	fi
else
	# loop through the architectures and compare
	for i in $opt_arches; do
		# dump the syscall tables
		dump_lib $i > $tmp_lib
		if [[ $? -ne 0 ]]; then
			echo "error: unknown arch $i"
			exit 1
		fi
		dump_sys $i "$kernel_dir" > $tmp_sys
		if [[ $? -ne 0 ]]; then
			echo "error: unknown arch $i"
			exit 1
		fi

		if [[ $opt_lib -eq 1 ]]; then
			cat $tmp_lib
		elif [[ $opt_sys -eq 1 ]]; then
			cat $tmp_sys
		else
			#  compare the lib and sys output
			diff -u --label="$i [library]" $tmp_lib \
				--label "$i [system]" $tmp_sys
		fi
	done
fi

# cleanup and exit
rm -f $tmp_lib $tmp_sys

exit 0
