/* -*-C-*- */

/*  fake.c  */


/*
 * Author: Nikita Danilov <NikitaDanilov@yahoo.COM>
 * Keywords: faked io, logging, io profiling
 *
 * Idea comes to me from Дмитрий Хазаров (Dmitry Khazarov), whose
 * current location I am unaware of.
 *
 * Copyright (C) 2000 Namesys (Hans Reiser)
 *
 * This file is part of fakeio.
 *
 * Fakeio is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This software 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 * */

#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/stat.h>

/* caveats:
   
   * doesn't monitor mmaped operations (it is possible to intercept 
   them, but to do fair replay we also need to dirty pages as host
   process did (ok, I can unmap everything just after mmap, them intercept
   SIGSEGV and SIGBUS, check, log and...))

   * shouldn't work with suids without root permissions
   * no docs
   * only open/open64(what is this?)/read/write/lseek/lseek64/close are
   intercepted. What else?
   * cannot intercept stat and friends (trying xstat)
 */
#define ALIASES( retType, name, args )										\
  retType name args __attribute__( ( weak, alias( "fake_" #name ) ) );		\
  retType _##name args __attribute__( ( weak, alias( "fake_" #name ) ) );	\
  retType __##name args __attribute__( ( weak, alias( "fake_" #name ) ) );

#define DECLARE_ALIASES( retType, name, args )	\
  ALIASES( retType, name, args )				\
  retType fake_##name args

#define PROLOGUE								\
  if( fakeDepth > 0 )							\
	{											\
	  return libCResult;						\
	}											\
  else											\
	{											\
	  ++fakeDepth;								\
	}

typedef enum { New, Append, Ok, Fail } SeekStatus;
typedef struct 
{ 
  char       *path;
  SeekStatus  seek;
  off_t       offset;
} FileEntry;

extern pid_t getpid(void);
#define TRACE  loge( "I trace %s %i %s\n", \
					 __PRETTY_FUNCTION__, __LINE__, __FILE__ );

extern long sysconf( int name );
#define MAXFD 1024 /* where to get this from? */
static FileEntry *filePath = NULL;

extern int gettimeofday( struct timeval *tv, struct timezone *tz );

static unsigned long fakeDepth = 0;

static int logFd = -1;

static void updateOffset( int fd, off_t off )
{
  if( filePath != NULL )
	{
	  if( filePath[ fd ].seek == Ok )
		{
		  filePath[ fd ].offset += off;
		}
	  else if( filePath[ fd ].seek == New )
		{
		  if( off >= 0 )
			{
			  filePath[ fd ].offset = off;
			}
		  else
			{
			  filePath[ fd ].seek = Fail;
			}
		}
	}
}

static char *getFdName( int fd )
{
  if( ( filePath != NULL ) && ( filePath[ fd ].path != NULL ) )
	{
	  return filePath[ fd ].path;
	}
  else
	{
	  return "";
	}
}

static void log( const char *const msg )
{
  extern ssize_t __libc_write( int fd, const void *buf, size_t count );

  if( logFd != -1 )
	{
	  if( msg != NULL )
		{
		  __libc_write( logFd, msg, strlen( msg ) );
		}
	  else
		{
		  __libc_write( logFd, "NULL!\n", 6 );
		}
	}
}

static void lognl()
{
  log( "\n" );
}

static size_t vgetVLength( const char *const format, va_list args )
{
  char buf[ 10 ];
  return vsnprintf( buf, 2, format, args );
}

static size_t getVLength( const char *const format, ... )
{
  va_list args;
  size_t result;

  va_start( args, format );
  result = vgetVLength( format, args );
  va_end( args );
  return result;
}

static char *vasprintf( const char *const format, va_list args )
{
  char *result;

  result = malloc( vgetVLength( format, args ) + 1 );
  vsprintf( result, format, args );
  return result;
}

static char *asprintf( const char *const format, ... )
{
  va_list args;
  char *result;

  va_start( args, format );
  result = vasprintf( format, args );
  va_end( args );
  return result;
}

static void vlogf( const char *const format, va_list args )
{
  char *buffer;

  buffer = vasprintf( format, args );
  if( buffer != NULL )
	{
	  log( buffer );
	  free( buffer );
	}
}

static void logf( const char *const format, ... ) __attribute__( ( format( printf, 1, 2 ) ) );
static void logf( const char *const format, ... )
{
  va_list arg;

  va_start( arg, format );
  vlogf( format, arg );
  va_end( arg );
}

static void vloge( const char *const format, va_list args  )
{
  char *buffer;

  char *prefix;
  char *body;

  struct timeval  time;
  struct timezone dummy;

  gettimeofday( &time, &dummy );

  prefix = asprintf( "%i.%i %i ", time.tv_sec, time.tv_usec, getpid() );
  body = vasprintf( format, args );
  buffer = malloc( strlen( prefix ) + strlen( body ) + 1 );
  strcpy( buffer, prefix );
  strcat( buffer, body );
  free( body );
  free( prefix );
  log( buffer );
}

static void loge( const char *const format, ...  )__attribute__( ( format( printf, 1, 2 ) ) );
static void loge( const char *const format, ...  )
{
  va_list args;

  va_start( args, format );
  vloge( format, args );
  va_end( args );
}

static void init()
{
  static int initialised = 0;
  extern char *getenv( const char *name );
  extern int __libc_open( const char *pathname, int flags, ... );

  char *logDestination;

  if( ! initialised )
	{
	  if( ( logDestination = getenv( "FAKE_LOG" ) ) != NULL )
		{
		  switch( logDestination[ 0 ] )
			{
			case 'F':
			  {
				logFd = __libc_open
				  ( ++logDestination, O_WRONLY | O_APPEND | O_CREAT, 0700 );
			  }
			}
		}
	  if( logFd == -1 )
		{
		  logFd = 2; /* log to stderr... */
		}
	  if( filePath == NULL )
		{
		  struct rlimit rlim;

		  if( getrlimit( RLIMIT_NOFILE, &rlim ) == 0 )
			{
 			  filePath = ( FileEntry * ) 
				malloc( rlim.rlim_cur * sizeof( FileEntry ) ); 
 			  memset( filePath, 0, rlim.rlim_cur * sizeof( FileEntry ) ); 
			}
		}
	  initialised = 1;
	}
}

static unsigned long readCalls = 0;
DECLARE_ALIASES( ssize_t, read, ( int fd, void *buf, size_t count ) )
{
  extern ssize_t __libc_read( int fd, void *buf, size_t count );
  extern ssize_t __libc_lseek( int fd, off_t off, int whence );
  off_t offset;
  int oldErrno;
  int libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_read( fd, buf, count );
  PROLOGUE;
  oldErrno = errno;
  ++readCalls;
  offset = __libc_lseek( fd, 0, SEEK_CUR );
  loge( "R %i %li %i %i %i %s\n", 
		fd, offset, count, libCResult, errno, getFdName( fd ) );
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

static unsigned long writeCalls = 0;
DECLARE_ALIASES( ssize_t, write, ( int fd, void *buf, size_t count ) )
{
  extern ssize_t __libc_write( int fd, void *buf, size_t count );
  extern ssize_t __libc_lseek( int fd, off_t off, int whence );
  off_t offset;
  int oldErrno;
  int libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_write( fd, buf, count );
  PROLOGUE;
  if( ( fd != -1 ) && ( fd == logFd ) )
	{
	  return libCResult;
	}
  oldErrno = errno;
  ++writeCalls;
  offset = __libc_lseek( fd, 0, SEEK_CUR );
  loge( "W %i %li %i %i %i %s\n", 
		fd, offset, count, libCResult, errno, getFdName( fd ) );
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

DECLARE_ALIASES( int, open, ( const char *pathname, int flags, ... ) )
{
  extern int __libc_open( const char *pathname, int flags, ... );
  int mode = 0;
  int libCResult;
  int oldErrno;

  init();
  /* TRACE; */
  if( flags & O_CREAT )
    {
      va_list arg;

      va_start( arg, flags );
	  mode = va_arg( arg, int );
      va_end( arg );
	}

  libCResult = __libc_open( pathname, flags, mode );
  PROLOGUE;
  oldErrno = errno;
  loge( "O %i %i %3.3o %i %s\n", 
		libCResult, flags, mode, errno, pathname );
  if( libCResult != -1 )
	{
	  if( filePath != NULL )
		{
		  if( filePath[ libCResult ].path != NULL )
			{
			  loge( "I error Strange reusing `%s' as fd# %i (new path `%s')\n", 
					filePath[ libCResult ].path, 
					libCResult, pathname );
			  free( filePath[ libCResult ].path );
			}
		  filePath[ libCResult ].path = ( char * ) malloc( strlen( pathname ) + 1 );
		  if( filePath[ libCResult ].path != NULL )
			{
			  strcpy( filePath[ libCResult ].path, pathname );
			}
		  filePath[ libCResult ].seek = ( flags & O_APPEND ) ? Append : New;
		}
	}
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

DECLARE_ALIASES( int, open64, ( const char *pathname, int flags, ... ) )__attribute__( ( weak, alias( "fake_open" ) ) );

static unsigned long seekCalls = 0;
DECLARE_ALIASES( off_t, lseek, ( int fd, off_t off, int whence ) )
{
  extern off_t __libc_lseek( int fd, off_t off, int whence );
  int oldErrno;
  int libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_lseek( fd, off, whence );
  PROLOGUE;
  oldErrno = errno;
  ++seekCalls;
  loge( "S %i %li %i %i %i %s\n", 
		fd, off, whence, libCResult, errno, getFdName( fd ) );
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

static unsigned long seek64Calls = 0;
DECLARE_ALIASES( __off64_t, lseek64, ( int fd, __off64_t off, int whence ) )
{
  extern __off64_t __libc_lseek64( int fd, __off64_t off, int whence );
  int oldErrno;
  __off64_t libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_lseek64( fd, off, whence );
  PROLOGUE;
  oldErrno = errno;
  ++seek64Calls;
  loge( "S %i %lli %i %lli %i %s\n", 
		fd, off, whence, libCResult, errno, getFdName( fd ) );
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

static unsigned long closeCalls = 0;
DECLARE_ALIASES( off_t, close, ( int fd, off_t off, int whence ) )
{
  extern int __libc_close( int fd );
  int oldErrno;
  int libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_close( fd );
  PROLOGUE;
  oldErrno = errno;
  ++closeCalls;
  loge( "C %i %i %i %s\n", 
		fd, libCResult, errno, getFdName( fd ) );
  if( ( filePath != NULL ) && ( filePath[ fd ].path != NULL ) )
	{
	  free( filePath[ fd ].path );
	  filePath[ fd ].path = NULL;
	}
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

static unsigned long statCalls = 0;
DECLARE_ALIASES( int, xstat, ( int version, const char *file_name, struct stat *buf ) )
{
  extern int __libc_stat( const char *file_name, struct stat *buf );
  int oldErrno;
  int libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_stat( file_name, buf );
  PROLOGUE;
  oldErrno = errno;
  ++statCalls;
  loge( "T P %i %i %s\n", libCResult, errno, file_name );
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

DECLARE_ALIASES( int, stat64, ( const char *pathname, int flags, ... ) )__attribute__( ( weak, alias( "fake_stat" ) ) );

DECLARE_ALIASES( int, lstat, ( const char *file_name, struct stat *buf ) )
{
  extern int __libc_lstat( const char *file_name, struct stat *buf );
  int oldErrno;
  int libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_lstat( file_name, buf );
  PROLOGUE;
  oldErrno = errno;
  ++statCalls;
  loge( "T L %i %i %s\n", libCResult, errno, file_name );
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

DECLARE_ALIASES( int, lstat64, ( const char *pathname, int flags, ... ) )__attribute__( ( weak, alias( "fake_lstat" ) ) );

DECLARE_ALIASES( int, fstat, ( int fd, struct stat *buf ) )
{
  extern int __libc_fstat( int fd, struct stat *buf );
  int oldErrno;
  int libCResult;

  init();
  /* TRACE; */
  libCResult = __libc_fstat( fd, buf );
  PROLOGUE;
  oldErrno = errno;
  ++statCalls;
  loge( "T D %i %i %i %s\n", fd, libCResult, errno, getFdName( fd ) );
  errno = oldErrno;
  --fakeDepth;
  return libCResult;
}

DECLARE_ALIASES( int, fstat64, ( const char *pathname, int flags, ... ) )__attribute__( ( weak, alias( "fake_fstat" ) ) );

