#!/usr/bin/python
#
# this tool is used to check that the syscall numbers that are in
# SYSCALLS.TXT correspond to those found in the Linux kernel sources
# for the arm and i386 architectures
#

import sys, re, string, os, commands
from   bionic_utils import *

# change this if necessary
syscalls_txt = "SYSCALLS.TXT"

def usage():
    print "usage: checksyscalls [options] [kernel_headers_rootdir]"
    print "    options:    -v   enable verbose mode"
    sys.exit(1)


linux_root    = None
syscalls_file = None

def parse_command_line(args):
    global linux_root, syscalls_file, verbose

    program = args[0]
    args    = args[1:]
    while len(args) > 0 and args[0][0] == "-":
        option = args[0][1:]
        args   = args[1:]

        if option == "v":
            D_setlevel(1)
        else:
            usage()

    if len(args) > 2:
        usage()

    if len(args) == 0:
        linux_root = find_kernel_headers()
        if linux_root == None:
            print "Could not locate original or system kernel headers root directory."
            print "Please specify one when calling this program, i.e. 'checksyscalls <headers-directory>'"
            sys.exit(1)
        print "using the following kernel headers root: '%s'" % linux_root
    else:
        linux_root = args[0]
        if not os.path.isdir(linux_root):
            print "the directory '%s' does not exist. aborting\n" % headers_root
            sys.exit(1)

parse_command_line(sys.argv)

syscalls_file = find_file_from_upwards(None, syscalls_txt)
if not syscalls_file:
    print "could not locate the %s file. Aborting" % syscalls_txt
    sys.exit(1)

print "parsing %s" % syscalls_file

# read the syscalls description file
#

parser = SysCallsTxtParser()
parser.parse_file(syscalls_file)
syscalls = parser.syscalls

re_nr_line       = re.compile( r"#define __NR_(\w*)\s*\(__NR_SYSCALL_BASE\+\s*(\w*)\)" )
re_nr_clock_line = re.compile( r"#define __NR_(\w*)\s*\(__NR_timer_create\+(\w*)\)" )
re_arm_nr_line   = re.compile( r"#define __ARM_NR_(\w*)\s*\(__ARM_NR_BASE\+\s*(\w*)\)" )
re_x86_line      = re.compile( r"#define __NR_(\w*)\s*([0-9]*)" )
re_mips_line     = re.compile( r"#define __NR_(\w*)\s*\(__NR_Linux\s*\+\s*([0-9]*)\)" )

# now read the Linux arm header
def process_nr_line(line,dict):

    m = re_mips_line.match(line)
    if m:
        if dict["Linux"]==4000:
            dict[m.group(1)] = int(m.group(2))
        return

    m = re_nr_line.match(line)
    if m:
        dict[m.group(1)] = int(m.group(2))
        return

    m = re_nr_clock_line.match(line)
    if m:
        dict[m.group(1)] = int(m.group(2)) + 259
        return

    m = re_arm_nr_line.match(line)
    if m:
        offset_str = m.group(2)
        #print "%s = %s" % (m.group(1), offset_str)
        base = 10
        if offset_str.lower().startswith("0x"):
          # Processing something similar to
          #   #define __ARM_NR_cmpxchg  (__ARM_NR_BASE+0x00fff0)
          base = 16
        dict["ARM_"+m.group(1)] = int(offset_str, base) + 0x0f0000
        return

    m = re_x86_line.match(line)
    if m:
        # try block because the ARM header has some #define _NR_XXXXX  /* nothing */
        try:
            #print "%s = %s" % (m.group(1), m.group(2))
            dict[m.group(1)] = int(m.group(2))
        except:
            pass
        return


def process_header(header_file,dict):
    fp = open(header_file)
    D("reading "+header_file)
    for line in fp.xreadlines():
        line = line.strip()
        if not line: continue
        process_nr_line(line,dict)
    fp.close()

arm_dict = {}
x86_dict = {}
mips_dict = {}

# remove trailing slash from the linux_root, if any
if linux_root[-1] == '/':
    linux_root = linux_root[:-1]

arm_unistd = find_arch_header(linux_root, "arm", "unistd.h")
if not arm_unistd:
    print "WEIRD: Could not locate the ARM unistd.h kernel header file,"
    print "maybe using a different set of kernel headers might help."
    sys.exit(1)

# on recent kernels, asm-i386 and asm-x64_64 have been merged into asm-x86
# with two distinct unistd_32.h and unistd_64.h definition files.
# take care of this here
#
x86_unistd = find_arch_header(linux_root, "i386", "unistd.h")
if not x86_unistd:
    x86_unistd = find_arch_header(linux_root, "x86", "unistd_32.h")
    if not x86_unistd:
        print "WEIRD: Could not locate the i386/x86 unistd.h header file,"
        print "maybe using a different set of kernel headers might help."
        sys.exit(1)

mips_unistd = find_arch_header(linux_root, "mips", "unistd.h")
if not mips_unistd:
    print "WEIRD: Could not locate the Mips unistd.h kernel header file,"
    print "maybe using a different set of kernel headers might help."
    sys.exit(1)

process_header( arm_unistd, arm_dict )
process_header( x86_unistd, x86_dict )
process_header( mips_unistd, mips_dict )

# now perform the comparison
errors = 0

def check_syscalls(archname, idname, arch_dict):
    errors = 0
    for sc in syscalls:
        sc_name = sc["name"]
        sc_id   = sc[idname]
        if sc_id >= 0:
            if not arch_dict.has_key(sc_name):
                print "error: %s syscall %s not defined, should be %d" % (archname, sc_name, sc_id)
                errors += 1
            elif not arch_dict.has_key(sc_name):
                print "error: %s syscall %s is not implemented" % (archname, sc_name)
                errors += 1
            elif arch_dict[sc_name] != sc_id:
                print "error: %s syscall %s should be %d instead of %d" % (archname, sc_name, arch_dict[sc_name], sc_id)
                errors += 1
    return errors

errors += check_syscalls("arm", "armid", arm_dict)
errors += check_syscalls("x86", "x86id", x86_dict)
errors += check_syscalls("mips", "mipsid", mips_dict)

if errors == 0:
    print "congratulations, everything's fine !!"
else:
    print "correct %d errors !!" % errors