/*
    LZOlayer FileSystem, version5
    Author: Micha³ Kazior
    
    Copyrights reserved, blah blah
    
    Use it at your OWN RISK
    Absolutely NO WARANTY
*/
#define FUSE_USE_VERSION 23
#include <fuse.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define __USE_LARGEFILE64
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#define __USE_UNIX98
#include <unistd.h>
#include <zlib.h>
#if !defined(O_LARGEFILE)
#  define O_LARGEFILE 0
#endif

#include "minilzo.h"
#define HEAP_ALLOC(var, size) \
  lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) -1)) / sizeof(lzo_align_t) ]

static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);

#define max_packets 2048
off_t block_size = 131072;
#define __LZOlayer_DEBUG 0
#define VERSION "20060202-2"

static char *srcPath;
static int zlibCompression = 0;

int errno;

typedef struct 
{
  unsigned char *data;
         size_t  size;
          off_t  offset;
} LZOlayer_write_packet;
            

typedef struct
{
           char *out_buf;
           char *path;
      short int  mod;
          off_t  size;
            int  packet_count;

  LZOlayer_write_packet
                 packets[max_packets];
  LZOlayer_write_packet
                 cache;
} LZOlayer_file;



static char *LZOlayer_makePath(const char *path)
{
  char *xPath = (char *)calloc(strlen(path)+strlen(srcPath)+1, sizeof(char));
  memcpy(xPath, srcPath, strlen(srcPath));
  memcpy(xPath+strlen(srcPath), path, strlen(path));
  
  return xPath;
}  

static int LZOlayer_read_block(int fd, unsigned char *mem)
{
  off_t nblock_size,
        cblock_size;
  
  if (read(fd, &nblock_size, sizeof(off_t)) != sizeof(off_t)) return 0;
  if (read(fd, &cblock_size, sizeof(off_t)) != sizeof(off_t)) return 0;
  
  unsigned char *compr = malloc(cblock_size);
  if (read(fd, compr, cblock_size) != cblock_size) { free(compr); return 0; }
  if (cblock_size > 0)
  {
    if(!zlibCompression)
      lzo1x_decompress(compr, cblock_size, mem, (uLong *)&nblock_size, NULL);
    else
      uncompress(mem, (uLong *)&nblock_size, compr, cblock_size);
  }
  free(compr);
  
  return nblock_size;
}

static int LZOlayer_block_seek(int fd, int block_start)
{
  lseek(fd, sizeof(off_t), SEEK_SET);
  int curr_block = 0;

  off_t nblock_size,
        cblock_size;

  while(curr_block != block_start)
  {
    read(fd, &nblock_size, sizeof(off_t));
    read(fd, &cblock_size, sizeof(off_t));
    
    lseek(fd, cblock_size, SEEK_CUR);
    curr_block++;
  }
  
  return 0;
}  


static int LZOlayer_getattr(const char *path, struct stat *stbuf)
{
  int   res   = 0;
  char *xPath = LZOlayer_makePath(path);
  
  memset(stbuf, 0, sizeof(struct stat));
  if ((res = lstat(xPath, stbuf)) != 0) res = -errno;
  
  if (S_ISREG(stbuf->st_mode))
  {
    int fd = open(xPath, O_RDONLY|O_LARGEFILE);
    read(fd, &stbuf->st_size, sizeof(off_t));
    close(fd);
  }
  if (S_ISLNK(stbuf->st_mode))
  {
    char *mem = calloc(4096, sizeof(char));
    readlink(xPath, mem, 4097);
    stbuf->st_size = strlen(mem);
    free(mem);
  }
  free(xPath);
  
  return res;
}


static int LZOlayer_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
  char     dpath[MAXPATHLEN+1];
  char     *xPath = LZOlayer_makePath(path);
  char     *p;

  DIR *dp = opendir(xPath);
  struct dirent *dirp;
  struct stat sb;

  strcpy(dpath, path);
  p = dpath + strlen(path);
  
  while(dp)
  {
    if ((dirp = readdir(dp)) != NULL) {
      strcpy(p, dirp->d_name);
      if (LZOlayer_getattr(dpath, &sb))
        filler(buf, dirp->d_name, NULL, 0);
      else
        filler(buf, dirp->d_name, &sb, 0);
    }
    else break;
  }
  
  closedir(dp);
  free(xPath);
  
  return 0;
}


static int LZOlayer_open(const char *path, struct fuse_file_info *fi)
{
  char *xPath = LZOlayer_makePath(path);
  
  int fd = open(xPath, O_RDONLY|O_LARGEFILE);
  off_t outLen = 0;
  read(fd, &outLen, sizeof(off_t));
  close(fd);
  
  LZOlayer_file *filePtr = malloc(sizeof(LZOlayer_file));
  filePtr->out_buf       = malloc(block_size*1.2);
  filePtr->mod           = 0;
  filePtr->path          = xPath;
  filePtr->size          = outLen;
  filePtr->packet_count  = 0;
  filePtr->cache.data    = malloc(block_size);
  filePtr->cache.size    = 0;
  filePtr->cache.offset  = 0;
  
  fi->fh = (uintptr_t)filePtr;
  
  return 0;
}


static int LZOlayer_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
  LZOlayer_file *filePtr = (LZOlayer_file *)fi->fh;
  char *xPath            = filePtr->path;
  
  int done = 0;
  int fd = open(xPath, O_RDONLY|O_LARGEFILE);
  
  while(1)
  {
    if ( ((offset+done) < filePtr->cache.offset) ||
         ((offset+done) >= filePtr->cache.offset+filePtr->cache.size) ||
         (filePtr->cache.size == 0)
       )
    {
      int block_start = ((offset+done)/block_size);
      LZOlayer_block_seek(fd, block_start);
      
      filePtr->cache.size   = LZOlayer_read_block(fd, filePtr->cache.data);
      filePtr->cache.offset = block_start*block_size;
      if (filePtr->cache.size == 0) break;
    }
    else
    {
      int read_size = (filePtr->cache.size - ((offset+done)%block_size));
      if (read_size < 0) read_size = 0;
      if (read_size > (size-done)) read_size = (size-done);
      memcpy(buf+done, filePtr->cache.data+((offset+done)%block_size), read_size);
      
      done += read_size;
      
      if ((read_size == 0) || (done >= size) || (filePtr->size <= offset+done))
      {
        done = size;
        break;
      }
    }
  }
  close(fd);
  
  return done;
}

static int LZOlayer_packet_sync(const char *path, struct fuse_file_info *fi)
{
  LZOlayer_file *filePtr = (LZOlayer_file *)fi->fh;
  int i,
      min_offset = 0,
      max_offset = 0;
  
  
  for(i=0;i<filePtr->packet_count;i++)
  {
    if(filePtr->packets[max_offset].offset+filePtr->packets[max_offset].size <
       filePtr->packets[i].offset+filePtr->packets[i].size) max_offset = i;
    if(filePtr->packets[min_offset].offset > filePtr->packets[i].offset) min_offset = i;
  }
  
  off_t to_write      = (filePtr->packets[max_offset].offset+filePtr->packets[max_offset].size) - filePtr->packets[min_offset].offset;
  off_t block_start   = (float)filePtr->packets[min_offset].offset / (float)block_size;
  
  char *xPath = filePtr->path;
  int fd      = open(xPath, O_RDWR|O_LARGEFILE);
  LZOlayer_block_seek(fd, block_start);
  
  off_t alloc_size = (filePtr->size-(block_start*block_size)
                        >
                      filePtr->packets[max_offset].offset+filePtr->packets[max_offset].size-(block_start*block_size)
                     ) ? filePtr->size-(block_start*block_size) : filePtr->packets[max_offset].offset+filePtr->packets[max_offset].size-(block_start*block_size);
  unsigned char *mem = malloc(alloc_size);
  unsigned char *out = malloc(block_size*1.2);
  to_write=alloc_size; /// fix 
  for(i=0;LZOlayer_read_block(fd, mem+(i*block_size));i++);
  for(i=0;i<filePtr->packet_count;i++)
  {
    memcpy(mem+filePtr->packets[i].offset-(block_start*block_size), filePtr->packets[i].data, filePtr->packets[i].size);
    free(filePtr->packets[i].data);
  }
  
  LZOlayer_block_seek(fd, block_start);
  int curr_block = 0;
  off_t nblock_size,
        cblock_size;
  while(to_write > 0)
  {
    nblock_size = (to_write > block_size) ? block_size : to_write;
// no need to do that i think
//    cblock_size = ((256*256*256*256)*(256*256*256*256))-1;
//    lzo_init();
//    cblocks = (256*256*256*256)-1;

    if(!zlibCompression)
    {
      cblock_size = 0;
      lzo1x_1_compress(mem+(curr_block*block_size), nblock_size, out, (uLong *)&cblock_size, wrkmem);
    }
     else
    {
      uLong cblocks = (256*256*256*256)-1;
      compress(out, &cblocks, mem+(curr_block*block_size), nblock_size);
      cblock_size = cblocks;
    }

    curr_block ++;
    to_write -= block_size;
    write(fd, &nblock_size, sizeof(off_t));
    write(fd, &cblock_size, sizeof(off_t));
    write(fd, out, cblock_size);
  }
  
  free(out);
  free(mem);
  close(fd);
  
  filePtr->size = (filePtr->size > alloc_size + (block_start*block_size)) ? filePtr->size : alloc_size + (block_start*block_size);
  filePtr->packet_count = 0;
  
  return 0;
}

static int LZOlayer_release(const char *path, struct fuse_file_info *fi)
{
  LZOlayer_file *filePtr = (LZOlayer_file *)fi->fh;
  
  if (filePtr->mod)
  {
    LZOlayer_packet_sync(path, fi);
    
    int fd = open(filePtr->path, O_WRONLY|O_LARGEFILE);
    if(fd == -1)
    {
      open(filePtr->path, O_CREAT|O_WRONLY|O_LARGEFILE);
      chown(filePtr->path, fuse_get_context()->uid, fuse_get_context()->gid);
    }
// old open/create for write
//    int fd = open(filePtr->path, O_CREAT|O_WRONLY);
    lseek(fd, 0, SEEK_SET);
    write(fd, &filePtr->size, sizeof(off_t));
    close(fd);
  }
  
  free(filePtr->cache.data);
  free(filePtr->out_buf);
  free(filePtr->path);
  free(filePtr);
  
  return 0;
}

static int LZOlayer_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
  LZOlayer_file *filePtr = (LZOlayer_file *)fi->fh;
  filePtr->mod  = 1;
  
  if (filePtr->packet_count >= max_packets)
    LZOlayer_packet_sync(path, fi);
  
  filePtr->packets[filePtr->packet_count].data   = malloc(size);
  memcpy(filePtr->packets[filePtr->packet_count].data, buf, size);
  filePtr->packets[filePtr->packet_count].size   = size;
  filePtr->packets[filePtr->packet_count].offset = offset;
  filePtr->packet_count++;
  
  return size;
}


static int LZOlayer_mknod(const char *path, mode_t mode, dev_t rdev)
{
  char *xPath = LZOlayer_makePath(path);
  int   res;

  if (S_ISREG(mode)) {
    res = open(xPath, O_WRONLY | O_LARGEFILE | O_CREAT | O_TRUNC, 0700);
    if (res != -1) {
      close(res);
      res = 0;
    }
  } else {
    res = mknod(xPath, mode, rdev);
  }

  if (res == -1)
  {
    res = -errno;
  }
  else
  {
    int fd = open(xPath, O_WRONLY|O_LARGEFILE);
    off_t null = 0;
    write(fd, &null, sizeof(off_t));
    write(fd, &null, sizeof(off_t));
    write(fd, &null, sizeof(off_t));
    close(fd);
	 
    chown(xPath, fuse_get_context()->uid, fuse_get_context()->gid);
    chmod(xPath, mode);
  }
  free(xPath);
  
  return res;
}

static int LZOlayer_truncate(const char *path, off_t size)
{
  char *xPath = LZOlayer_makePath(path);
  
  int fd = open(xPath, O_RDWR|O_LARGEFILE);
  off_t file_size = 0;
  read(fd, &file_size, sizeof(off_t));
  
  int start_block = size / block_size;

  if (file_size >= size)
  {
    if (size == 0)
    {
      lseek(fd, 0, SEEK_SET);
      write(fd, &size, sizeof(off_t));
      write(fd, &size, sizeof(off_t));
      write(fd, &size, sizeof(off_t));
      off_t curr = lseek(fd, 0, SEEK_CUR);
      ftruncate(fd, curr);
      close(fd);
    }
    else
    {
    unsigned char *mem = malloc(block_size);
    unsigned char *out = malloc(block_size*1.2);
    LZOlayer_block_seek(fd, start_block);
    LZOlayer_read_block(fd, mem);
    LZOlayer_block_seek(fd, start_block);
    
    off_t nblock_size = size % block_size;
    off_t cblock_size = 0;
// no need to do that i think
//    lzo_init();
//    cblocks = (256*256*256*256)-1;
//    compress(out, &cblocks, mem+(curr_block*block_size), nblock_size);
    lzo1x_1_compress(mem, nblock_size, out, (uLong *)&cblock_size, wrkmem);
    
    write(fd, &nblock_size, sizeof(off_t));
    write(fd, &cblock_size, sizeof(off_t));
    write(fd, out, cblock_size);
    
    off_t curr_pos = lseek(fd, 0, SEEK_CUR);
    ftruncate(fd, curr_pos);
    
    lseek(fd, 0, SEEK_SET);
    write(fd, &size, sizeof(off_t));
    
    close(fd);
    free(mem);
    free(out);
    }
  }
  else
  {
    off_t to_write = size-(block_size*start_block);
    unsigned char *mem = calloc(to_write, sizeof(char));
    unsigned char *out = malloc(block_size*1.2);
    
    if (file_size > 0)
    {
      LZOlayer_block_seek(fd, start_block);
      int i;
      for(i=0;LZOlayer_read_block(fd, mem+(i*block_size));i++);
    }

    int curr_block = 0;
    off_t nblock_size,
          cblock_size;
    while(to_write > 0)
    {
      nblock_size = (to_write > block_size) ? block_size : to_write;
      cblock_size = 0;
// no need to do that i think
//      lzo_init();
//      cblocks = (256*256*256*256)-1;
//      compress(out, &cblocks, mem+(curr_block*block_size), nblock_size);
      lzo1x_1_compress(mem+(curr_block*block_size), nblock_size, out, (uLong *)&cblock_size, wrkmem);
      curr_block ++;
      to_write -= block_size;
      
      write(fd, &nblock_size, sizeof(off_t));
      write(fd, &cblock_size, sizeof(off_t));
      write(fd, out, cblock_size);
    }
    
    off_t curr_pos = lseek(fd, 0, SEEK_CUR);
    ftruncate(fd, curr_pos);
    
    lseek(fd, 0, SEEK_SET);
    write(fd, &size, sizeof(off_t));
    
    close(fd);
    free(mem);
    free(out);
  }
  
  free(xPath);
  
  return 0;
}

static int LZOlayer_unlink(const char *path)
{
  char *xPath = LZOlayer_makePath(path);
  unlink(xPath);
  free(xPath);
  
  return 0;
}

static int LZOlayer_mkdir(const char *path, mode_t mode)
{
  char *xPath = LZOlayer_makePath(path);
  mkdir(xPath, mode);
  chown(xPath, fuse_get_context()->uid, fuse_get_context()->gid);
  free(xPath);
  
  return 0;
}

static int LZOlayer_rmdir(const char *path)
{
  char *xPath = LZOlayer_makePath(path);
  rmdir(xPath);
  free(xPath);
  
  return 0;
}

static int LZOlayer_chmod(const char *path, mode_t mode)
{
  char *xPath = LZOlayer_makePath(path);
  chmod(xPath, mode);
  free(xPath);

  return 0;
}

static int LZOlayer_chown(const char *path, uid_t uid, gid_t gid)
{
  char *xPath = LZOlayer_makePath(path);
  chown(xPath, uid, gid);
  free(xPath);
  
  return 0;
}

static int LZOlayer_symlink(const char *from, const char *to)
{
  char *xTo = LZOlayer_makePath(to);
  symlink(from, xTo);
  free(xTo);
  
  return 0;
}

static int LZOlayer_readlink(const char *path, char *buf, size_t size)
{
  char *xPath = LZOlayer_makePath(path);
  int res = readlink(xPath, buf, size-1);
  free(xPath);
  buf[res] = '\0';
  
  return 0;
}

static int LZOlayer_rename(const char *from, const char *to)
{
  char *fromPath = LZOlayer_makePath(from);
  char *toPath   = LZOlayer_makePath(to);
  
  int res = rename(fromPath, toPath);
  if (res == -1) res = -errno;
  
  return res;
}

static int LZOlayer_link(const char *from, const char *to)
{
  char *fromPath = LZOlayer_makePath(from);
  char *toPath   = LZOlayer_makePath(to);
  
  int res = link(fromPath, toPath);
  if (res == -1) res = -errno;
  
  free(fromPath);
  free(toPath);
  
  return res;
}

static int LZOlayer_utime(const char *path, struct utimbuf *buf)
{
  char *xPath = LZOlayer_makePath(path);
  
  int res = utime(xPath, buf);
  if(res == -1) res = -errno;
  
  free(xPath);
  
  return res;
}


static struct fuse_operations LZOlayer_oper = {
  .getattr  = LZOlayer_getattr,
  .mknod    = LZOlayer_mknod,
  .readdir  = LZOlayer_readdir,
  .open     = LZOlayer_open,
  .release  = LZOlayer_release,
  .read     = LZOlayer_read,
  .write    = LZOlayer_write,
  .truncate = LZOlayer_truncate, 
  .unlink   = LZOlayer_unlink,
  .mkdir    = LZOlayer_mkdir,
  .rmdir    = LZOlayer_rmdir,
  .chown    = LZOlayer_chown,
  .chmod    = LZOlayer_chmod,
  .symlink  = LZOlayer_symlink,
  .readlink = LZOlayer_readlink,
  .rename   = LZOlayer_rename,
  .link     = LZOlayer_link,
  .utime    = LZOlayer_utime,
};


void usage(char *appName)
{
  char **argv = (char **)malloc(2*sizeof(char *));
  argv[0] = appName;
  argv[1] = (char *)malloc(3*sizeof(char));
  memcpy(argv[1], "-h\0", 3);
  fuse_main(2, argv, &LZOlayer_oper);
  
  printf("\n"
         "-------------------------------------------------------\n"
         "LZOlayer_fs %s\n"
         "\n"
         "Usage: %s [source_path] [mount_point] <-zlib> <FUSE OPTIONS>\n"
         "\n"
         "  [source_path] - place where compressed files will be/are stored\n"
         "  -zlib         - use ZLIB compression instead of LZO\n"
         "\n"
         "-------------------------------------------------------\n",
         VERSION, appName);
}


int main(int argc, char **argv)
{
  char **argv_new = (char **)malloc(argc*sizeof(char *));
   int   argc_new = argc - 1;
  
  
  
  if((argc > 1)&&(strcmp(argv[1], "-h") == 0))
  {
    usage(argv[0]);
    return 0;
  }
  
  if(argc < 3)
  {
    usage(argv[0]);
    return -1;
  }
  
  if(argv[1][0] != '/')
  {
    char *pwd = (char *)calloc(1024, sizeof(char));
    if(getcwd(pwd, 1024) == NULL)
    {
      printf("-------------------------------------------------------\n\n");
      perror("LZOlayer_fs: getcwd() for srcPath");
      printf("\n"
             "-------------------------------------------------------\n"
             "getcwd() failed, thus LZOlayer_fs cannot continue\n"
             "-------------------------------------------------------\n"
             );
      return -1;
    }
	 else
	 {
      srcPath = (char *)calloc((strlen(pwd)+strlen(argv[1])+2), sizeof(char));
		strcat(srcPath, pwd);
		strcat(srcPath, "/");
	 }
  }
  else
  {
    srcPath   = (char *)calloc(strlen(argv[1])+1, sizeof(char));
  }
  strcat(srcPath, argv[1]);
  
  if((argc > 3)&&(strcmp(argv[3], "-zlib")==0))
  {
    printf("zlibCompression Enabled\n");
    zlibCompression = 1;
    argc_new--;
  }
  
  if(close(open(srcPath, O_RDONLY)) == -1)
  {
    printf("-------------------------------------------------------\n\n");
    perror("LZOlayer_fs: srcPath");
    printf("\n"
           "-------------------------------------------------------\n"
           "Please check if %s exists. If not, create it\n"
           "or check if you have proper permissions to it.\n"
           "-------------------------------------------------------\n"
           , srcPath);
	 return -1;
  }
  
  argv_new[0] = argv[0];
  
  int i;
  for(i=1; i<argc-1; i++)
    argv_new[i] = argv[i+1];
  
  if(zlibCompression)
    for(i=2; i<argc-1; i++)
      argv_new[i] = argv_new[i+1];
  
  return fuse_main(argc_new, argv_new, &LZOlayer_oper);
}
