/*
  FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
  
  2010 Stef Bon <stefbon@gmail.com>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

  gcc -Wall `pkg-config fuse --cflags --libs` -lulockmgr fusexmp_ll_fh.c -o fusexmp_ll_fh
*/

#define FUSE_USE_VERSION 26
#define _REENTRANT
#define _GNU_SOURCE

#ifndef FUSE_SET_ATTR_ATIME_NOW
#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
#endif

#ifndef FUSE_SET_ATTR_MTIME_NOW
#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
#endif


#ifdef HAVE_CONFIG_H
#include <config.h>
#else
#define PACKAGE_VERSION "1.0"
#endif


#include <fuse/fuse_lowlevel.h>
#include <ulockmgr.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <err.h>
#include <sys/time.h>
#include <assert.h>
#include <syslog.h>

#ifdef HAVE_SETXATTR
#include <sys/xattr.h>
#endif

#define DEBUG 1

struct xmpfs_inode_struct {
	fuse_ino_t ino;
	uint64_t nlookup;
	struct xmpfs_inode_struct *id_next;
	struct xmpfs_entry_struct *alias;
};

struct xmpfs_entry_struct {
	char *name;
	struct xmpfs_inode_struct *inode;
	struct xmpfs_entry_struct *name_next;
	struct xmpfs_entry_struct **name_prevp;
	struct xmpfs_entry_struct *parent;
};



struct xmpfs_inode_struct **inode_hash_table;
static size_t id_table_size;
struct xmpfs_entry_struct **name_hash_table;
static size_t name_table_size;

struct xmpfs_options_struct {
     char *bind_directory;
     char *tmp_directory;
     char *conf_directory;
     int o_direct;
     int inplace;
};

struct xmpfs_dirent {
	mode_t mode;
	ino_t ino;
	char *name;
	struct xmpfs_dirent *next;
};

struct xmpfs_dirp {
	DIR *dp;
	bool read;
	struct dirent *entry;
	off_t offset;
	struct xmpfs_dirent *first;
};

struct xmpfs_options_struct xmpfs_options;

static struct fuse_chan *xmpfs_chan;
static unsigned pagesize;

typedef char pathstring[PATH_MAX + 1];

static double attr_timeout = 999999999.0;
static double entry_timeout = 999999999.0;
static double negative_timeout = 999999999.0;
static unsigned long long inoctr = 2;

static int bind_fd;
static int tmp_fd;
static int conf_fd;

enum {
     KEY_HELP,
     KEY_VERSION,
};

struct xmpfs_tmpdir_struct {
	pathstring path;
	unsigned long int fsid;
	struct xmpfs_tmpdir_struct *tmpdir_next;
	};

struct xmpfs_tmpdir_struct root_tmpdir;


#define MODEMASK 07777

#define XMPFS_OPT(t, p, v) { t, offsetof(struct xmpfs_options_struct, p), v }

static struct fuse_opt xmpfs_opts[] = {
     XMPFS_OPT("--bind-directory=%s",	bind_directory, 0),
     XMPFS_OPT("bind-directory=%s",	bind_directory, 0),
     XMPFS_OPT("--conf-directory=%s",	conf_directory, 0),
     XMPFS_OPT("conf-directory=%s",	conf_directory, 0),
     XMPFS_OPT("--tmp-directory=%s",	tmp_directory, 0),
     XMPFS_OPT("tmp-directory=%s",	tmp_directory, 0),
     XMPFS_OPT("o_direct=yes",		o_direct, 1),
     XMPFS_OPT("o_direct=no",		o_direct, 0),
     XMPFS_OPT("inplace=yes",		inplace, 1),
     XMPFS_OPT("inplace=no",		inplace, 0),
     FUSE_OPT_KEY("-V",             KEY_VERSION),
     FUSE_OPT_KEY("--version",      KEY_VERSION),
     FUSE_OPT_KEY("-h",             KEY_HELP),
     FUSE_OPT_KEY("--help",         KEY_HELP),
     FUSE_OPT_END
};


static void usage(const char *progname, FILE *f)
{
	fprintf(f, "usage: %s [opts] -o bind-directory=DIR,tmp-directory=DIR,o_direct=yes/no mountpoint\n",
		progname);
}

static int xmpfs_opt_proc(void *data, const char *arg, int key,
			  struct fuse_args *outargs)
{
	(void) arg;
	(void) data;

	switch (key) {
	case KEY_HELP:
		usage(outargs->argv[0], stdout);
		printf(
		"General options:\n"
		"    -o opt,[opt...]        mount options\n"
		"    -h   --help            print help\n"
		"    -V   --version         print version\n"
		"    -d   -o debug          enable debug output (implies -f)\n"
		"    -f                     foreground operation\n"
		"\n"
		"xmpfs options:\n"
		"    -o bind-directory=DIR        directory to bind\n"
		"    --bind-directory=DIR         same as bind-directory\n"
		"    -o tmp-directory=DIR         directory to store temporary files\n"
		"    ---tmp-directory=DIR         same as tmp-directory\n"
		"    -o o_direct=yes        	  enable O_DIRECT \n"
		"    -o o_direct=no               disable O_DIRECT (default)\n"
		"    -o inplace=yes        	  create in place \n"
		"    -o inplace=no                create in tmp directory first(default)\n"
		"\n"
		"FUSE options:\n");
		fflush(stdout);
		dup2(1, 2);
		fuse_opt_add_arg(outargs, "--help");
		fuse_mount(NULL, outargs);
		fuse_lowlevel_new(outargs, NULL, 0, NULL);
		exit(0);

	case KEY_VERSION:
		printf("xmpfs version %s\n", PACKAGE_VERSION);
		fflush(stdout);
		dup2(1, 2);
		fuse_opt_add_arg(outargs, "--version");
		fuse_parse_cmdline(outargs, NULL, NULL, NULL);
		fuse_lowlevel_new(outargs, NULL, 0, NULL);
		exit(0);
	}
	return 1;
}

static bool read_environment_var(pid_t pid)
{
    int fd;
    char *environment;
    size_t readlen;
    char *var, *value;
    char path[21];
    bool hiderootentries=true;

    environment=malloc(8192);

    snprintf(path, sizeof(path), "/proc/%d/environ", (int) pid);

    fd=open(path, O_RDONLY);

    if (fd<0) goto out;

    readlen=read(fd, environment, 8191);

    close(fd);

    environment[readlen]='\0';

    var=environment;

    while (var<environment+readlen) {

	if (strncmp(var, "SHOW_ROOT_ENTRIES=", 18) == 0) {
	
	    value=var+18;
	    
	    if ( strcmp(value, "1") == 0 ) {
	    
		hiderootentries=false;
		
	    }

	    break;

	}

	var+=strlen(var)+1;

    }

    out:

    free(environment);
    
    return hiderootentries;

}


static size_t inode_2_hash(fuse_ino_t inode)
{
	return inode % id_table_size;
}

static size_t name_2_hash(fuse_ino_t parent_inode, const char *name)
{
	uint64_t hash = parent_inode;

	for (; *name; name++)
		hash = hash * 31 + (unsigned char) *name;

	return hash % name_table_size;
}

static void add_to_inode_hash_table(struct xmpfs_inode_struct *inode)
{
	size_t idh = inode_2_hash(inode->ino);

	inode->id_next = inode_hash_table[idh];
	inode_hash_table[idh] = inode;
}


static void add_to_name_hash_table(struct xmpfs_entry_struct *entry)
{
	size_t tmphash = name_2_hash(entry->parent->inode->ino, entry->name);
	struct xmpfs_entry_struct **ep = &name_hash_table[tmphash];
	struct xmpfs_entry_struct *first = *ep;

	entry->name_next = first;
	if (first)
		first->name_prevp = &entry->name_next;
	*ep = entry;
	entry->name_prevp = ep;
}

static void remove_from_name_hash(struct xmpfs_entry_struct *entry)
{

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "remove_from_name_hash");

	if (entry->name_prevp) {
		struct xmpfs_entry_struct *next = entry->name_next;
		struct xmpfs_entry_struct **prevp = entry->name_prevp;

		*prevp = next;

		if (next) next->name_prevp = prevp;

		entry->name_prevp = NULL;
		entry->name_next = NULL;
	}
}


static struct xmpfs_inode_struct *find_inode(fuse_ino_t inode)
{
	struct xmpfs_inode_struct *tmpinode = inode_hash_table[inode_2_hash(inode)];
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "find_inode");

	while (tmpinode && tmpinode->ino != inode)
		tmpinode = tmpinode->id_next;

	if (!tmpinode) {
	    syslog(LOG_DEBUG, "internal error: inode %li not found", inode);
	} else {
	    syslog(LOG_DEBUG, "inode %li found", inode);
	}

	return tmpinode;
}


static struct xmpfs_entry_struct *find_entry(fuse_ino_t parent, const char *name)
{
	struct xmpfs_entry_struct *tmpentry = name_hash_table[name_2_hash(parent, name)];
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "find_entry, parent ino: %li, name: %s", parent, name);

	while (tmpentry) {
		
		if (tmpentry->parent && tmpentry->parent->inode->ino == parent &&
		    strcmp(tmpentry->name, name) == 0)
			// found: exit the loop here
			break;

		// not found yet: take the next one

		tmpentry = tmpentry->name_next;
	}
	
	if ( tmpentry ) {
	
	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "entry found");
	    
	} else {
	
	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "entry not found");
	    
	}

	return tmpentry;
}


static struct xmpfs_inode_struct *create_inode(fuse_ino_t ino)
{
	struct xmpfs_inode_struct *inode;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "create_inode");

	inode = malloc(sizeof(struct xmpfs_inode_struct));

	if (inode) {
		memset(inode, 0, sizeof(struct xmpfs_inode_struct));
		inode->ino = ino;
		inode->nlookup = 0;
	}

	return inode;
}

static struct xmpfs_entry_struct *create_entry(struct xmpfs_entry_struct *parent,
					  const char *name,
					  struct xmpfs_inode_struct *inode)
{
	struct xmpfs_entry_struct *entry;
	
	if ( inode != NULL ) {
	
	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "create_entry, name: %s, inode: %li", name, inode->ino);
	
	} else {
	
	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "create_entry, name: %s, no inode", name);

	}
	

	entry = malloc(sizeof(struct xmpfs_entry_struct));

	if (entry) {
		memset(entry, 0, sizeof(struct xmpfs_entry_struct));
		entry->name = strdup(name);
		if (!entry->name) {
		
			free(entry);
			entry = NULL;
		} else {
			entry->parent = parent;
			entry->inode = inode;
			if (inode != NULL) {
			    inode->alias=entry;
			    }
		}
	}

	return entry;
}

static struct xmpfs_entry_struct *new_entry(fuse_ino_t parent, const char *name)
{
	struct xmpfs_entry_struct *entry;
	struct xmpfs_inode_struct *inode;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "new_entry");

	inode = create_inode(inoctr++);
	if (!inode) return NULL;

	entry = create_entry(find_inode(parent)->alias, name, inode);
	if (!entry) {
		free(inode);
		return NULL;
	}
	add_to_inode_hash_table(inode);
	add_to_name_hash_table(entry);

	return entry;
}



static char *add_name(char *path, char *s, char *name, int slash)
{
	size_t namelen = strlen(name);
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "add_name, path: %s, s: %s", path, s);

	if (s - path < (long) namelen + (slash ? 1 : 0))
		return NULL;

	if (slash) {
		s--;
		*s = '/';
	}
	s -= namelen;

	//
	// copy the contents from name to s
	//

	memcpy(s, name, namelen);

	return s;
}

static int determine_path(pathstring path, struct xmpfs_entry_struct *entry)
{
	char *s = path + sizeof(pathstring) - 1;
	size_t lenname;

	*s = '\0';
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "determine_path.");
	
	lenname=strlen(entry->name);
	
	if ( s - path < (long) lenname ) {
	
	    return -ENAMETOOLONG;
	    
	    }
	
	s -= (long) lenname;
	
	memcpy(s, entry->name, lenname);
	
	// go to all the parents

	while (entry->parent != NULL) {

		entry = entry->parent;
		
		// stop when root is reached
		
		if (entry->inode->ino == FUSE_ROOT_ID) break;
		
		// add the name of this entry (at the start of path) and a slash to separate it

		s--;
		*s = '/';
		
		lenname=strlen(entry->name);
		
		if ( s - path < (long) lenname ) {
	
		    return -ENAMETOOLONG;
	    
		}

		s -= lenname;
		
		memcpy(s, entry->name, lenname);
		
	}

	//
	// move the path pointed by s back to the beginning of string path
	//

	memmove(path, s, sizeof(pathstring) - (s - path));

	return 0;
}


static int open_ino(struct xmpfs_entry_struct *entry, int flags)
{
	pathstring path;
	int res;
	
	// path=malloc(PATH_MAX+1);
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "open_ino");

	res = determine_path(path, entry);

	if (res < 0) return res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "open_ino, path: %s", path);

	res = openat(bind_fd, path, flags);

	if (res == -1) res = -errno;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "open_ino, res: %i", res);

	return res;
}

static int opendir_ino(struct xmpfs_entry_struct *entry, DIR **dp)
{
	int res;
	int fd;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "opendir_ino");

	// fd = open_ino(entry, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
	
	fd = open_ino(entry, O_RDONLY | O_DIRECTORY );
	
	if (fd < 0) return fd;

	*dp = fdopendir(fd);
	if (!*dp) {
		res = -errno;
		close(fd);
		return res;
	}

	return 0;
}


static int check_directory_is_empty(struct xmpfs_entry_struct *entry)
{
	int res;
	DIR *dp;
	struct dirent *de;
	bool is_empty = true;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "check_directory_is_empty");

	res = opendir_ino(entry, &dp);

	if (res < 0) return res;

	while ((de = readdir(dp)) != NULL) {
		if (strcmp(de->d_name, ".") != 0 &&
		    strcmp(de->d_name, "..") != 0) {
			is_empty = false;
			break;
		}
	}
	closedir(dp);

	return is_empty ? 0 : -ENOTEMPTY;
}

//
// determine the stat call given an entry
//

static int stat_ino(struct xmpfs_entry_struct *entry, struct stat *st)
{
	pathstring path;
	int res, res2;
	struct stat testst;

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "stat_ino");

	// path=malloc(PATH_MAX+1);

	res = determine_path(path, entry);

	if (res < 0) return res;

	res = fstatat(bind_fd, path, st, AT_SYMLINK_NOFOLLOW);
	
	if (res == -1) return -errno;

	if ( S_ISLNK(st->st_mode) ) {

	    if ( conf_fd != 0 ) {

		res2=fstatat(conf_fd, path, &testst, AT_SYMLINK_NOFOLLOW);
		
		if ( res2 != -1 ) {

		    if ( S_ISDIR(testst.st_mode)) {

			// take the stat of the target (follow the link)

			res=fstatat(bind_fd, path, st, 0);

			if (res == -1) res = -errno;

		    }
		}
	    }
	}

	return res;
}

static int do_opendir(fuse_ino_t ino, struct xmpfs_dirp *d)
{
	struct xmpfs_entry_struct *dir = find_inode(ino)->alias;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_opendir");

	memset(d, 0, sizeof(struct xmpfs_dirp));

	res = opendir_ino(dir, &d->dp);

	if (res < 0)
		return res;

	d->offset = 0;
	d->entry = NULL;

	return 0;
}

static int unlink_ino(struct xmpfs_entry_struct *entry, int flags)
{
	pathstring path;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "unlink_ino");

	res = determine_path(path, entry);

	if (res < 0) return res;

	res = unlinkat(bind_fd, path, flags);

	if (res == -1) res = -errno;

	return res;
}

static int link_from_ino(struct xmpfs_entry_struct *entry, int fd, const char *name)
{
	pathstring path;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "link_from_ino");

	res = determine_path(path, entry);

	if (res < 0) return res;

	res = linkat(bind_fd, path, fd, name, 0);

	if (res == -1) res = -errno;

	return res;
}


static int readlink_ino(struct xmpfs_entry_struct *entry, char **link)
{
	pathstring path;
	int size = 4096;
	char *buf;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "readlink_ino");
	
	// path=malloc(PATH_MAX+1);

	res = determine_path(path, entry);

	if (res < 0) return res;

	do {
		buf = malloc(size);

		if (buf == NULL) {
			res = -ENOMEM;
			break;
		}

		res = readlinkat(bind_fd, path, buf, size);

		if (res == -1) {
			res = -errno;
			free(buf);
			break;
		}

		if (res < size) {
			buf[res] = '\0';
			*link = buf;
			res = 0;
			break;
		}

		free(buf);
		size *= 2;

	} while (true);

	return res;
}

static int setattr_path(int fd, const char *path, struct stat *st, int to_set)
{
	int res;
	int tmpfd;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "setattr_path");

	if (to_set & FUSE_SET_ATTR_MODE) {
		res = fchmodat(fd, path, st->st_mode, 0);
		if (res == -1) return -errno;
	}
	if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
		uid_t uid = (to_set & FUSE_SET_ATTR_UID) ?
			st->st_uid : (uid_t) -1;
		gid_t gid = (to_set & FUSE_SET_ATTR_GID) ?
			st->st_gid : (gid_t) -1;

		res = fchownat(fd, path, uid, gid, AT_SYMLINK_NOFOLLOW);
		
		if (res == -1) return -errno;
	}
	if (to_set & FUSE_SET_ATTR_SIZE) {

		tmpfd = openat(fd, path, O_WRONLY);
		if (fd == -1) return -errno;

		res = ftruncate(tmpfd, st->st_size);
		if (res == -1) {
			res = -errno;
			close(tmpfd);
			return res;
		}
		close(tmpfd);
	}
	if (to_set & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
		struct timespec times[2];

		times[0].tv_sec = 0;
		times[1].tv_sec = 0;
		times[0].tv_nsec = UTIME_OMIT;
		times[1].tv_nsec = UTIME_OMIT;


		if (to_set & FUSE_SET_ATTR_ATIME_NOW)
			times[0].tv_nsec = UTIME_NOW;
		else if (to_set & FUSE_SET_ATTR_ATIME)
			times[0] = st->st_atim;

		if (to_set & FUSE_SET_ATTR_MTIME_NOW)
			times[1].tv_nsec = UTIME_NOW;
		else if (to_set & FUSE_SET_ATTR_MTIME)
			times[1] = st->st_mtim;

		res = utimensat(fd, path, times, AT_SYMLINK_NOFOLLOW);

		if (res == -1) return -errno;

	}
	return 0;
}

//
// lookup an inode
// if it does not exists create it here
//

static int do_lookup(fuse_ino_t parent_inode, const char *name,
		     struct fuse_entry_param *e)
{
	int res;
	struct xmpfs_entry_struct *entry = find_entry(parent_inode, name);
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_lookup");

	if (!entry) {
	
		// if not found create it here
		// note that the entry does not have to exist, so for now it's temporarly

		entry = new_entry(parent_inode, name);

		if (!entry) return -ENOMEM;
	}

	// check it does exist in the underlying fs

	res = stat_ino(entry, &e->attr);

	// if it does not exist remove the just created entry again 

	if (res == -ENOENT) {
		remove_from_name_hash(entry);
		/* remove entry */
		e->ino = 0;
		e->entry_timeout = negative_timeout;
		return 0;
	}

	if (res < 0)
		return res;

	entry->inode->nlookup++;

	e->ino = entry->inode->ino;
	e->attr.st_ino = e->ino;
	e->generation = 0;
	e->attr_timeout = attr_timeout;
	e->entry_timeout = entry_timeout;

	return 0;
}


static void xmpfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
{
	struct fuse_entry_param e;
	int res = do_lookup(parent, name, &e);
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "lookup");
	
	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_entry(req, &e);
}

static int do_getattr(fuse_ino_t ino, struct stat *st)
{
	struct xmpfs_entry_struct *entry = find_inode(ino)->alias;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_getattr, inode: %li", ino);
	
	if ( entry != NULL ) {
	
	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_getattr, entry, name: %s", entry->name);
	    
	} else {
	
	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_getattr, entryname empty");
	    
	}

	res = stat_ino(entry, st);

	if (res < 0) return res;

	st->st_ino = entry->inode->ino;

	return 0;
}

static void xmpfs_forget(fuse_req_t req, fuse_ino_t ino, unsigned long nlookup)
{
	struct xmpfs_inode_struct *tmpinode = find_inode(ino);
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "forget");

	if (tmpinode->nlookup < nlookup) {
		fprintf(stderr, "internal error: forget ino=%llu %llu from %llu\n",
		     (unsigned long long) ino, (unsigned long long) nlookup,
		     (unsigned long long) tmpinode->nlookup);
	}

	tmpinode->nlookup -= nlookup;
	//if (tmpinode->nlookup == 0) {
		// struct xmpfs_entry_struct *entry = tmpinode->alias;

		/* remove entry */
	//}
	fuse_reply_none(req);
}

static void xmpfs_getattr(fuse_req_t req, fuse_ino_t ino,
			  struct fuse_file_info *fi)
{
	struct stat st;
	int res;

	(void) fi;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "getattr");

	res = do_getattr(ino, &st);

	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_attr(req, &st, attr_timeout);
}



static int rename_to_ino(int rfd, const char *name, struct xmpfs_entry_struct *entry)
{
	pathstring path;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "rename_to_ino");

	res = determine_path(path, entry);

	if (res < 0) return res;

	res = renameat(rfd, name, bind_fd, path);

	if (res == -1) res = -errno;

	return res;
}


static int set_owner(int rfd, const char *path, struct stat *st)
{
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "set_owner");

	res = fchownat(rfd, path, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW);

	if (res == -1) return -errno;

	return 0;
}

static int set_times(int rfd, const char *path, struct stat *st)
{
	int res;
	struct timespec times[2];
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "set_times");

	times[0] = st->st_atim;
	times[1] = st->st_mtim;
	res = utimensat(rfd, path, times, AT_SYMLINK_NOFOLLOW);
	if (res == -1)
		return -errno;

	return 0;
}

static int set_times_ino(struct xmpfs_entry_struct *entry, struct stat *st)
{
	pathstring path;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "set_times_ino");

	res = determine_path(path, entry);

	if (res < 0) return res;

	res = set_times(bind_fd, path, st);

	return res;
}

static int setattr_ino(struct xmpfs_entry_struct *entry, struct stat *st, int to_set)
{
	pathstring path;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "setattr_ino");

	res = determine_path(path, entry);
	
	if (res < 0) return res;

	res = setattr_path(bind_fd, path, st, to_set);

	return res;
}

static int do_setattr(fuse_ino_t ino, struct stat *st, int to_set)
{
	struct xmpfs_entry_struct *tmpentry = find_inode(ino)->alias;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_setattr");

	res = setattr_ino(tmpentry, st, to_set);

	return stat_ino(tmpentry, st);
}

static void xmpfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
			  int to_set, struct fuse_file_info *fi)
{
	int res;

	(void) fi;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "setattr");

	res = do_setattr(ino, attr, to_set);

	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_attr(req, attr, attr_timeout);
}

static int do_remove(struct xmpfs_entry_struct *entry, bool is_dir)
{
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_remove");

	if (is_dir) {
		res = check_directory_is_empty(entry);
		if (res < 0) return res;
	}

	// res = create_parent(entry);

	// if (res < 0) return res;

	res = unlink_ino(entry, 0);

	if (res < 0) return res;

	remove_from_name_hash(entry);

	// remove the inode??

	free(entry->name);
	free(entry);
	return 0;

}


static int do_newnode(struct xmpfs_entry_struct *entry, struct stat *st,
		      struct fuse_entry_param *e, const char *link)
{
	int res;
	char *tmpname = "tmp.create";
	int unlinkflag = 0;
	pathstring path;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_newnode");

	// is this call necessary?
	// res = create_parent(entry);

	// if (res < 0) return res;
	// dit werkt niet!! er is nog geen entry
	res=determine_path(path, entry);
	
	if (res<0) return res;

	if (S_ISDIR(st->st_mode)) {

		// directory

		unlinkflag |= AT_REMOVEDIR;

		if ( xmpfs_options.inplace) {

		    res = mkdirat(bind_fd, path, st->st_mode);

		} else {

		    res = mkdirat(tmp_fd, tmpname, st->st_mode);

		}

		if (res == -1)res = -errno;

	} else if (S_ISREG(st->st_mode)) {

		// regular file

		if ( xmpfs_options.inplace ) {
		
		    res = openat(bind_fd, path, O_WRONLY | O_CREAT | O_EXCL, st->st_mode);
		    
		} else {

		    res = openat(tmp_fd, tmpname, O_WRONLY | O_CREAT | O_EXCL, st->st_mode);
		}

		if (res == -1) res = -errno;
		else {
			close(res);
			res = 0;
		}

	} else if (S_ISLNK(st->st_mode)) {

		// link (symbolic?)

		if ( xmpfs_options.inplace ) {

		    res = symlinkat(link, bind_fd, path);

		} else {

		    res = symlinkat(link, tmp_fd, tmpname);

		}

		if (res == -1) res = -errno;

	} else {
	
		// everything else
		
		if ( xmpfs_options.inplace ) {

		    res = mknodat(bind_fd, path, st->st_mode, st->st_rdev);

		} else {

		    res = mknodat(tmp_fd, tmpname, st->st_mode, st->st_rdev);

		}

		if (res == -1) res = -errno;

	}

	if (res == 0) {

	    if  ( xmpfs_options.inplace ) {

		res = fchownat(bind_fd, path, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW);

	    } else {

		res = fchownat(tmp_fd, tmpname, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW);

	    }

	    if (res == -1) res = -errno;

	}

	if (res == 0) {

	    if ( xmpfs_options.inplace ) {

		res = fstatat(bind_fd, path, &e->attr, AT_SYMLINK_NOFOLLOW);

	    } else {

		res = fstatat(tmp_fd, tmpname, &e->attr, AT_SYMLINK_NOFOLLOW);

	    }
	    
	    if (res == -1) res = -errno;

	}

	if (res == 0) {

	    if ( xmpfs_options.inplace == 0) {

		// note: this does not work if bind and tmp are on different devices!!

		res = renameat(tmp_fd, tmpname, bind_fd, path);

	    }

	}

	if (res == 0) {

		entry->inode->nlookup++;
		e->ino = entry->inode->ino;
		e->attr.st_ino = e->ino;
		e->generation = 0;
		e->attr_timeout = attr_timeout;
		e->entry_timeout = entry_timeout;

	} else {

		if ( xmpfs_options.inplace ) {

		    unlinkat(bind_fd, path, unlinkflag);

		} else {

		    unlinkat(tmp_fd, tmpname, unlinkflag);

		}
	}

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_newnode, res: %i", res);

	return res;
}


static void xmpfs_newnode(fuse_req_t req, fuse_ino_t parent, const char *name,
			  mode_t mode, dev_t rdev, const char *link)
{
	int res;
	struct fuse_entry_param e;
	struct xmpfs_entry_struct *entry;
	const struct fuse_ctx *ctx = fuse_req_ctx(req);

	struct stat st = {
		.st_mode = mode,
		.st_rdev = rdev,
		.st_uid = ctx->uid,
		.st_gid = ctx->gid,
	};
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "newnode");

	/* fixme: make sure the entry doesn't exist before mknod */
	entry = find_entry(parent, name);

	if (!entry) {
		entry = new_entry(parent, name);
		if (!entry) {
			fuse_reply_err(req, ENOMEM);
			return;
		}
	}

	res = do_newnode(entry, &st, &e, link);

	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_entry(req, &e);
}

static void xmpfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
			mode_t mode, dev_t rdev)
{
	xmpfs_newnode(req, parent, name, mode, rdev, NULL);
}

static void xmpfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
			mode_t mode)
{
	xmpfs_newnode(req, parent, name, (mode & MODEMASK) | S_IFDIR , 0, NULL);
}

static void xmpfs_symlink(fuse_req_t req, const char *link, fuse_ino_t parent,
			  const char *name)
{
	xmpfs_newnode(req, parent, name, S_IFLNK, 0, link);
}

static void xmpfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{
	int res;
	struct xmpfs_entry_struct *entry;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "unlink");

	entry = find_entry(parent, name);
	res = do_remove(entry, false);
	
	fuse_reply_err(req, -res);
}

static void xmpfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
{
	int res;
	struct xmpfs_entry_struct *entry;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "rmdir");

	entry = find_entry(parent, name);

	res = do_remove(entry, true);

	fuse_reply_err(req, -res);
}

static void xmpfs_readlink(fuse_req_t req, fuse_ino_t ino)
{
	struct xmpfs_entry_struct *tmpentry = find_inode(ino)->alias;
	char *link;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "readlink");

	res = readlink_ino(tmpentry, &link);

	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_readlink(req, link);

	free(link);
}

static int do_rename(struct xmpfs_entry_struct *entry, struct xmpfs_entry_struct *newentry)
{
	int res;
	struct stat st;
	pathstring path;
	bool dst_exists = false;
	bool dst_is_dir = false;
	char *newname = NULL;
	struct xmpfs_entry_struct *newparent;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_rename");
	
	// test the newpath already exists
	// if so then it will be replaced (in case of dir, it must be empty!)

	res = stat_ino(newentry, &st);

	if (res == 0) {

	    dst_exists = true;
	    if (S_ISDIR(st.st_mode)) dst_is_dir = true;

	} else if (res != -ENOENT) {

	    return res;

	}

	if (dst_exists && dst_is_dir) {
	    res = check_directory_is_empty(newentry);
	    if (res < 0) return res;
	}

	res = determine_path(path, entry);

	if (res < 0) goto error;

	res = -ENOMEM;
	newname = strdup(newentry->name);
	if (newname == NULL) goto error;

	res = rename_to_ino(bind_fd, path, newentry);

	if (res < 0) goto error;

	newparent = newentry->parent;

	if (dst_exists) {
#if FUSE_VERSION >= 28
		/* kernel module forgot to invalidate target attributes */
		fuse_lowlevel_notify_inval_inode(xmpfs_chan, newentry->inode->ino,
						 -1, -1);
#endif
		remove_from_name_hash(newentry);
	}

	remove_from_name_hash(entry);
	free(entry->name);
	entry->name = newname;
	entry->parent = newparent;
	add_to_name_hash_table(entry);

	return 0;

error:

	free(entry->name);

	return res;
}

static void xmpfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
			 fuse_ino_t newparent, const char *newname)
{
	int res;
	struct xmpfs_entry_struct *entry;
	struct xmpfs_entry_struct *tmpnewentry;
	bool created = false;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "rename, name: %s, new name: %s", name, newname);

	entry = find_entry(parent, name);
	tmpnewentry = find_entry(newparent, newname);

	if (!tmpnewentry) {
	
		if ( DEBUG > 0 ) syslog(LOG_DEBUG, "tmpnewentry not found......");

		struct xmpfs_entry_struct *parententry = find_inode(newparent)->alias;
		
		tmpnewentry = create_entry(parententry, newname, NULL);

		if (tmpnewentry == NULL) {
			fuse_reply_err(req, ENOMEM);
			return;
		}
		created = true;
	}

	res = do_rename(entry, tmpnewentry);

	if (created) {
	    free(tmpnewentry->name);
	    free(tmpnewentry);
	}

	fuse_reply_err(req, -res);
}

static int do_link(struct xmpfs_entry_struct *entry, struct xmpfs_entry_struct *newentry,
		   struct fuse_entry_param *e)
{
	char *tmplink = "tmp.link";
	pathstring path, targetpath;
	int res;

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_link");

	res = determine_path(targetpath, entry);

	if ( res < 0 ) return res;

	res = determine_path(path, newentry);

	if ( res < 0 ) return res;
	
	if ( xmpfs_options.inplace ) {

	    res = linkat(bind_fd, targetpath, bind_fd, path,0);

	} else {

	    res = link_from_ino(entry, tmp_fd, tmplink);

	}

	if (res < 0) return res;
	
	if ( xmpfs_options.inplace ) {

	    res = fstatat(bind_fd, path, &e->attr, AT_SYMLINK_NOFOLLOW);

	} else {

	    res = fstatat(tmp_fd, tmplink, &e->attr, AT_SYMLINK_NOFOLLOW);

	}

	if (res == -1) {
		res = -errno;
		goto cleanup_tmp;
	}
	
	if ( xmpfs_options.inplace == 0) {

	    res = renameat(tmp_fd, tmplink, bind_fd, path);

	    }

	if (res < 0) goto cleanup_tmp;

	add_to_name_hash_table(newentry);

	newentry->inode->nlookup++;

	e->ino = newentry->inode->ino;
	e->attr.st_ino = e->ino;
	e->generation = 0;
	e->attr_timeout = attr_timeout;
	e->entry_timeout = entry_timeout;

	return 0;

cleanup_tmp:

	unlinkat(tmp_fd, tmplink, 0);

	return res;
}

// create a link to ino from newparent/newname

static void xmpfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
		const char *newname)
{
	int res;
	struct xmpfs_entry_struct *entry;
	struct xmpfs_entry_struct *newdir;
	struct xmpfs_entry_struct *newentry;
	struct fuse_entry_param e;

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "link");

	entry = find_inode(ino)->alias;
	newdir = find_inode(newparent)->alias;

	newentry = create_entry(newdir, newname, entry->inode);

	if (!newentry) {
		fuse_reply_err(req, ENOMEM);
		return;
	}

	res = do_link(entry, newentry, &e);

	if (res < 0) {
		free(newentry->name);
		free(newentry);
		fuse_reply_err(req, -res);
	} else {
		fuse_reply_entry(req, &e);
	}

}
static void free_dirlist(struct xmpfs_dirent **listp)
{
	while (*listp) {
		struct xmpfs_dirent *e = *listp;

		*listp = e->next;
		free(e->name);
		free(e);
	}
}

static inline struct xmpfs_dirp *get_dirp(struct fuse_file_info *fi)
{
	return (struct xmpfs_dirp *) (uintptr_t) fi->fh;
}

static void free_dirp(struct xmpfs_dirp *d)
{
	free_dirlist(&d->first);
	free(d);
}

static void xmpfs_opendir(fuse_req_t req, fuse_ino_t ino,
			  struct fuse_file_info *fi)
{
	struct xmpfs_dirp *d;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "opendir");

	d = malloc(sizeof(struct xmpfs_dirp));

	if (d == NULL) {
		fuse_reply_err(req, ENOMEM);
		return;
	}

	res = do_opendir(ino, d);

	if (res < 0) {
		free_dirp(d);
		fuse_reply_err(req, -res);
	} else {
		fi->fh = (unsigned long) d;
		fuse_reply_open(req, fi);
	}
}

static int entry_to_stat(struct xmpfs_entry_struct *dir, struct dirent *entry, struct stat *st)
{
	const char *name;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "entry_to_stat");

	name = entry->d_name;
	memset(st, 0, sizeof(struct stat));
	st->st_mode = entry->d_type << 12;
	st->st_ino = entry->d_ino;
	if (strcmp(name, ".") == 0) {
		st->st_ino = dir->inode->ino;
	} else if (strcmp(name, "..") == 0) {
		if (dir->inode->ino == FUSE_ROOT_ID)
			st->st_ino = FUSE_ROOT_ID;
		else
			st->st_ino = dir->parent->inode->ino;
	} else  {
		struct xmpfs_entry_struct *entry2;

		entry2 = find_entry(dir->inode->ino, name);
		if (!entry2) {
			entry2 = new_entry(dir->inode->ino, name);
			if (entry2 == NULL)
				return -ENOMEM;
		}

		st->st_ino = entry2->inode->ino;
	}

	return 0;
}

static int my_readdir(DIR *dp, struct dirent **entp)
{
	errno = 0;
	*entp = readdir(dp);

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "my_readdir");

	if (*entp == NULL) {
		if (errno)
			return -errno;
		return 0;
	}
	return 1;
}

static int do_readdir(fuse_req_t req, fuse_ino_t ino, char *buf, size_t size,
			off_t off, struct xmpfs_dirp *d)
{
	size_t bufpos = 0;
	struct xmpfs_entry_struct *entry = find_inode(ino)->alias;
	int res;
	bool add_entry;
	pathstring dirpath, path;
	struct stat confst;
	int readlink_size=4096;
	char *readlink_buf;
	const struct fuse_ctx *ctx = fuse_req_ctx(req);
	bool hiderootentries;

	// create a buffer to read a link. This is only used to read links in the root
	// so there is no need for a bigger size.
	readlink_buf=malloc(readlink_size);

	hiderootentries=read_environment_var(ctx->pid);

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_readdir");

	if (off != d->offset) {
		seekdir(d->dp, off);
		d->entry = NULL;
		d->offset = off;
	}

	// path of the directory

	res = determine_path(dirpath, entry);
	
	if (res<0) return res;
	
	while (size - bufpos) {
		struct stat st;
		off_t nextoff;
		size_t entsize;
		add_entry=true;

		if (!d->entry) {
			res = my_readdir(d->dp, &d->entry);
			if (res < 0)
				return res;
			if (!res)
				break;
		}

		res = entry_to_stat(entry, d->entry, &st);

		if (res < 0) return res;

		if ( conf_fd != 0 ) {

		    if ( S_ISLNK(st.st_mode) ) {
		    
			// test the link is a "root" directory

			if ( ino == FUSE_ROOT_ID && hiderootentries) {

			    res=readlinkat(bind_fd, d->entry->d_name, readlink_buf, readlink_size);

			    if ( res != -1) {
			    
				if ( res < readlink_size ) {
				
				    readlink_buf[res]='\0';
				    
				    snprintf(path, sizeof(pathstring), "/%s", d->entry->d_name);

				    if ( strcmp(path, readlink_buf) == 0 ) {

					// skip this entry: it's only there to build a valid chroot environment
					
					add_entry=false;
					    
				    }
				}
			    }
			}

			if ( add_entry) { 
			
			    // a link, make it a directory if the same dir exists in conf

			    snprintf(path, sizeof(pathstring), "%s/%s", dirpath, d->entry->d_name);

			    res=fstatat(conf_fd, path, &confst, AT_SYMLINK_NOFOLLOW);

			    if ( res != -1 ) {
			    
				// this is not completely right, the mode of the target should be taken... but later
				// it will be overwritten by getattr, for now it's sufficient to make this a directory

				if ( S_ISDIR(confst.st_mode)) st.st_mode=confst.st_mode;

			    }
			}

		    } else if ( S_ISDIR(st.st_mode) && ino == FUSE_ROOT_ID && hiderootentries) {


			// a directory: test the .hide file exists in conf


			snprintf(path, sizeof(pathstring), "%s/.hide", d->entry->d_name);

			res=fstatat(conf_fd, path, &confst, AT_SYMLINK_NOFOLLOW);

			if ( res != -1 ) {
			
			    if ( S_ISREG(confst.st_mode)) add_entry=false;
			}
		    }
		}
		
		nextoff = telldir(d->dp);
		
		if ( add_entry) {

		    entsize = fuse_add_direntry(req, buf + bufpos, size - bufpos,
					    d->entry->d_name, &st, nextoff);
		    if (entsize > size - bufpos)
			    break;

		    bufpos += entsize;
		}
		
		d->entry = NULL;
		d->offset = nextoff;
	}
	return bufpos;
}


static void xmpfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
			     off_t off, struct fuse_file_info *fi)
{
	struct xmpfs_dirp *d = get_dirp(fi);
	char *buf;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "readdir");

	buf = malloc(size);

	if (buf == NULL) {
		fuse_reply_err(req, ENOMEM);
		return;
	}

	res = do_readdir(req, ino, buf, size, off, d);

	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_buf(req, buf, res);
	free(buf);
}

static void xmpfs_releasedir(fuse_req_t req, fuse_ino_t ino,
			     struct fuse_file_info *fi)
{
	struct xmpfs_dirp *d = get_dirp(fi);

	(void) ino;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "releasedir");

	closedir(d->dp);
	free_dirp(d);
	fuse_reply_err(req, 0);
}

static int do_open(fuse_ino_t ino, struct fuse_file_info *fi)
{
	struct xmpfs_entry_struct *entry = find_inode(ino)->alias;
	int fd;
	int flags = (fi->flags & O_ACCMODE) | O_NOFOLLOW;

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_open");

	if (xmpfs_options.o_direct) flags |= O_DIRECT;

	fd = open_ino(entry, flags);

	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "do_open: call open_ino, res: %i", fd);

	if (fd < 0) return fd;

	fi->fh = fd;
	fi->keep_cache=1;
	fi->nonseekable=0;
	
	return 0;
}

static void xmpfs_open(fuse_req_t req, fuse_ino_t ino,
			struct fuse_file_info *fi)
{
	int res = do_open(ino, fi);
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "open");
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "open, waarde fi->nonseekable :%i", fi->nonseekable);
	
	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_open(req, fi);
}

static void xmpfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
		       off_t off, struct fuse_file_info *fi)
{
	int res;
	void *buf;

	(void) ino;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "read");
	
	res = posix_memalign(&buf, pagesize, size);
	if (res != 0) {
		fuse_reply_err(req, res);
		return;
	}
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "read, waarde fi->nonseekable :%i", fi->nonseekable);
	
	if ( fi->nonseekable ) {
	
	    // non seekable -> use the default read command
	    
	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "read: normal read, without offset");

	    res = read(fi->fh, buf, size);

	} else {

	    // seekable -> use the pread command using the offset

	    if ( DEBUG > 0 ) syslog(LOG_DEBUG, "read: pread with offset");

	    res = pread(fi->fh, buf, size, off);
	    
	}
	
	if (res == -1)
		fuse_reply_err(req, errno);
	else
		fuse_reply_buf(req, buf, res);
	free(buf);
}

static void xmpfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
			size_t size, off_t off, struct fuse_file_info *fi)
{
	int res;

	(void) ino;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "write");

	res = pwrite(fi->fh, buf, size, off);

	if (res == -1)
		fuse_reply_err(req, errno);
	else
		fuse_reply_write(req, res);
}


static void xmpfs_release(fuse_req_t req, fuse_ino_t ino,
			  struct fuse_file_info *fi)
{
	(void) ino;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "release");

	close(fi->fh);
	fuse_reply_err(req, 0);
}

static void xmpfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
			struct fuse_file_info *fi)
{
	int res;

	(void) ino;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "fsync");

	if (datasync)
		res = fdatasync(fi->fh);
	else
		res = fsync(fi->fh);
	if (res == -1)
		fuse_reply_err(req, errno);
	else
		fuse_reply_err(req, 0);
}

static int do_statfs(struct statvfs *st)
{
	int res;

	res = fstatvfs(bind_fd, st);

	if (res < 0) return -errno;

	return 0;
}

static void xmpfs_statfs(fuse_req_t req, fuse_ino_t ino)
{
	struct statvfs st;
	int res;

	(void) ino;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "statfs");

	res = do_statfs(&st);

	if (res < 0)
		fuse_reply_err(req, -res);
	else
		fuse_reply_statfs(req, &st);
}

#ifdef HAVE_SETXATTR

static void xmpfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value,
			size_t size, int flags)
{
	struct xmpfs_entry_struct *entry = find_inode(ino)->alias;
	pathstring path;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "setxattr");

	res = determine_path(path, entry);
	
	if (res < 0) return res;

	res = fsetxattr(bind_fd, path, value, size, flags);
	
	if (res == -1)
		fuse_reply_err(req, errno);
	else
		fuse_reply_err(req, 0);
}

static void xmpfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
{
	pathstring path;
	int res;
	const char *value;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "getxattr");

	res = determine_path(path, entry);
	
	if (res < 0) return res;

	res = fgetxattr(bind_fd, name, value, size);
	
	if ( res == -1 ) { 

	    fuse_reply_err(req, errno);

	} else {

	    if ( size == 0 ) {

		fuse_reply_xattr(req, strlen(value));

	    } else if ( strlen(value) <= size ) {

		fuse_reply_buf(req, value, size);

	    } else {

		fuse_reply_err(req, ERANGE);
		
	    }
	    
	}
}

static void xmpfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
{
	pathstring path;
	ssize_t nlenlist;
	char *list;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "listxattr");

	res = determine_path(path, entry);
	
	if (res < 0) return res;

	nlenlist = flistxattr(bind_fd, list, size);
	
	if ( nlenlist == -1 ) { 

	    fuse_reply_err(req, errno);

	} else {

	    if ( nlenlist == 0 ) {

		fuse_reply_xattr(req, strlen(list));

	    } else if ( strlen(list) <= size ) {

		fuse_reply_buf(req, list, strlen(list));

	    } else {

		fuse_reply_err(req, ERANGE);
		
	    }
	    
	}
}

static void xmpfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
{
	struct xmpfs_entry_struct *entry = find_inode(ino)->alias;
	pathstring path;
	int res;
	
	if ( DEBUG > 0 ) syslog(LOG_DEBUG, "removexattr");

	res = determine_path(path, entry);
	
	if (res < 0) return res;

	res = fremovexattr(bind_fd, path, name);
	
	if (res == -1)
		fuse_reply_err(req, errno);
	else
		fuse_reply_err(req, 0);
}
#endif

static void xmpfs_getlk (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
{
    int res;
    
    res=ulockmgr_op(fi->fh, F_GETLK, lock, &fi->lock_owner, sizeof(fi->lock_owner));
    
    if ( res == 0 ) {
    
	// success
	
	fuse_reply_lock(req, lock);
	
    } else {

	// error: return -res 

	fuse_reply_err(req, -res);
    }
}

static void xmpfs_setlk (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
{
    int res;
    
    if ( sleep ) {
    
	res=ulockmgr_op(fi->fh, F_SETLKW, lock, &fi->lock_owner, sizeof(fi->lock_owner));
	
    } else {
    
	res=ulockmgr_op(fi->fh, F_SETLK, lock, &fi->lock_owner, sizeof(fi->lock_owner));
	
    }
    
    if ( res == 0 ) {
    
	// success
	
	fuse_reply_err(req, 0);
	
    } else {

	// error: return -res 

	fuse_reply_err(req, -res);
    }
}

static struct fuse_lowlevel_ops xmpfs_oper = {
	.lookup		= xmpfs_lookup,
	.forget		= xmpfs_forget,
	.getattr	= xmpfs_getattr,
	.setattr	= xmpfs_setattr,
	.readlink	= xmpfs_readlink,
	.mknod		= xmpfs_mknod,
	.mkdir		= xmpfs_mkdir,
	.unlink		= xmpfs_unlink,
	.rmdir		= xmpfs_rmdir,
	.symlink	= xmpfs_symlink,
	.rename		= xmpfs_rename,
	.link		= xmpfs_link,
	.open		= xmpfs_open,
	.read		= xmpfs_read,
	.write		= xmpfs_write,
	.release	= xmpfs_release,
	.fsync		= xmpfs_fsync,
	.opendir	= xmpfs_opendir,
	.readdir	= xmpfs_readdir,
	.releasedir	= xmpfs_releasedir,
	.statfs		= xmpfs_statfs,
#ifdef HAVE_SETXATTR
	.setxattr	= xmpfs_setxattr,
	.getxattr	= xmpfs_getxattr,
	.listxattr	= xmpfs_listxattr,
	.removexattr	= xmpfs_removexattr,
#endif
 	.getlk		= xmpfs_getlk,
 	.setlk		= xmpfs_setlk,
};


void unslash(char *p)
{
    char *q = p;
    char *pkeep = p;

    while ((*q++ = *p++) != 0) {

	if (q[-1] == '/') {
	
	    while (*p == '/') {
	    
		p++;
	    }
	    
	}
    }

    if (q > pkeep + 2 && q[-2] == '/') q[-2] = '\0';
}

/*static pathstring get_tmpdir(pathstring path)
{
    struct xmpfs_tmpdir_struct *tmpdir = &root_tmpdir;
    struct statvfs *tmpstatvfs;
    int fd;
    int res;

    fd=openat(bind_fd, path, O_RDONLY);

    if (fd>0) {

	res=fstatvfs(fd, tmpstatvfs);

	if (res!=-1) {

	    while ( tmpdir->fsid != tmpstatvfs->f_fsid ) {

		if ( tmpdir->tmpdir_next ) {

		    tmpdir=tmpdir->tmpdir_next;
		} else break;

	    }

	    if ( tmpdir->fsid == tmpstatvfs->f_fsid ) {

		*/


int main(int argc, char *argv[])
{
    struct fuse_args xmpfs_args = FUSE_ARGS_INIT(argc, argv);
    struct xmpfs_inode_struct *root_inode;
    struct xmpfs_entry_struct *root;
    struct fuse_chan *tmp_chan;
    char *xmpfs_mountpoint;
    int foreground=0;
    int res;

    umask(0);
    pagesize = getpagesize();

    res = fuse_opt_parse(&xmpfs_args, &xmpfs_options, xmpfs_opts, xmpfs_opt_proc);

    if (res == -1) {
	fprintf(stderr, "Error parsing option.\n");
	exit(1);
	}

    res = fuse_opt_insert_arg(&xmpfs_args, 1, "-oallow_other,default_permissions,nonempty,big_writes,dev,suid");

    if ( xmpfs_options.bind_directory == NULL ) {

	xmpfs_options.bind_directory = "/";
	fprintf(stdout, "directory to bind to not defined, taking the root (/)\n");

	} else {

	unslash(xmpfs_options.bind_directory);

    }

    fprintf(stdout, "taking bind directory %s\n", xmpfs_options.bind_directory);

    bind_fd = open(xmpfs_options.bind_directory, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);

    if (bind_fd == -1) {

	fprintf(stderr, "Error, failed to open %s\n", xmpfs_options.bind_directory);
	exit(1);

    }

    if ( xmpfs_options.tmp_directory == NULL ) {

	xmpfs_options.tmp_directory = "/tmp";
	
	fprintf(stdout, "tmp directory not defined, taking /tmp\n");

	} else {

	unslash(xmpfs_options.tmp_directory);

    }

    tmp_fd = open(xmpfs_options.tmp_directory, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);

    if (tmp_fd == -1) {

	fprintf(stderr, "Error, failed to open %s\n", xmpfs_options.tmp_directory);
	exit(1);

    }
    
    if ( xmpfs_options.conf_directory != NULL ) {

	xmpfs_options.tmp_directory = "/tmp";
	
	fprintf(stdout, "conf directory defined\n");

	unslash(xmpfs_options.conf_directory);

	conf_fd = open(xmpfs_options.conf_directory, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);

	if (conf_fd == -1) {

	    fprintf(stderr, "Error, failed to open %s\n", xmpfs_options.conf_directory);
	    exit(1);

	}
    }

    id_table_size = 32768;
    name_table_size = 32768;

    inode_hash_table = calloc(id_table_size, sizeof(struct xmpfs_entry_struct *));
    name_hash_table = calloc(name_table_size, sizeof(struct xmpfs_entry_struct *));

    if (inode_hash_table == NULL || name_hash_table == NULL)
		fprintf(stderr, "memory allocation failed.\n");

    root_inode=create_inode(FUSE_ROOT_ID);
    root=create_entry(NULL,".",root_inode);

    if ( root == NULL || root_inode == NULL ) {

	fprintf(stderr, "Error, failed to create the root inode and/or entry.\n");
	exit(1);

    }

    add_to_inode_hash_table(root_inode);

    res = -1;

    if (fuse_parse_cmdline(&xmpfs_args, &xmpfs_mountpoint, NULL, &foreground) != -1 ) {
    
	if ( (tmp_chan = fuse_mount(xmpfs_mountpoint, &xmpfs_args)) != NULL) {
	
	    struct fuse_session *xmpfs_session;

	    xmpfs_session=fuse_lowlevel_new(&xmpfs_args, &xmpfs_oper, sizeof(xmpfs_oper), NULL);

	    if ( xmpfs_session != NULL ) {

		res = fuse_daemonize(foreground);
		
		if (res==0) {
		
		    if ( fuse_set_signal_handlers(xmpfs_session) != -1) {

			fuse_session_add_chan(xmpfs_session, tmp_chan);
			xmpfs_chan=tmp_chan;

			res=fuse_session_loop(xmpfs_session);

			fuse_remove_signal_handlers(xmpfs_session);
			
			fuse_session_remove_chan(tmp_chan);

		    }

		}

		fuse_session_destroy(xmpfs_session);
	    } else {
	    
		fprintf(stderr, "Error starting a new session.\n");
		
	    }

	    fuse_unmount(xmpfs_mountpoint, tmp_chan);

	} else {
	
	    fprintf(stderr, "Error mounting and setting up a channel.\n");
	}

    } else {
    
	fprintf(stderr, "Error parsing options.\n");
    }

    fuse_opt_free_args(&xmpfs_args);

    return res ? 1 : 0;
}

