/*
shimfile: shim file io in user space with LD_PRELOAD
(c) Harmen van der Wal, 2006.
License: GPLv2
$Id: shimfile.c,v 1.1.1.1 2006/08/09 01:26:17 harmen Exp $
See http://www.harmwal.nl/pccam880/
*/

#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>

extern int shim_open();
extern int shim_ioctl(unsigned long int request, char *argp);
extern ssize_t shim_read(void *buf, size_t count);
extern int shim_close();

#define DBG(fmt,args...)
//#define DBG(fmt,args...) fprintf(stderr, "%s %s (%d): "fmt, __FILE__, __FUNCTION__, __LINE__,##args)

int shim_inited = 0;
char *shim_path = NULL;
int shim_fd = -1;
FILE *shim_f = NULL;

int (*libc_open)(const char*, int, ...);
int (*libc___fxstat64)(int, int, void*);
int (*libc_ioctl)(int, int, ...);
ssize_t (*libc_read)(int, void*, size_t);
int (*libc_close)(int);

static void shim_init(void)
{
  void *handle;
  char *error;

  // TODO: use a lock here.
  if (!shim_inited) {

    shim_path = getenv("SHIM_FILE");

    DBG("shim_path=%s\n", shim_path);

    handle = dlopen("/lib/libc.so.6", RTLD_LAZY);
    if (!handle) {
      fputs(dlerror(), stderr);
      exit(1);
    }
    
    libc_open = dlsym(handle, "open");
    libc___fxstat64 = dlsym(handle, "__fxstat64");
    libc_ioctl = dlsym(handle, "ioctl");
    libc_read = dlsym(handle, "read");
    libc_close = dlsym(handle, "close");

    if ((error = dlerror()) != NULL) {
      DBG("%s\n", error);
      exit(1);
    }

    shim_inited = 1;
  }
}

static int open_mode(const char *pathname, int flags, mode_t mode)
{
  int result = -1;

  shim_init();

  if (shim_path && strncmp(shim_path, pathname, strlen(shim_path)) == 0) {

    // Get dummy fd
    shim_fd = open("/dev/null", O_RDWR);
    DBG("Open shim file=%s fd=%d\n", shim_path, shim_fd);

    result = shim_open();
   
    if (result >= 0) {
      result = shim_fd;
    }else{
      libc_close(shim_fd);
    }
    
    DBG("result=%d\n", result);      
  }else{
    if (mode) {
      result = libc_open(pathname, flags, mode);
    }else{
      result = libc_open(pathname, flags);
    }
  }

  return result;
}

/* function signatures */

int open(const char *pathname, int flags, ...)
{
  DBG("open %s\n", pathname);

  mode_t mode = 0;
  va_list ap;
  va_start(ap, flags);
  mode = (mode_t)va_arg(ap, mode_t);
  va_end(ap);

  return open_mode(pathname, flags, mode);
}

int open64(const char *pathname, int flags, ...)
{
  DBG("open64 %s\n", pathname);

  mode_t mode = 0;
  va_list ap;
  va_start(ap, flags);
  mode = (mode_t)va_arg(ap, mode_t);
  va_end(ap);

  return open_mode(pathname, flags, mode);
}

int __fxstat64 (int __ver, int __filedes, void *__stat_buf)
{
  int result = -1;

  DBG("__filedes=%d\n", __filedes);

  if (__filedes == shim_fd) {
    shim_init();
    // stat the original filename
    result = stat(shim_path, __stat_buf);
  }else{
    shim_init();
    result =  libc___fxstat64(__ver, __filedes,  __stat_buf);
  }

  DBG("result=%d\n", result);
  
  return result;
}

int ioctl(int fd, unsigned long int request, ...)
{
  int result = -1;
  char *argp = NULL;

  va_list ap;
  va_start(ap, request);
  argp = (char *)va_arg(ap, char *);
  va_end(ap);

  if (fd == shim_fd) {

    DBG("ioctl shim file=%s fd=%d request=%d argp=%p\n",
	shim_path, shim_fd, (int)request, (void *)argp);

    result = shim_ioctl(request, argp);

  }else{
    shim_init();

    if (argp) {
      result =  libc_ioctl(fd, request, argp);
    }else{
      result =  libc_ioctl(fd, request);
    }
  }

  return result;

}

ssize_t read(int fd, void *buf, size_t count)
{
  int result = -1;

  if (fd == shim_fd) {
    DBG("Read shim file=%s fd=%d\n", shim_path, shim_fd);
    result = shim_read(buf, count);
  }else{
    shim_init();
    result =  libc_read(fd, buf, count);
  }

  return result;
}

int close(int fd) 
{
  int result = -1;
  DBG("fd=%d\n", fd);
  if (fd == shim_fd) {
    DBG("Close shim file=%s fd=%d\n", shim_path, shim_fd);
    result = shim_close();
  }else{
    shim_init();
    result =  libc_close(fd);
  }

  return result;
}

