// Copyright (c) 2007-2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ---
// Author: Markus Gutschke

// Include linux_syscall_support.h as the first file, so we will compilation
// errors if it has unexpected dependencies on other header files.
#include "linux_syscall_support.h"

#include <stdio.h>

// Used to count the number of check failures
static int check_failures = 0;

#define CHECK(cond)                 \
  do {                              \
    if (!(cond)) {                  \
      puts("Check failed: " #cond); \
      ++check_failures;             \
    }                               \
  } while (0)

// We need to do some "namespace" magic to be able to include both <asm/stat.h>
// and <sys/stat.h>, which normally are mutually exclusive.
// This is currently the only reason why this test has to be compiled as
// C++ code.
namespace linux_syscall_support {
#include <asm/stat.h>
}
#include <sys/stat.h>

namespace linux_syscall_support {
// Define kernel data structures as known to glibc
#include <asm/poll.h>
#include <asm/posix_types.h>
#include <asm/types.h>
#include <errno.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <unistd.h>

// Set by the signal handler to show that we received a signal
static int signaled;

static void CheckStructures() {
  puts("CheckStructures...");
  // Compare sizes of the kernel structures. This will allow us to
  // catch cases where linux_syscall_support.h defined structures that
  // are obviously different from the ones the kernel expects. This is
  // a little complicated, because glibc often deliberately defines
  // incompatible versions. We address this issue on a case-by-case
  // basis by including the appropriate linux-specific header files
  // within our own namespace, instead of within the global
  // namespace. Occasionally, this requires careful sorting of header
  // files, too (e.g. in the case of "stat.h"). And we provide cleanup
  // where necessary (e.g. in the case of "struct statfs").  This is
  // far from perfect, but in the worst case, it'll lead to false
  // error messages that need to be fixed manually.  Unfortunately,
  // there are a small number of data structures (e.g "struct
  // kernel_old_sigaction") that we cannot test at all, as glibc does
  // not have any definitions for them.
  CHECK(sizeof(struct iovec) == sizeof(struct kernel_iovec));
  CHECK(sizeof(struct msghdr) == sizeof(struct kernel_msghdr));
  CHECK(sizeof(struct pollfd) == sizeof(struct kernel_pollfd));
  CHECK(sizeof(struct rlimit) == sizeof(struct kernel_rlimit));
  CHECK(sizeof(struct rusage) == sizeof(struct kernel_rusage));
  CHECK(sizeof(struct sigaction) == sizeof(struct kernel_sigaction)
                                        // glibc defines an excessively large sigset_t. Compensate for it:
                                        + sizeof(((struct sigaction *)0)->sa_mask) - KERNEL_NSIG / 8
#ifdef __mips__
                                        + 2 * sizeof(int)
#endif
  );
  CHECK(sizeof(struct sockaddr) == sizeof(struct kernel_sockaddr));
  CHECK(sizeof(struct stat) == sizeof(struct kernel_stat));
  CHECK(sizeof(struct statfs) == sizeof(struct kernel_statfs)
#ifdef __USE_FILE_OFFSET64
                                     // glibc sometimes defines 64-bit wide fields in "struct statfs"
                                     // even though this is just the 32-bit version of the structure.
                                     + 5 * (sizeof(((struct statfs *)0)->f_blocks) - sizeof(unsigned))
#endif
  );
  CHECK(sizeof(struct timespec) == sizeof(struct kernel_timespec));
#ifndef __x86_64__
  CHECK(sizeof(struct stat64) == sizeof(struct kernel_stat64));
  CHECK(sizeof(struct statfs64) == sizeof(struct kernel_statfs64));
#endif
}

#ifdef __mips__
#define ZERO_SIGACT \
  { 0 }
#else
#define ZERO_SIGACT \
  {                 \
    { 0 }           \
  }
#endif

static void SigHandler(int signum) {
  if (signaled) {
    // Caller will report an error, as we cannot do so from a signal handler
    signaled = -1;
  } else {
    signaled = signum;
  }
  return;
}

static void SigAction(int signum, siginfo_t *si, void *arg) { SigHandler(signum); }

static void Sigaction() {
  puts("Sigaction...");
  int signum = SIGPWR;
  for (int info = 0; info < 2; info++) {
    signaled = 0;
    struct kernel_sigaction sa = ZERO_SIGACT, old, orig;
    CHECK(!sys_sigaction(signum, NULL, &orig));
    if (info) {
      sa.sa_sigaction_ = SigAction;
    } else {
      sa.sa_handler_ = SigHandler;
    }
    sa.sa_flags = SA_RESETHAND | SA_RESTART | (info ? SA_SIGINFO : 0);
    CHECK(!sys_sigemptyset(&sa.sa_mask));
    CHECK(!sys_sigaction(signum, &sa, &old));
    CHECK(!memcmp(&old, &orig, sizeof(struct kernel_sigaction)));
    CHECK(!sys_sigaction(signum, NULL, &old));
#if defined(__i386__) || defined(__x86_64__)
    old.sa_restorer = sa.sa_restorer;
    old.sa_flags &= ~SA_RESTORER;
#endif
    CHECK(!memcmp(&old, &sa, sizeof(struct kernel_sigaction)));
    struct kernel_sigset_t pending;
    CHECK(!sys_sigpending(&pending));
    CHECK(!sys_sigismember(&pending, signum));
    struct kernel_sigset_t mask, oldmask;
    CHECK(!sys_sigemptyset(&mask));
    CHECK(!sys_sigaddset(&mask, signum));
    CHECK(!sys_sigprocmask(SIG_BLOCK, &mask, &oldmask));
    CHECK(!sys_kill(sys_getpid(), signum));
    CHECK(!sys_sigpending(&pending));
    CHECK(sys_sigismember(&pending, signum));
    CHECK(!signaled);
    CHECK(!sys_sigfillset(&mask));
    CHECK(!sys_sigdelset(&mask, signum));
    CHECK(sys_sigsuspend(&mask) == -1);
    CHECK(signaled == signum);
    CHECK(!sys_sigaction(signum, &orig, NULL));
    CHECK(!sys_sigprocmask(SIG_SETMASK, &oldmask, NULL));
  }
}

static void OldSigaction() {
#if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__PPC__) || \
    (defined(__mips__) && _MIPS_SIM == _MIPS_SIM_ABI32)
  puts("OldSigaction...");
  int signum = SIGPWR;
  for (int info = 0; info < 2; info++) {
    signaled = 0;
    struct kernel_old_sigaction sa = ZERO_SIGACT, old, orig;
    CHECK(!sys__sigaction(signum, NULL, &orig));
    if (info) {
      sa.sa_sigaction_ = SigAction;
    } else {
      sa.sa_handler_ = SigHandler;
    }
    sa.sa_flags = SA_RESETHAND | SA_RESTART | (info ? SA_SIGINFO : 0);
    memset(&sa.sa_mask, 0, sizeof(sa.sa_mask));
    CHECK(!sys__sigaction(signum, &sa, &old));
    CHECK(!memcmp(&old, &orig, sizeof(struct kernel_old_sigaction)));
    CHECK(!sys__sigaction(signum, NULL, &old));
#ifndef __mips__
    old.sa_restorer = sa.sa_restorer;
#endif
    CHECK(!memcmp(&old, &sa, sizeof(struct kernel_old_sigaction)));
    unsigned long pending;
    CHECK(!sys__sigpending(&pending));
    CHECK(!(pending & (1UL << (signum - 1))));
    unsigned long mask, oldmask;
    mask = 1 << (signum - 1);
    CHECK(!sys__sigprocmask(SIG_BLOCK, &mask, &oldmask));
    CHECK(!sys_kill(sys_getpid(), signum));
    CHECK(!sys__sigpending(&pending));
    CHECK(pending & (1UL << (signum - 1)));
    CHECK(!signaled);
    mask = ~mask;
    CHECK(sys__sigsuspend(
#ifndef __PPC__
              &mask, 0,
#endif
              mask) == -1);
    CHECK(signaled == signum);
    CHECK(!sys__sigaction(signum, &orig, NULL));
    CHECK(!sys__sigprocmask(SIG_SETMASK, &oldmask, NULL));
  }
#endif
}

template <class A, class B>
static void AlmostEquals(A a, B b) {
  double d = 0.0 + a - b;
  if (d < 0) {
    d = -d;
  }
  double avg = a / 2.0 + b / 2.0;
  if (avg < 4096) {
    // Round up to a minimum size. Otherwise, even minute changes could
    // trigger a false positive.
    avg = 4096;
  }
  // Check that a and b are within one percent of each other.
  CHECK(d / avg < 0.01);
}

static void StatFs() {
  puts("StatFs...");
  struct statfs64 libc_statfs;
  struct kernel_statfs kernel_statfs;
  CHECK(!statfs64("/", &libc_statfs));
  CHECK(!sys_statfs("/", &kernel_statfs));
  CHECK(libc_statfs.f_type == kernel_statfs.f_type);
  CHECK(libc_statfs.f_bsize == kernel_statfs.f_bsize);
  CHECK(libc_statfs.f_blocks == kernel_statfs.f_blocks);
  AlmostEquals(libc_statfs.f_bfree, kernel_statfs.f_bfree);
  AlmostEquals(libc_statfs.f_bavail, kernel_statfs.f_bavail);
  CHECK(libc_statfs.f_files == kernel_statfs.f_files);
  AlmostEquals(libc_statfs.f_ffree, kernel_statfs.f_ffree);
  CHECK(libc_statfs.f_fsid.__val[0] == kernel_statfs.f_fsid.val[0]);
  CHECK(libc_statfs.f_fsid.__val[1] == kernel_statfs.f_fsid.val[1]);
  CHECK(libc_statfs.f_namelen == kernel_statfs.f_namelen);
}

static void StatFs64() {
#if defined(__i386__) || defined(__ARM_ARCH_3__) || (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI64)
  puts("StatFs64...");
  struct statfs64 libc_statfs;
  struct kernel_statfs64 kernel_statfs;
  CHECK(!statfs64("/", &libc_statfs));
  CHECK(!sys_statfs64("/", &kernel_statfs));
  CHECK(libc_statfs.f_type == kernel_statfs.f_type);
  CHECK(libc_statfs.f_bsize == kernel_statfs.f_bsize);
  CHECK(libc_statfs.f_blocks == kernel_statfs.f_blocks);
  AlmostEquals(libc_statfs.f_bfree, kernel_statfs.f_bfree);
  AlmostEquals(libc_statfs.f_bavail, kernel_statfs.f_bavail);
  CHECK(libc_statfs.f_files == kernel_statfs.f_files);
  AlmostEquals(libc_statfs.f_ffree, kernel_statfs.f_ffree);
  CHECK(libc_statfs.f_fsid.__val[0] == kernel_statfs.f_fsid.val[0]);
  CHECK(libc_statfs.f_fsid.__val[1] == kernel_statfs.f_fsid.val[1]);
  CHECK(libc_statfs.f_namelen == kernel_statfs.f_namelen);
#endif
}

static void Stat() {
  static const char *const entries[] = {"/dev/null", "/bin/sh", "/", NULL};
  puts("Stat...");
  for (int i = 0; entries[i]; i++) {
    struct ::stat64 libc_stat;
    struct kernel_stat kernel_stat;
    CHECK(!::stat64(entries[i], &libc_stat));
    CHECK(!sys_stat(entries[i], &kernel_stat));
    //  CHECK(libc_stat.st_dev     == kernel_stat.st_dev);
    CHECK(libc_stat.st_ino == kernel_stat.st_ino);
    CHECK(libc_stat.st_mode == kernel_stat.st_mode);
    CHECK(libc_stat.st_nlink == kernel_stat.st_nlink);
    CHECK(libc_stat.st_uid == kernel_stat.st_uid);
    CHECK(libc_stat.st_gid == kernel_stat.st_gid);
    CHECK(libc_stat.st_rdev == kernel_stat.st_rdev);
    CHECK(libc_stat.st_size == kernel_stat.st_size);
#if !defined(__i386__) && !defined(__ARM_ARCH_3__) && !defined(__PPC__) && \
    !(defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI64)
    CHECK(libc_stat.st_blksize == kernel_stat.st_blksize);
    CHECK(libc_stat.st_blocks == kernel_stat.st_blocks);
#endif
    CHECK(libc_stat.st_atime == kernel_stat.st_atime_);
    CHECK(libc_stat.st_mtime == kernel_stat.st_mtime_);
    CHECK(libc_stat.st_ctime == kernel_stat.st_ctime_);
  }
}

static void Stat64() {
#if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__PPC__) || \
    (defined(__mips__) && _MIPS_SIM != _MIPS_SIM_ABI64)
  puts("Stat64...");
  static const char *const entries[] = {"/dev/null", "/bin/sh", "/", NULL};
  for (int i = 0; entries[i]; i++) {
    struct ::stat64 libc_stat;
    struct kernel_stat64 kernel_stat;
    CHECK(!::stat64(entries[i], &libc_stat));
    CHECK(!sys_stat64(entries[i], &kernel_stat));
    CHECK(libc_stat.st_dev == kernel_stat.st_dev);
    CHECK(libc_stat.st_ino == kernel_stat.st_ino);
    CHECK(libc_stat.st_mode == kernel_stat.st_mode);
    CHECK(libc_stat.st_nlink == kernel_stat.st_nlink);
    CHECK(libc_stat.st_uid == kernel_stat.st_uid);
    CHECK(libc_stat.st_gid == kernel_stat.st_gid);
    CHECK(libc_stat.st_rdev == kernel_stat.st_rdev);
    CHECK(libc_stat.st_size == kernel_stat.st_size);
    CHECK(libc_stat.st_blksize == kernel_stat.st_blksize);
    CHECK(libc_stat.st_blocks == kernel_stat.st_blocks);
    CHECK(libc_stat.st_atime == kernel_stat.st_atime_);
    CHECK(libc_stat.st_mtime == kernel_stat.st_mtime_);
    CHECK(libc_stat.st_ctime == kernel_stat.st_ctime_);
  }
#endif
}

}  // namespace linux_syscall_support

int main(int argc, char *argv[]) {
  linux_syscall_support::CheckStructures();
  linux_syscall_support::Sigaction();
  linux_syscall_support::OldSigaction();
  linux_syscall_support::StatFs();
  linux_syscall_support::StatFs64();
  linux_syscall_support::Stat();
  linux_syscall_support::Stat64();

  if (check_failures == 0)
    puts("PASS");
  else
    puts("FAIL");
  return check_failures;
}
