/* This program attempts to verify that all functions that are
   supposed to be wrapped by tc_intercepts.c really are wrapped.  The
   main way it does this is to cause failures in those functions, so
   as to obtain various error messages which imply that the wrapper
   really did engage.

   Any regressions shown up by this program are potentially serious
   and should be investigated carefully. */

/* Needed for older glibcs (2.3 and older, at least) who don't
   otherwise "know" about some more exotic pthread stuff, in this case
   PTHREAD_MUTEX_ERRORCHECK. */
#define _GNU_SOURCE 1
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include "safe-pthread.h"
#include "safe-semaphore.h"

#if !defined(__APPLE__) && !defined(__FreeBSD__)

#if defined(__GLIBC__)
#if !defined(__GLIBC_PREREQ)
# error "This program needs __GLIBC_PREREQ (in /usr/include/features.h)"
#endif
#else
/* Fake __GLIBC_PREREQ. Pretend glibc >= 2.4. */
# define __GLIBC_PREREQ
#endif /* __GLIBC__ */

typedef union {
   pthread_spinlock_t spinlock;
   pthread_rwlock_t rwlock;
} spin_rw_lock;

short unprotected = 0;

void* lazy_child ( void* v ) {
   assert(0); /* does not run */
}

void* racy_child ( void* v ) {
   unprotected = 1234;
   return NULL;
}

int main ( void )
{
   int r;
   /* pthread_t thr; */
   /* pthread_attr_t thra; */
   pthread_mutexattr_t mxa, mxa2;
   pthread_mutex_t mx, mx2, mx3, mx4;
   pthread_cond_t cv;
   struct timespec abstime;
   pthread_rwlock_t rwl;
   pthread_rwlock_t rwl2;
   pthread_rwlock_t rwl3;
   sem_t s1;

#  if __GLIBC_PREREQ(2,4)
   fprintf(stderr, 
           "\n\n------ This is output for >= glibc 2.4 ------\n");
#  else
   fprintf(stderr,
           "\n\n------ This is output for < glibc 2.4 ------\n");
#  endif

   /* --------- pthread_create/join --------- */

   fprintf(stderr,
   "\n---------------- pthread_create/join ----------------\n\n");

   /* make pthread_create fail */
   /* It's amazingly difficult to make pthread_create fail
      without first soaking up all the machine's resources.
      Instead, in order to demonstrate that it's really wrapped,
      create a child thread, generate a race error, and join with it
      again. */
   /* This just segfaults:
      memset( &thra, 0xFF, sizeof(thra) );
      r= pthread_create( &thr, NULL, lazy_child, NULL ); assert(r);
   */
   { pthread_t child;
     r= pthread_create( &child, NULL, racy_child, NULL ); assert(!r);
     sleep(1); /* just to ensure parent thread reports race, not child */
     unprotected = 5678;
     r= pthread_join( child, NULL ); assert(!r);
   }

   /* make pthread_join fail */
   r= pthread_join( pthread_self(), NULL ); assert(r);

   /* --------- pthread_mutex_lock et al --------- */

   fprintf(stderr,
   "\n---------------- pthread_mutex_lock et al ----------------\n\n");

   /* make pthread_mutex_init fail */
#if defined(__sun__)
   pthread_mutexattr_init( &mxa );
   memset( mxa.__pthread_mutexattrp, 0xFF, 5 * sizeof(int) );
#else
   memset( &mxa, 0xFF, sizeof(mxa) );
#endif
   r= pthread_mutex_init( &mx, &mxa );
#  if __GLIBC_PREREQ(2,4)
   assert(r); /* glibc >= 2.4: the call should fail */
#  else
   assert(!r); /* glibc < 2.4: oh well, glibc didn't bounce this */
#  endif

   /* make pthread_mutex_destroy fail */
   r= pthread_mutex_init( &mx2, NULL ); assert(!r);
   r= pthread_mutex_lock( &mx2 ); assert(!r);
   r= pthread_mutex_destroy( &mx2 );

   /* make pthread_mutex_lock fail (skipped on < glibc 2.4 because it
      doesn't fail, hence hangs the test) */
#  if __GLIBC_PREREQ(2,4)
   memset( &mx3, 0xFF, sizeof(mx3) );
   r= pthread_mutex_lock( &mx3 ); assert(r);
#  else
   fprintf(stderr, "\nmake pthread_mutex_lock fail: "
                   "skipped on glibc < 2.4\n\n");
#  endif

   /* make pthread_mutex_trylock fail */
   memset( &mx3, 0xFF, sizeof(mx3) );
   r= pthread_mutex_trylock( &mx3 ); assert(r);

   /* make pthread_mutex_timedlock fail */
   memset( &abstime, 0, sizeof(abstime) );
   memset( &mx3, 0xFF, sizeof(mx3) );
   r= pthread_mutex_timedlock( &mx3, &abstime ); assert(r);

   /* make pthread_mutex_unlock fail */
   memset( &mx3, 0xFF, sizeof(mx3) );
   r= pthread_mutex_unlock( &mx3 );
#  if __GLIBC_PREREQ(2,4)
   assert(r);
#  else
   assert(!r);
#  endif

   /* --------- pthread_cond_wait et al --------- */

   fprintf(stderr,
   "\n---------------- pthread_cond_wait et al ----------------\n\n");

   /* make pthread_cond_wait fail.  This is difficult.  Our cunning
      plan (tm) is to show up at pthread_cond_wait bearing a
      not-locked mutex of the ERRORCHECK flavour and hope (as is
      indeed the case with glibc-2.5) that pthread_cond_wait notices
      it is not locked, and bounces our request. */
   r= pthread_mutexattr_init( &mxa2 ); assert(!r);
   r= pthread_mutexattr_settype( &mxa2, PTHREAD_MUTEX_ERRORCHECK );
      assert(!r);
   r= pthread_mutex_init( &mx4, &mxa2 ); assert(!r);
   r= pthread_cond_init( &cv, NULL ); assert(!r);
   r= pthread_cond_wait( &cv, &mx4 ); assert(r);
   r= pthread_mutexattr_destroy( &mxa2 ); assert(!r);

   /* make pthread_cond_signal fail.  FIXME: can't figure out how
      to */
   r= pthread_cond_signal( &cv ); assert(!r);
   fprintf(stderr, "\nFIXME: can't figure out how to "
                   "verify wrap of pthread_cond_signal\n\n");

   /* make pthread_cond_broadcast fail.  FIXME: can't figure out how
      to */
   r= pthread_cond_broadcast( &cv ); assert(!r);
   fprintf(stderr, "\nFIXME: can't figure out how to "
                   "verify wrap of pthread_broadcast_signal\n\n");

   /* make pthread_cond_timedwait fail. */
   memset( &abstime, 0, sizeof(abstime) );
   abstime.tv_nsec = 1000000000 + 1;
   r= pthread_cond_timedwait( &cv, &mx4, &abstime ); assert(r);

   /* --------- pthread_rwlock_* --------- */

   fprintf(stderr,
   "\n---------------- pthread_rwlock_* ----------------\n\n");

   /* pthread_rwlock_init, pthread_rwlock_unlock */
   /* pthread_rwlock_init: can't make glibc's implementation fail.
      However, can demonstrate interceptedness by initialising but not
      locking a lock and then unlocking it.  Then the unlock call
      should say "first seen at .. the init call."  So this tests
      wrappedness of both calls. */
   r= pthread_rwlock_init( &rwl, NULL ); assert(!r);
   r= pthread_rwlock_unlock( &rwl ); 
   /* assert(r); *//* glibc doesn't complain.  It really ought to. Oh well. */

   /* We can infer the presence of wrapping for pthread_rwlock_rdlock,
      pthread_rwlock_wrlock and pthread_rwlock_unlock by making
      Thrcheck count the lockedness state, and warning when we unlock
      a not-locked lock.  Thusly: */
   r= pthread_rwlock_init( &rwl2, NULL ); assert(!r);

   /* w-lock it */
   fprintf(stderr, "(1) no error on next line\n");
   r= pthread_rwlock_wrlock( &rwl2 ); assert(!r);
   /* unlock it */
   fprintf(stderr, "(2) no error on next line\n");
   r= pthread_rwlock_unlock( &rwl2 ); assert(!r);
   /* unlock it again, get an error */
   fprintf(stderr, "(3)    ERROR on next line\n");
   r= pthread_rwlock_unlock( &rwl2 );
#if defined(__sun__)
   assert(r);
#else
   assert(!r);
#endif

   /* same game with r-locks */
   r= pthread_rwlock_init( &rwl2, NULL ); assert(!r);
   /* r-lock it twice */
   fprintf(stderr, "(4) no error on next line\n");
   r= pthread_rwlock_rdlock( &rwl2 ); assert(!r);
   fprintf(stderr, "(5) no error on next line\n");
   r= pthread_rwlock_rdlock( &rwl2 ); assert(!r);
   /* unlock it twice */
   fprintf(stderr, "(6) no error on next line\n");
   r= pthread_rwlock_unlock( &rwl2 ); assert(!r);
   fprintf(stderr, "(7) no error on next line\n");
   r= pthread_rwlock_unlock( &rwl2 ); assert(!r);
   /* unlock it again, get an error */
   fprintf(stderr, "(8)    ERROR on next line\n");
   r= pthread_rwlock_unlock( &rwl2 );
#if defined(__sun__)
   assert(r);
#else
   assert(!r);
#endif

   /* Lock rwl3 so the locked-lock-at-dealloc check can complain about
      it. */
   r= pthread_rwlock_init( &rwl3, NULL ); assert(!r);
   r= pthread_rwlock_rdlock( &rwl3 ); assert(!r);

   /* --------- pthread_spin_* --------- */

   fprintf(stderr,
   "\n---------------- pthread_spin_* ----------------\n\n");

   /* The following sequence verifies correct wrapping of pthread_spin_init()
      and pthread_spin_destroy(). */
   spin_rw_lock srwl1;
   pthread_spin_init(&srwl1.spinlock, PTHREAD_PROCESS_PRIVATE);
   pthread_spin_destroy(&srwl1.spinlock);

   pthread_rwlock_init(&srwl1.rwlock, NULL);
   pthread_rwlock_destroy(&srwl1.rwlock);

   /* ------------- sem_* ------------- */

   /* This is pretty lame, and duplicates tc18_semabuse.c. */

   fprintf(stderr,
   "\n---------------- sem_* ----------------\n\n");

   /* verifies wrap of sem_init */
   /* Do sem_init with huge initial count - fails */
   r= sem_init(&s1, 0, ~0); assert(r);

   /* initialise properly */
   r= sem_init(&s1, 0, 0);

   /* in glibc, sem_destroy is a no-op; making it fail is
      impossible. */
   fprintf(stderr, "\nFIXME: can't figure out how to verify wrap of "
                   "sem_destroy\n\n");

   /* verifies wrap of sem_wait */
   /* Do 'wait' on a bogus semaphore.  This should fail, but on glibc
      it succeeds. */
   memset(&s1, 0x55, sizeof(s1));
   r= sem_wait(&s1); /* assert(r != 0); */

   /* this only fails with glibc 2.7 or later. */
   r= sem_post(&s1);
   fprintf(stderr, "\nFIXME: can't figure out how to verify wrap of "
                   "sem_post\n\n");

   sem_destroy(&s1);

   /* ------------- dealloc of mem holding locks ------------- */

   fprintf(stderr,
   "\n------------ dealloc of mem holding locks ------------\n\n");

   /* At this point it should complain about deallocation
      of memory containing locked locks:
         rwl3
   */

   return 0;
}

#else /* defined(__APPLE__) */
int main ( void )
{
   fprintf(stderr, "This program does not work on Mac OS X.\n");
   return 0;
}
#endif
