/**
 * fs_stub.c: TOYX stub filesystem (implementation).
 * Eric Cronin <ecronin@cis.upenn.edu>
 * $Id: fs_stub.c 35 2006-10-12 22:03:02Z ecronin $
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "fs_stub.h"

static unsigned char mounted = 0;
static char f_root[PATH_MAX] = "";
static unsigned int f_root_len = 0;

/**
 * For absolute paths, adds on the root; for relative paths, chroots.
 * NOTE: I'm lazy.  This breaks on things like /../../../.. (!= /)
 * 
 * Returns NULL if the path is outside the chroot or otherwise
 * invalid.
 */
static char *
f_chroot_pathname(const char *pathname) {
  char *chrootdir, *base;
  char tmp1[PATH_MAX], tmp2[PATH_MAX];
  static char retpath[PATH_MAX];
  
  if (pathname == NULL) {
    errno = EFAULT;
    return NULL;
  }
 
  if (pathname[0] == '/')  // absolute path
    snprintf(tmp1, PATH_MAX, "%s%s", f_root, pathname);
  else  // relative path
    snprintf(tmp1, PATH_MAX, "%s", pathname);

  // convert to cannonical path, make sure begins with f_root
  // Try realpath twice before giving up: Linux doesn't like it
  // if any portion of the path doesn't exist, but we may be trying
  // to create the last bit...
  if (realpath(tmp1, retpath) == NULL) {
    // Split into dir and base, realpath() the dir
    strncpy(tmp2, tmp1, PATH_MAX);
    chrootdir = dirname(tmp1);
    base = basename(tmp2);
    if (realpath(chrootdir, retpath) == NULL)
      return NULL;
    strncat(retpath, "/", PATH_MAX);
    strncat(retpath, base, PATH_MAX);
  }
  if (strncmp(f_root, retpath, f_root_len) != 0)
    return NULL;
  else if ( (strlen(retpath) > f_root_len) &&
            ( retpath[f_root_len] != '/') )
    return NULL;
  else
    return retpath;
}

int
f_open(const char *pathname, int flags, f_mode_t mode) {
  int fd = open(f_chroot_pathname(pathname), flags, mode);
  if (fd == -1)
    return fd;
  else
    return fd + 0x1badbeef;
}

int
f_read(int fd, void *buf, size_t count) {
  return read(fd - 0x1badbeef, buf, count);
}

int
f_write(int fd, const void *buf, size_t count) {
  return write(fd - 0x1badbeef, buf, count);
}

int
f_close(int fd) {
  return close(fd - 0x1badbeef);
}

off_t
f_lseek(int fd, off_t offset) {
  return lseek(fd - 0x1badbeef, offset, SEEK_SET);
}

int
f_stat(const char *pathname, struct f_stat *buf) {
  struct stat x;

  return stat(f_chroot_pathname(pathname), &x);
}

int
f_chmod(const char *pathname, f_mode_t mode) {
  return chmod(f_chroot_pathname(pathname), mode);
}

int
f_unlink(const char *pathname) {
  return unlink(f_chroot_pathname(pathname));
}

F_DIR *
f_opendir(const char *pathname) {
  return opendir(f_chroot_pathname(pathname));
}

struct f_dirent *
f_readdir(F_DIR *dir) {
  struct dirent *d;
  static struct f_dirent f_d;
  
  if ( (d=readdir(dir)) == NULL)
    return NULL;

  strncpy(f_d.d_name, d->d_name, sizeof(f_d.d_name));
  return &f_d;
}

int
f_closedir(F_DIR *dir) {
  return closedir(dir);
}

int
f_mkdir(const char *pathname, f_mode_t mode) {
  return mkdir(f_chroot_pathname(pathname), mode);
}

int
f_rmdir(const char *pathname) {
  return rmdir(f_chroot_pathname(pathname));
}

/**
 * "Mount" the directory at source as the root of our filesystem
 */
int
f_mount(const char *source, const char *target) {
  if (mounted != 0) {
    errno = EPERM;
    return -1;
  }
  if (source == NULL || target == NULL) {
    errno = EFAULT;
    return -1;
  }
 
  if (realpath(source, f_root) == NULL)
    return -1;

  f_root_len = strlen(f_root);

  return chdir(f_root);
}

int
f_umount(const char *target) {
  if (target == NULL) {
    errno = EFAULT;
    return -1;
  }

  mounted = 0;
  f_root[0] = '\0';
  f_root_len = 0;

  return 0;
}



#ifdef FS_STUB_TEST
int
main(int argc, char *argv[]) {
  int fd;
  int i;
  char c;
  F_DIR *dd;
  struct f_dirent *d;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <filesystem-root>\n", argv[0]);
    exit(1);
  }

  printf("=>Mounting %s at /.\n", argv[1]);
  assert(f_mount(argv[1], "/") == 0);

  printf("=>Creating directory \"/fs_stub_test\".\n");
  assert(f_mkdir("/fs_stub_test", F_S_IRWXU) >= 0);
  
  printf("=>Creating file \"fs_stub_test/alpha.txt\".\n");
  assert( (fd=f_open("fs_stub_test/alpha.txt",
          F_O_WRONLY|F_O_TRUNC|F_O_CREAT, F_S_IRUSR|F_S_IWUSR)) >= 0);

  printf("=>Writing the alphabet.\n");
  for (c = 'a'; c <= 'z'; c++) {
    assert(f_write(fd, &c, 1));
    printf("%c", c);
  }
  for (c = 'A'; c <= 'Z'; c++) {
    assert(f_write(fd, &c, 1));
    printf("%c", c);
  }
  printf("\n");

  printf("=>Closing file.\n");
  assert(f_close(fd) >= 0);

  printf("=>Opening the file \"/fs_stub_test/alpha.txt\" to read.\n");
  assert( (fd=f_open("/fs_stub_test/alpha.txt", F_O_RDONLY, 0)) >= 0);

  printf("=>Reading the alphabet with seek.\n");
  for (i=0; i < 26; i++) {
    assert(f_lseek(fd, i) >= 0);
    assert(f_read(fd, &c, 1));
    printf("%c", c);
    assert(f_lseek(fd, i + 26) >= 0);
    assert(f_read(fd, &c, 1));
    printf("%c", c);
  }
  printf("\n");

  printf("=>Closing file.\n");
  assert(f_close(fd) >= 0);

  printf("=>Removing write permission from \"fs_stub_test/alpha.txt\".\n");
  assert(f_chmod("fs_stub_test/alpha.txt", F_S_IRUSR) >= 0);

  printf("=>Opening the file \"/fs_stub_test/alpha.txt\" to write (fails).\n");
  assert( (fd=f_open("/fs_stub_test/alpha.txt", F_O_WRONLY, 0)) < 0);

  printf("=>Opening the directory \"fs_stub_test\".\n");
  assert( (dd=f_opendir("fs_stub_test")) != NULL);

  printf("=>Listing the directory.\n");
  while ( (d=f_readdir(dd)) != NULL )
    printf("Found: %s\n", d->d_name);
  
  printf("=>Closing the directory.\n");
  assert(f_closedir(dd) >= 0);

  printf("=>Deleting the file \"/fs_stub_test/alpha.txt\".\n");
  assert(f_unlink("/fs_stub_test/alpha.txt") >= 0);

  printf("=>Removing the directory \"/fs_stub_test\".\n");
  assert(f_rmdir("/fs_stub_test") >= 0);

  printf("=>Unmounting /.\n");
  assert(f_umount("/") == 0);

  exit(0);
}
#endif
