#!/usr/bin/env python3
import re
import sys
import pathlib

# Base path for Linux Kernel sources
BASE = (sys.argv[1] if len(sys.argv) > 1 else '.')

SOURCES = [
    ('b32/arm',         {'common'},                 'arch/arm/tools/syscall.tbl'),
    ('b32/sparc',       {'common', '32'},           'arch/sparc/kernel/syscalls/syscall.tbl'),
    ('b32/x86',         {'i386'},                   'arch/x86/entry/syscalls/syscall_32.tbl'),
    ('b32/powerpc',     {'common', 'nospu', '32'},  'arch/powerpc/kernel/syscalls/syscall.tbl'),
    ('b32/mips',        {'o32'},                    'arch/mips/kernel/syscalls/syscall_o32.tbl'),
    ('b64/x86_64',      {'common', '64'},           'arch/x86/entry/syscalls/syscall_64.tbl'),
    ('b64/powerpc64',   {'common', 'nospu', '64'},  'arch/powerpc/kernel/syscalls/syscall.tbl'),
    ('b64/s390x',       {'common', '64'},           'arch/s390/kernel/syscalls/syscall.tbl'),
    ('b64/sparc64',     {'common', '64'},           'arch/sparc/kernel/syscalls/syscall.tbl'),
    ('b64/mips64',      {'n64'},                    'arch/mips/kernel/syscalls/syscall_n64.tbl'),
]

def header(o):
    print('/// An enum of all syscalls', file=o)
    print('#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString)]', file=o)
    print('#[allow(non_camel_case_types)]', file=o)
    print('#[non_exhaustive]', file=o)
    print('pub enum Syscall {', file=o)

def footer(o):
    print('}', file=o)

def convert_tbl(out, f):
    with open(out, 'w') as o:
        header(o)
        for l in f:
            m = re.match('^(\\d+)\\s+(\\S+)\\s+(\\S+)(?:\\s+(\\S+))?', l)
            if m:
                nr, abi, name, entrypoint = m.groups()
                if name in {'break'}:
                    name = '_' + name
                if abi in abis:
                    print(name + ' = ' + nr + ',', file=o)
        footer(o)

UNISTD_MAP64 = {
    'fcntl': 'fcntl',
    'statfs': 'statfs',
    'fstatfs': 'fstatfs',
    'truncate': 'truncate',
    'ftruncate': 'ftruncate',
    'lseek': 'lseek',
    'sendfile': 'sendfile',
    'fstatat': 'newfstatat',
    'fstat': 'fstat',
    'mmap': 'mmap',
    'fadvise64': 'fadvise64',
    'stat': 'stat',
    'lstat': 'lstat',
}

UNISTD_MAP32 = {
    'fcntl':'fcntl64',
    'statfs':'statfs64',
    'fstatfs':'fstatfs64',
    'truncate':'truncate64',
    'ftruncate':'ftruncate64',
    'lseek':'llseek',
    'sendfile':'sendfile64',
    'fstatat':'fstatat64',
    'fstat':'fstat64',
    'mmap':'mmap2',
    'fadvise64':'fadvise64_64',
    'stat':'stat64',
    'lstat':'lstat64',
}

UNISTD_SOURCES = [
    ('b64/riscv64', UNISTD_MAP64),
    ('b64/aarch64', UNISTD_MAP64),
    ('b64/loongarch64', UNISTD_MAP64),
]

def convert_unistd(out, unistd_map, f):
    with open(out, 'w') as o:
        header(o)
        uniq = set()
        for l in f:
            m = re.match('#define __NR(3264)?_([^ ]+)\\s+([0-9]+)$', l)
            if m:
                name = m[2]
                nr = m[3]
                if name in {'syscalls', 'arch_specific_syscall'}:
                    continue
                if m[1]:
                    name = unistd_map[name]
                if nr in uniq:
                    continue
                print(name + ' = ' + nr + ',', file=o)
                uniq.add(nr)
        footer(o)

def setup_path_for_name(name):
    out = pathlib.Path('src/' + name + '.rs')
    out.parents[0].mkdir(parents=True, exist_ok=True)
    return out

for (name, abis, path) in SOURCES:
    with open(BASE + '/' + path) as f:
        out = setup_path_for_name(name)
        convert_tbl(out, f)

for (name, unistd_map) in UNISTD_SOURCES:
    with open(BASE + '/include/uapi/asm-generic/unistd.h') as f:
        out = setup_path_for_name(name)
        convert_unistd(out, unistd_map, f)
