/* -*-  mode:c; tab-width:8; c-basic-offset:8; indent-tabs-mode:nil;  -*- */
/*
   Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1 of the License, or
   (at your option) any later version.

   This program 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 Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
 * High level api to nfs filesystems
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef AROS
#include "aros_compat.h"
#endif

#ifdef PS2_EE
#include "ps2_compat.h"
#endif

#ifdef PS3_PPU
#include "ps3_compat.h"
#endif

#ifdef WIN32
#include <win32/win32_compat.h>
#endif

#ifdef HAVE_UTIME_H
#include <utime.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_VFS_H
#include <sys/vfs.h>
#endif

#ifdef HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif

#if defined(__ANDROID__) && !defined(HAVE_SYS_STATVFS_H)
#define statvfs statfs
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "slist.h"
#include "libnfs.h"
#include "libnfs-raw.h"
#include "libnfs-raw-mount.h"
#include "libnfs-raw-portmap.h"
#include "libnfs-private.h"

void
nfs_free_nfsdir(struct nfsdir *nfsdir)
{
	while (nfsdir->entries) {
		struct nfsdirent *dirent = nfsdir->entries->next;
		if (nfsdir->entries->name != NULL) {
			free(nfsdir->entries->name);
		}
		free(nfsdir->entries);
		nfsdir->entries = dirent;
	}
	free(nfsdir->fh.val);
	free(nfsdir);
}

void
nfs_dircache_add(struct nfs_context *nfs, struct nfsdir *nfsdir)
{
	int i = 0;
	LIBNFS_LIST_ADD(&nfs->dircache, nfsdir);

	for (nfsdir = nfs->dircache; nfsdir; nfsdir = nfsdir->next, i++) {
		if (i > MAX_DIR_CACHE) {
			LIBNFS_LIST_REMOVE(&nfs->dircache, nfsdir);
			nfs_free_nfsdir(nfsdir);
			break;
		}
	}
}

struct nfsdir *
nfs_dircache_find(struct nfs_context *nfs, struct nfs_fh *fh)
{
	struct nfsdir *nfsdir;

	for (nfsdir = nfs->dircache; nfsdir; nfsdir = nfsdir->next) {
		if (nfsdir->fh.len == fh->len &&
		    !memcmp(nfsdir->fh.val, fh->val, fh->len)) {
			LIBNFS_LIST_REMOVE(&nfs->dircache, nfsdir);
			return nfsdir;
		}
	}

	return NULL;
}

void
nfs_dircache_drop(struct nfs_context *nfs, struct nfs_fh *fh)
{
	struct nfsdir *cached;

	cached = nfs_dircache_find(nfs, fh);
	if (cached) {
		nfs_free_nfsdir(cached);
	}
}

static uint32_t
nfs_pagecache_hash(struct nfs_pagecache *pagecache, uint64_t offset) {
	return (2654435761UL * (1 + ((uint32_t)(offset) / NFS_BLKSIZE))) &
                (pagecache->num_entries - 1);
}

void
nfs_pagecache_invalidate(struct nfs_context *nfs, struct nfsfh *nfsfh) {
	if (nfsfh->pagecache.entries) {
		RPC_LOG(nfs->rpc, 2, "invalidating pagecache");
		memset(nfsfh->pagecache.entries, 0x00,
                       sizeof(struct nfs_pagecache_entry) *
                       nfsfh->pagecache.num_entries);
	}
}

void
nfs_pagecache_put(struct nfs_pagecache *pagecache, uint64_t offset,
                  const char *buf, size_t len)
{
	time_t ts = pagecache->ttl ? (time_t)(rpc_current_time() / 1000) : 1;
	if (!pagecache->num_entries) return;
	while (len > 0) {
		uint64_t page_offset = offset & ~(NFS_BLKSIZE - 1);
		uint32_t entry = nfs_pagecache_hash(pagecache, page_offset);
		struct nfs_pagecache_entry *e = &pagecache->entries[entry];
		size_t n = MIN(NFS_BLKSIZE - offset % NFS_BLKSIZE, len);

		/* we can only write to the cache if we add a full page or
		 * partially update a page that is still valid */
		if (n == NFS_BLKSIZE ||
		    (e->ts && e->offset == page_offset &&
		     (!pagecache->ttl || ts - e->ts <= pagecache->ttl))) {
			e->ts = ts;
			e->offset = page_offset;
			memcpy(e->buf + offset % NFS_BLKSIZE, buf, n);
		}
		buf += n;
		offset += n;
		len -= n;
	}
}

char *
nfs_pagecache_get(struct nfs_pagecache *pagecache, uint64_t offset)
{
	uint32_t entry;
	struct nfs_pagecache_entry *e;

	entry = nfs_pagecache_hash(pagecache, offset);
	e = &pagecache->entries[entry];

	if (offset != e->offset) {
		return NULL;
	}
	if (!e->ts) {
		return NULL;
	}
	if (pagecache->ttl && (time_t)(rpc_current_time() / 1000) - e->ts > pagecache->ttl) {
		return NULL;
	}

	return e->buf;
}

void nfs_pagecache_init(struct nfs_context *nfs, struct nfsfh *nfsfh) {
	/* init page cache */
	if (nfs->rpc->pagecache) {
		nfsfh->pagecache.num_entries = nfs->rpc->pagecache;
		nfsfh->pagecache.ttl = nfs->rpc->pagecache_ttl;
		nfsfh->pagecache.entries = malloc(sizeof(struct nfs_pagecache_entry) * nfsfh->pagecache.num_entries);
		nfs_pagecache_invalidate(nfs, nfsfh);
		RPC_LOG(nfs->rpc, 2, "init pagecache entries %d pagesize %d\n",
                        nfsfh->pagecache.num_entries, NFS_BLKSIZE);
	}
}

void
nfs_set_auth(struct nfs_context *nfs, struct AUTH *auth)
{
	rpc_set_auth(nfs->rpc, auth);
}

int
nfs_get_fd(struct nfs_context *nfs)
{
	return rpc_get_fd(nfs->rpc);
}

int
nfs_queue_length(struct nfs_context *nfs)
{
	return rpc_queue_length(nfs->rpc);
}

int
nfs_which_events(struct nfs_context *nfs)
{
	return rpc_which_events(nfs->rpc);
}

int
nfs_service(struct nfs_context *nfs, int revents)
{
	return rpc_service(nfs->rpc, revents);
}

char *
nfs_get_error(struct nfs_context *nfs)
{
	return rpc_get_error(nfs->rpc);
};

#ifdef HAVE_SO_BINDTODEVICE
void
nfs_set_interface(struct nfs_context *nfs, const char *ifname)
{
	rpc_set_interface(nfs_get_rpc_context(nfs), ifname);
}
#endif

static int
nfs_set_context_args(struct nfs_context *nfs, const char *arg, const char *val)
{
	if (!strcmp(arg, "tcp-syncnt")) {
		rpc_set_tcp_syncnt(nfs_get_rpc_context(nfs), atoi(val));
	} else if (!strcmp(arg, "uid")) {
		rpc_set_uid(nfs_get_rpc_context(nfs), atoi(val));
	} else if (!strcmp(arg, "gid")) {
		rpc_set_gid(nfs_get_rpc_context(nfs), atoi(val));
	} else if (!strcmp(arg, "readahead")) {
		rpc_set_readahead(nfs_get_rpc_context(nfs), atoi(val));
	} else if (!strcmp(arg, "pagecache")) {
		rpc_set_pagecache(nfs_get_rpc_context(nfs), atoi(val));
	} else if (!strcmp(arg, "debug")) {
		rpc_set_debug(nfs_get_rpc_context(nfs), atoi(val));
	} else if (!strcmp(arg, "auto-traverse-mounts")) {
		nfs->auto_traverse_mounts = atoi(val);
	} else if (!strcmp(arg, "dircache")) {
		nfs_set_dircache(nfs, atoi(val));
	} else if (!strcmp(arg, "autoreconnect")) {
		nfs_set_autoreconnect(nfs, atoi(val));
#ifdef HAVE_SO_BINDTODEVICE
	} else if (!strcmp(arg, "if")) {
		nfs_set_interface(nfs, val);
#endif
	} else if (!strcmp(arg, "version")) {
		if (nfs_set_version(nfs, atoi(val)) < 0) {
			nfs_set_error(nfs, "NFS version %d is not supported",
				      atoi(val));
			return -1;
		}
	} else if (!strcmp(arg, "nfsport")) {
		nfs->nfsport =  atoi(val);
	} else if (!strcmp(arg, "mountport")) {
		nfs->mountport =  atoi(val);
	}
	return 0;
}

static int
tohex(char ch)
{
        if (ch >= '0' && ch <= '9') {
                return ch - '0';
        }
        ch &= 0xDF;
        if (ch >= 'A' && ch <= 'F') {
                return ch - 'A' + 10;
        }
        return -1;
}

static struct nfs_url *
nfs_parse_url(struct nfs_context *nfs, const char *url, int dir, int incomplete)
{
	struct nfs_url *urls;
	char *strp, *flagsp, *strp2, ch;
        int tmp;

	if (strncmp(url, "nfs://", 6)) {
		nfs_set_error(nfs, "Invalid URL specified");
		return NULL;
	}

	urls = malloc(sizeof(struct nfs_url));
	if (urls == NULL) {
		nfs_set_error(nfs, "Out of memory");
		return NULL;
	}

	memset(urls, 0x00, sizeof(struct nfs_url));
	urls->server = strdup(url + 6);
	if (urls->server == NULL) {
		nfs_destroy_url(urls);
		nfs_set_error(nfs, "Out of memory");
		return NULL;
	}

        /* unescape all % hex hex characters */
        strp = urls->server;
        while (strp && *strp) {
                strp = strchr(strp, '%');
                if (strp == NULL) {
                        break;
                }
                tmp = tohex(strp[1]);
                if (tmp < 0) {
                        strp++;
                        continue;
                }
                ch = (tmp & 0x0f) << 4;
                tmp = tohex(strp[2]);
                if (tmp < 0) {
                        strp++;
                        continue;
                }
                ch |= tmp & 0x0f;
                *strp = ch;
                strcpy(strp + 1, strp + 3);
                strp++;
        }
       
	if (urls->server[0] == '/' || urls->server[0] == '\0' ||
		urls->server[0] == '?') {
		if (incomplete) {
			flagsp = strchr(urls->server, '?');
			goto flags;
		}
		nfs_destroy_url(urls);
		nfs_set_error(nfs, "Invalid server string");
		return NULL;
	}

	strp = strchr(urls->server, '/');
	if (strp == NULL) {
		if (incomplete) {
			flagsp = strchr(urls->server, '?');
			goto flags;
		}
		nfs_destroy_url(urls);
		nfs_set_error(nfs, "Incomplete or invalid URL specified.");
		return NULL;
	}

	urls->path = strdup(strp);
	if (urls->path == NULL) {
		nfs_destroy_url(urls);
		nfs_set_error(nfs, "Out of memory");
		return NULL;
	}
	*strp = 0;

	strp = strchr(urls->server, ':');
        if (strp) {
                *strp++ = 0;
		nfs->nfsport =  atoi(strp);
        }

	if (dir) {
		flagsp = strchr(urls->path, '?');
		goto flags;
	}

	strp = strrchr(urls->path, '/');
	if (strp == NULL) {
		if (incomplete) {
			flagsp = strchr(urls->path, '?');
			goto flags;
		}
		nfs_destroy_url(urls);
		nfs_set_error(nfs, "Incomplete or invalid URL specified.");
		return NULL;
	}
	urls->file = strdup(strp);
	if (urls->path == NULL) {
		nfs_destroy_url(urls);
		nfs_set_error(nfs, "Out of memory");
		return NULL;
	}
	*strp = 0;
	flagsp = strchr(urls->file, '?');

flags:
	if (flagsp) {
		*flagsp = 0;
	}

	if (urls->file && !strlen(urls->file)) {
		free(urls->file);
		urls->file = NULL;
		if (!incomplete) {
			nfs_destroy_url(urls);
			nfs_set_error(nfs, "Incomplete or invalid URL "
                                      "specified.");
			return NULL;
		}
	}

	while (flagsp != NULL && *(flagsp+1) != 0) {
		strp = flagsp + 1;
		flagsp = strchr(strp, '&');
		if (flagsp) {
			*flagsp = 0;
		}
		strp2 = strchr(strp, '=');
		if (strp2) {
			*strp2 = 0;
			strp2++;
			nfs_set_context_args(nfs, strp, strp2);
		}
	}

	if (urls->server && strlen(urls->server) <= 1) {
		free(urls->server);
		urls->server = NULL;
	}

	return urls;
}

struct nfs_url *
nfs_parse_url_full(struct nfs_context *nfs, const char *url)
{
	return nfs_parse_url(nfs, url, 0, 0);
}

struct nfs_url *
nfs_parse_url_dir(struct nfs_context *nfs, const char *url)
{
	return nfs_parse_url(nfs, url, 1, 0);
}

struct nfs_url *
nfs_parse_url_incomplete(struct nfs_context *nfs, const char *url)
{
	return nfs_parse_url(nfs, url, 0, 1);
}


void
nfs_destroy_url(struct nfs_url *url)
{
	if (url) {
		free(url->server);
		free(url->path);
		free(url->file);
	}
	free(url);
}

#define MAX_CLIENT_NAME 64

struct nfs_context *
nfs_init_context(void)
{
	struct nfs_context *nfs;
        int i;
        uint64_t v;
        verifier4 verifier;
        char client_name[MAX_CLIENT_NAME];

	nfs = malloc(sizeof(struct nfs_context));
	if (nfs == NULL) {
		return NULL;
	}
	memset(nfs, 0, sizeof(struct nfs_context));

	nfs->rpc = rpc_init_context();
	if (nfs->rpc == NULL) {
		free(nfs);
		return NULL;
	}

	nfs->cwd = strdup("/");
	nfs->mask = 022;
	nfs->auto_traverse_mounts = 1;
	nfs->dircache_enabled = 1;
	/* Default is never give up, never surrender */
	nfs->auto_reconnect = -1;
	nfs->version = NFS_V3;

        /* NFSv4 parameters */
        /* We need a "random" initial verifier */
        v = rpc_current_time() << 32 | getpid();
        for (i = 0; i < NFS4_VERIFIER_SIZE; i++) {
                verifier[i] = v & 0xff;
                v >>= 8;
        }
        nfs4_set_verifier(nfs, verifier);
        
        snprintf(client_name, MAX_CLIENT_NAME, "Libnfs pid:%d %d", getpid(),
                 (int)time(NULL));
        nfs4_set_client_name(nfs, client_name);

	return nfs;
}

void
nfs4_set_client_name(struct nfs_context *nfs, const char *client_name)
{
        nfs->client_name = strdup(client_name);
}

void
nfs4_set_verifier(struct nfs_context *nfs, const char *verifier)
{
        memcpy(nfs->verifier, verifier, NFS4_VERIFIER_SIZE);
}

void
nfs_destroy_context(struct nfs_context *nfs)
{
	while (nfs->nested_mounts) {
		struct nested_mounts *mnt = nfs->nested_mounts;

		LIBNFS_LIST_REMOVE(&nfs->nested_mounts, mnt);
		free(mnt->path);
		free(mnt->fh.val);
                free(mnt);
	}

	rpc_destroy_context(nfs->rpc);
	nfs->rpc = NULL;

        free(nfs->server);
        nfs->server = NULL;

        free(nfs->export);
        nfs->export = NULL;

        free(nfs->cwd);
        nfs->cwd = NULL;

        free(nfs->rootfh.val);
        nfs->rootfh.len = 0;
        nfs->rootfh.val = NULL;

        free(nfs->client_name);
        nfs->client_name = NULL;

	while (nfs->dircache) {
		struct nfsdir *nfsdir = nfs->dircache;
		LIBNFS_LIST_REMOVE(&nfs->dircache, nfsdir);
		nfs_free_nfsdir(nfsdir);
	}

	free(nfs);
}

struct rpc_cb_data {
       char *server;
       uint32_t program;
       uint32_t version;

       rpc_cb cb;
       void *private_data;
};

void free_rpc_cb_data(struct rpc_cb_data *data)
{
	free(data->server);
	data->server = NULL;
	free(data);
}

static int
rpc_connect_port_internal(struct rpc_context *rpc, int port, struct rpc_cb_data *data);

static void
rpc_connect_program_5_cb(struct rpc_context *rpc, int status,
                         void *command_data, void *private_data)
{
	struct rpc_cb_data *data = private_data;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	/* Dont want any more callbacks even if the socket is closed */
	rpc->connect_cb = NULL;

	if (status != RPC_STATUS_SUCCESS) {
		data->cb(rpc, status, command_data, data->private_data);
		free_rpc_cb_data(data);
		return;
	}

	data->cb(rpc, status, NULL, data->private_data);
	free_rpc_cb_data(data);
}

static void
rpc_connect_program_4_cb(struct rpc_context *rpc, int status,
                         void *command_data, void *private_data)
{
	struct rpc_cb_data *data = private_data;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	/* Dont want any more callbacks even if the socket is closed */
	rpc->connect_cb = NULL;

	if (status != RPC_STATUS_SUCCESS) {
		data->cb(rpc, status, command_data, data->private_data);
		free_rpc_cb_data(data);
		return;
	}

        if (rpc_null_async(rpc, data->program, data->version,
                           rpc_connect_program_5_cb, data) != 0) {
                data->cb(rpc, RPC_STATUS_ERROR, command_data, data->private_data);
                free_rpc_cb_data(data);
                return;
        }
}

static void
rpc_connect_program_3_cb(struct rpc_context *rpc, int status,
                         void *command_data, void *private_data)
{
	struct rpc_cb_data *data = private_data;
	struct pmap3_string_result *gar;
	uint32_t rpc_port = 0;
	char *ptr;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	if (status != RPC_STATUS_SUCCESS) {
		data->cb(rpc, status, command_data, data->private_data);
		free_rpc_cb_data(data);
		return;
	}

	switch (rpc->s.ss_family) {
	case AF_INET:
		rpc_port = *(uint32_t *)(void *)command_data;
		break;
	case AF_INET6:
		/* ouch. portmapper and ipv6 are not great */
		gar = command_data;
		if (gar->addr == NULL) {
			break;
		}
		ptr = strrchr(gar->addr, '.');
		if (ptr == NULL) {
			break;
		}
		rpc_port = atoi(ptr + 1);
		*ptr = 0;
		ptr = strrchr(gar->addr, '.');
		if (ptr == NULL) {
			break;
		}
		rpc_port += 256 * atoi(ptr + 1);
		break;
	}
	if (rpc_port == 0) {
		rpc_set_error(rpc, "RPC error. Program is not available on %s",
			      data->server);
		data->cb(rpc, RPC_STATUS_ERROR, rpc_get_error(rpc),
			 data->private_data);
		free_rpc_cb_data(data);
		return;
	}

	rpc_disconnect(rpc, "normal disconnect");
        if (rpc_connect_port_internal(rpc, rpc_port, data)) {
		data->cb(rpc, RPC_STATUS_ERROR, command_data,
                         data->private_data);
		free_rpc_cb_data(data);
                return;
        }
}

static void
rpc_connect_program_2_cb(struct rpc_context *rpc, int status,
                         void *command_data, void *private_data)
{
	struct rpc_cb_data *data = private_data;
	struct pmap3_mapping map;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	if (status != RPC_STATUS_SUCCESS) {
		data->cb(rpc, status, command_data, data->private_data);
		free_rpc_cb_data(data);
		return;
	}

	switch (rpc->s.ss_family) {
	case AF_INET:
		if (rpc_pmap2_getport_async(rpc, data->program, data->version,
                                            IPPROTO_TCP,
                                            rpc_connect_program_3_cb,
                                            private_data) != 0) {
			data->cb(rpc, RPC_STATUS_ERROR, command_data, data->private_data);
			free_rpc_cb_data(data);
			return;
		}
		break;
	case AF_INET6:
		map.prog=data->program;
		map.vers=data->version;
		map.netid="";
		map.addr="";
		map.owner="";
		if (rpc_pmap3_getaddr_async(rpc, &map,
                                            rpc_connect_program_3_cb,
                                            private_data) != 0) {
			data->cb(rpc, RPC_STATUS_ERROR, command_data, data->private_data);
			free_rpc_cb_data(data);
			return;
		}
		break;
	}
}

static void
rpc_connect_program_1_cb(struct rpc_context *rpc, int status,
                         void *command_data, void *private_data)
{
	struct rpc_cb_data *data = private_data;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	/* Dont want any more callbacks even if the socket is closed */
	rpc->connect_cb = NULL;

	if (status != RPC_STATUS_SUCCESS) {
		data->cb(rpc, status, command_data, data->private_data);
		free_rpc_cb_data(data);
		return;
	}

	switch (rpc->s.ss_family) {
	case AF_INET:
		if (rpc_pmap2_null_async(rpc, rpc_connect_program_2_cb,
                                         data) != 0) {
			data->cb(rpc, RPC_STATUS_ERROR, command_data, data->private_data);
			free_rpc_cb_data(data);
			return;
		}
		break;
	case AF_INET6:
		if (rpc_pmap3_null_async(rpc, rpc_connect_program_2_cb,
                                         data) != 0) {
			data->cb(rpc, RPC_STATUS_ERROR, command_data, data->private_data);
			free_rpc_cb_data(data);
			return;
		}
		break;
	}
}

static int
rpc_connect_port_internal(struct rpc_context *rpc, int port, struct rpc_cb_data *data)
{
        if (rpc_connect_async(rpc, data->server, port,
                              rpc_connect_program_4_cb, data) != 0) {
		return -1;
	}

        return 0;
}

int
rpc_connect_port_async(struct rpc_context *rpc, const char *server,
                       int port,
                       int program, int version,
                       rpc_cb cb, void *private_data)
{
	struct rpc_cb_data *data;

	data = malloc(sizeof(struct rpc_cb_data));
	if (data == NULL) {
		return -1;
	}
	memset(data, 0, sizeof(struct rpc_cb_data));
	data->server       = strdup(server);
	data->program      = program;
	data->version      = version;

	data->cb           = cb;
	data->private_data = private_data;

        if (rpc_connect_port_internal(rpc, port, data)) {
		rpc_set_error(rpc, "Failed to start connection. %s",
                              rpc_get_error(rpc));
		free_rpc_cb_data(data);
                return -1;
        }
        return 0;
}

int
rpc_connect_program_async(struct rpc_context *rpc, const char *server,
                          int program, int version,
                          rpc_cb cb, void *private_data)
{
	struct rpc_cb_data *data;

	data = malloc(sizeof(struct rpc_cb_data));
	if (data == NULL) {
		return -1;
	}
	memset(data, 0, sizeof(struct rpc_cb_data));
	data->server       = strdup(server);
	data->program      = program;
	data->version      = version;

	data->cb           = cb;
	data->private_data = private_data;

	if (rpc_connect_async(rpc, server, 111, rpc_connect_program_1_cb,
                              data) != 0) {
		rpc_set_error(rpc, "Failed to start connection. %s",
                              rpc_get_error(rpc));
		free_rpc_cb_data(data);
		return -1;
	}
	return 0;
}

void
free_nfs_cb_data(struct nfs_cb_data *data)
{
	if (data->continue_data != NULL) {
		assert(data->free_continue_data);
		data->free_continue_data(data->continue_data);
	}

	free(data->saved_path);
	free(data->fh.val);
	if (!data->not_my_buffer) {
		free(data->buffer);
	}

	free(data);
}

void
nfs_free_nfsfh(struct nfsfh *nfsfh)
{
	if (nfsfh->fh.val != NULL) {
		free(nfsfh->fh.val);
		nfsfh->fh.len = 0;
		nfsfh->fh.val = NULL;
	}
	free(nfsfh->pagecache.entries);
	free(nfsfh);
}

/*
 * Async call for mounting an nfs share and geting the root filehandle
 */
int
nfs_mount_async(struct nfs_context *nfs, const char *server,
                const char *export, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_mount_async(nfs, server, export, cb, private_data);
        case NFS_V4:
                return nfs4_mount_async(nfs, server, export, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

/*
 * Async call for umounting an nfs share
 */
int
nfs_umount_async(struct nfs_context *nfs, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_umount_async(nfs, cb, private_data);
        case NFS_V4:
                /* umount is a no-op in v4 */
                (*cb)(0, nfs, NULL, private_data);
                return 0;
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_normalize_path(struct nfs_context *nfs, char *path)
{
	char *str;
	size_t len;

	/* // -> / */
	while ((str = strstr(path, "//"))) {
		while(*str) {
			*str = *(str + 1);
			str++;
		}
	}

	/* /./ -> / */
	while ((str = strstr(path, "/./"))) {
		while(*(str + 1)) {
			*str = *(str + 2);
			str++;
		}
	}

	/* ^/../ -> error */
	if (!strncmp(path, "/../", 4)) {
		nfs_set_error(nfs,
			"Absolute path starts with '/../' "
			"during normalization");
		return -1;
	}

	/* ^[^/] -> error */
	if (path[0] != '/') {
		nfs_set_error(nfs,
			"Absolute path does not start with '/'");
		return -1;
	}

	/* /string/../ -> / */
	while ((str = strstr(path, "/../"))) {
		char *tmp;

		if (!strncmp(path, "/../", 4)) {
			nfs_set_error(nfs,
				"Absolute path starts with '/../' "
				"during normalization");
			return -1;
		}

		tmp = str - 1;
		while (*tmp != '/') {
			tmp--;
		}
		str += 3;
		while((*(tmp++) = *(str++)) != '\0')
			;
	}

	/* /$ -> \0 */
	len = strlen(path);
	if (len > 1) {
		if (path[len - 1] == '/') {
			path[len - 1] = '\0';
			len--;
		}
	}
	if (path[0] == '\0') {
		nfs_set_error(nfs,
			"Absolute path became '' "
			"during normalization");
		return -1;
	}

	/* /.$ -> \0 */
	if (len >= 2) {
		if (!strcmp(&path[len - 2], "/.")) {
			path[len - 2] = '\0';
			len -= 2;
		}
	}

	/* ^/..$ -> error */
	if (!strcmp(path, "/..")) {
		nfs_set_error(nfs,
			"Absolute path is '/..' "
			"during normalization");
		return -1;
	}

	/* /string/..$ -> / */
	if (len >= 3) {
		if (!strcmp(&path[len - 3], "/..")) {
			char *tmp = &path[len - 3];
			while (*--tmp != '/')
				;
			*tmp = '\0';
		}
	}

	return 0;
}

int
nfs_stat_async(struct nfs_context *nfs, const char *path,
               nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_stat_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv4",
                              __FUNCTION__);
                return -1;
        }
}

int
nfs_stat64_async(struct nfs_context *nfs, const char *path,
                 nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_stat64_async(nfs, path, 0,
                                         cb, private_data);
        case NFS_V4:
                return nfs4_stat64_async(nfs, path, 0,
                                         cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_lstat64_async(struct nfs_context *nfs, const char *path,
                  nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_stat64_async(nfs, path, 1,
                                         cb, private_data);
        case NFS_V4:
                return nfs4_stat64_async(nfs, path, 1,
                                         cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_open2_async(struct nfs_context *nfs, const char *path, int flags,
                int mode, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_open_async(nfs, path, flags, mode,
                                       cb, private_data);
        case NFS_V4:
                return nfs4_open_async(nfs, path, flags, mode,
                                       cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_open_async(struct nfs_context *nfs, const char *path, int flags,
               nfs_cb cb, void *private_data)
{
        return nfs_open2_async(nfs, path, flags, 0666 & ~nfs->mask,
                               cb, private_data);
}

int
nfs_chdir_async(struct nfs_context *nfs, const char *path,
                nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_chdir_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_chdir_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_pread_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset,
                uint64_t count, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_pread_async_internal(nfs, nfsfh, offset,
                                                 (size_t)count,
                                                 cb, private_data, 0);
        case NFS_V4:
                return nfs4_pread_async_internal(nfs, nfsfh, offset,
                                                 (size_t)count,
                                                 cb, private_data, 0);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_read_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t count,
               nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_pread_async_internal(nfs, nfsfh, nfsfh->offset,
                                                 (size_t)count,
                                                 cb, private_data, 1);
        case NFS_V4:
                return nfs4_pread_async_internal(nfs, nfsfh, nfsfh->offset,
                                                 (size_t)count,
                                                 cb, private_data, 1);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_pwrite_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset,
                 uint64_t count, const void *buf, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_pwrite_async_internal(nfs, nfsfh, offset,
                                                  (size_t)count, buf,
                                                  cb, private_data, 0);
        case NFS_V4:
                return nfs4_pwrite_async_internal(nfs, nfsfh, offset,
                                                  (size_t)count, buf,
                                                  cb, private_data, 0);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d.",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_write_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t count,
                const void *buf, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_write_async(nfs, nfsfh, count, buf,
                                        cb, private_data);
        case NFS_V4:
                return nfs4_write_async(nfs, nfsfh, count, buf,
                                        cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_close_async(struct nfs_context *nfs, struct nfsfh *nfsfh, nfs_cb cb,
                void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_close_async(nfs, nfsfh, cb, private_data);
        case NFS_V4:
                return nfs4_close_async(nfs, nfsfh, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_fstat_async(struct nfs_context *nfs, struct nfsfh *nfsfh, nfs_cb cb,
                 void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_fstat_async(nfs, nfsfh, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv4",
                              __FUNCTION__);
                return -1;
        }
}

int
nfs_fstat64_async(struct nfs_context *nfs, struct nfsfh *nfsfh, nfs_cb cb,
                   void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_fstat64_async(nfs, nfsfh, cb, private_data);
        case NFS_V4:
                return nfs4_fstat64_async(nfs, nfsfh, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_fsync_async(struct nfs_context *nfs, struct nfsfh *nfsfh, nfs_cb cb,
                 void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_fsync_async(nfs, nfsfh, cb, private_data);
        case NFS_V4:
                return nfs4_fsync_async(nfs, nfsfh, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_ftruncate_async(struct nfs_context *nfs, struct nfsfh *nfsfh,
                    uint64_t length, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_ftruncate_async(nfs, nfsfh, length,
                                            cb, private_data);
        case NFS_V4:
                return nfs4_ftruncate_async(nfs, nfsfh, length,
                                            cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_truncate_async(struct nfs_context *nfs, const char *path, uint64_t length,
                   nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_truncate_async(nfs, path, length, cb, private_data);
        case NFS_V4:
                return nfs4_truncate_async(nfs, path, length, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_mkdir2_async(struct nfs_context *nfs, const char *path, int mode,
                 nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_mkdir2_async(nfs, path, mode, cb, private_data);
        case NFS_V4:
                return nfs4_mkdir2_async(nfs, path, mode, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_mkdir_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                void *private_data)
{
	return nfs_mkdir2_async(nfs, path, 0755, cb, private_data);
}

int
nfs_rmdir_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                 void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_rmdir_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_rmdir_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_create_async(struct nfs_context *nfs, const char *path, int flags,
                  int mode, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_create_async(nfs, path, flags, mode,
                                         cb, private_data);
        case NFS_V4:
                return nfs4_create_async(nfs, path, flags, mode,
                                         cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_creat_async(struct nfs_context *nfs, const char *path, int mode, nfs_cb cb,
                void *private_data)
{
	return nfs_create_async(nfs, path, 0, mode, cb, private_data);
}

int
nfs_unlink_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                  void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_unlink_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_unlink_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_mknod_async(struct nfs_context *nfs, const char *path, int mode, int dev,
                 nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_mknod_async(nfs, path, mode, dev, cb, private_data);
        case NFS_V4:
                return nfs4_mknod_async(nfs, path, mode, dev, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_opendir_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                   void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_opendir_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_opendir_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv4",
                              __FUNCTION__);
                return -1;
        }
}

struct nfsdirent *
nfs_readdir(struct nfs_context *nfs _U_, struct nfsdir *nfsdir)
{
	struct nfsdirent *nfsdirent = nfsdir->current;

	if (nfsdir->current != NULL) {
		nfsdir->current = nfsdir->current->next;
	}
	return nfsdirent;
}

long
nfs_telldir(struct nfs_context *nfs _U_, struct nfsdir *nfsdir)
{
        long i;
        struct nfsdirent *tmp;

        for (i = 0, tmp = nfsdir->entries; tmp; i++, tmp = tmp->next) {
                if (tmp == nfsdir->current) {
                        return i;
                }
        }
        return -1;
}

void
nfs_seekdir(struct nfs_context *nfs _U_, struct nfsdir *nfsdir, long loc)
{
        if (loc < 0) {
                return;
        }
        for (nfsdir->current = nfsdir->entries;
             nfsdir->current && loc--;
             nfsdir->current = nfsdir->current->next) {
        }
}

void
nfs_rewinddir(struct nfs_context *nfs _U_, struct nfsdir *nfsdir)
{
	nfsdir->current = nfsdir->entries;
}

void
nfs_closedir(struct nfs_context *nfs, struct nfsdir *nfsdir)
{
	if (nfs && nfs->dircache_enabled) {
		nfs_dircache_add(nfs, nfsdir);
	} else {
		nfs_free_nfsdir(nfsdir);
	}
}

void
nfs_getcwd(struct nfs_context *nfs, const char **cwd)
{
	if (cwd) {
		*cwd = nfs->cwd;
	}
}

int
nfs_lseek_async(struct nfs_context *nfs, struct nfsfh *nfsfh, int64_t offset,
                 int whence, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_lseek_async(nfs, nfsfh, offset, whence,
                                        cb, private_data);
        case NFS_V4:
                return nfs4_lseek_async(nfs, nfsfh, offset, whence,
                                        cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_lockf_async(struct nfs_context *nfs, struct nfsfh *nfsfh,
                enum nfs4_lock_op op, uint64_t count,
                nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V4:
                return nfs4_lockf_async(nfs, nfsfh, op, count,
                                        cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_fcntl_async(struct nfs_context *nfs, struct nfsfh *nfsfh,
                enum nfs4_fcntl_op cmd, void *arg,
                nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V4:
                return nfs4_fcntl_async(nfs, nfsfh, cmd, arg,
                                        cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_statvfs_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                   void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_statvfs_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_statvfs_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_statvfs64_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                    void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_statvfs64_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_statvfs64_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_readlink_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                    void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_readlink_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_readlink_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_chmod_async(struct nfs_context *nfs, const char *path, int mode,
                nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_chmod_async_internal(nfs, path, 0, mode,
                                                 cb, private_data);
        case NFS_V4:
                return nfs4_chmod_async_internal(nfs, path, 0, mode,
                                                 cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_lchmod_async(struct nfs_context *nfs, const char *path, int mode,
                 nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_chmod_async_internal(nfs, path, 1, mode,
                                                 cb, private_data);
        case NFS_V4:
                return nfs4_chmod_async_internal(nfs, path, 1, mode,
                                                 cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_fchmod_async(struct nfs_context *nfs, struct nfsfh *nfsfh, int mode,
                  nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_fchmod_async(nfs, nfsfh, mode, cb, private_data);
        case NFS_V4:
                return nfs4_fchmod_async(nfs, nfsfh, mode, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_chown_async(struct nfs_context *nfs, const char *path, int uid, int gid,
                nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_chown_async_internal(nfs, path, 0, uid, gid,
                                                 cb, private_data);
        case NFS_V4:
                return nfs4_chown_async_internal(nfs, path, 0, uid, gid,
                                                 cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_lchown_async(struct nfs_context *nfs, const char *path, int uid, int gid,
                 nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_chown_async_internal(nfs, path, 1, uid, gid,
                                                 cb, private_data);
        case NFS_V4:
                return nfs4_chown_async_internal(nfs, path, 1, uid, gid,
                                                 cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_fchown_async(struct nfs_context *nfs, struct nfsfh *nfsfh, int uid,
                 int gid, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_fchown_async(nfs, nfsfh, uid, gid,
                                         cb, private_data);
        case NFS_V4:
                return nfs4_fchown_async(nfs, nfsfh, uid, gid,
                                         cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_utimes_async(struct nfs_context *nfs, const char *path,
                 struct timeval *times, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_utimes_async_internal(nfs, path, 0, times,
                                                  cb, private_data);
        case NFS_V4:
                return nfs4_utimes_async_internal(nfs, path, 0, times,
                                                  cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_lutimes_async(struct nfs_context *nfs, const char *path,
                  struct timeval *times, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_utimes_async_internal(nfs, path, 1, times,
                                                  cb, private_data);
        case NFS_V4:
                return nfs4_utimes_async_internal(nfs, path, 1, times,
                                                  cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_utime_async(struct nfs_context *nfs, const char *path,
                struct utimbuf *times, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_utime_async(nfs, path, times, cb, private_data);
        case NFS_V4:
                return nfs4_utime_async(nfs, path, times, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv4",
                              __FUNCTION__);
                return -1;
        }
}

int
nfs_access_async(struct nfs_context *nfs, const char *path, int mode,
                 nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_access_async(nfs, path, mode, cb, private_data);
        case NFS_V4:
                return nfs4_access_async(nfs, path, mode, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_access2_async(struct nfs_context *nfs, const char *path, nfs_cb cb,
                   void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_access2_async(nfs, path, cb, private_data);
        case NFS_V4:
                return nfs4_access2_async(nfs, path, cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv4",
                              __FUNCTION__);
                return -1;
        }
}

int
nfs_symlink_async(struct nfs_context *nfs, const char *target,
                   const char *newpath, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_symlink_async(nfs, target, newpath,
                                          cb, private_data);
        case NFS_V4:
                return nfs4_symlink_async(nfs, target, newpath,
                                          cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_rename_async(struct nfs_context *nfs, const char *oldpath,
                  const char *newpath, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_rename_async(nfs, oldpath, newpath,
                                         cb, private_data);
        case NFS_V4:
                return nfs4_rename_async(nfs, oldpath, newpath,
                                         cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

int
nfs_link_async(struct nfs_context *nfs, const char *oldpath,
               const char *newpath, nfs_cb cb, void *private_data)
{
	switch (nfs->version) {
        case NFS_V3:
                return nfs3_link_async(nfs, oldpath, newpath,
                                       cb, private_data);
        case NFS_V4:
                return nfs4_link_async(nfs, oldpath, newpath,
                                       cb, private_data);
        default:
                nfs_set_error(nfs, "%s does not support NFSv%d",
                              __FUNCTION__, nfs->version);
                return -1;
        }
}

/*
 * Get/Set the maximum supported READ size by the server
 */
uint64_t
nfs_get_readmax(struct nfs_context *nfs)
{
	return nfs->readmax;
}
void
nfs_set_readmax(struct nfs_context *nfs, uint64_t readmax)
{
	nfs->readmax = readmax;
}

/*
 * Get/Set the maximum supported WRITE size by the server
 */
uint64_t
nfs_get_writemax(struct nfs_context *nfs)
{
	return nfs->writemax;
}
void
nfs_set_writemax(struct nfs_context *nfs, uint64_t writemax)
{
	nfs->writemax = writemax;
}

void
nfs_set_tcp_syncnt(struct nfs_context *nfs, int v) {
	rpc_set_tcp_syncnt(nfs->rpc, v);
}

void
nfs_set_uid(struct nfs_context *nfs, int uid) {
	rpc_set_uid(nfs->rpc, uid);
}

void
nfs_set_gid(struct nfs_context *nfs, int gid) {
	rpc_set_gid(nfs->rpc, gid);
}

void
nfs_set_pagecache(struct nfs_context *nfs, uint32_t v) {
	rpc_set_pagecache(nfs->rpc, v);
}

void
nfs_set_pagecache_ttl(struct nfs_context *nfs, uint32_t v) {
	rpc_set_pagecache_ttl(nfs->rpc, v);
}

void
nfs_set_readahead(struct nfs_context *nfs, uint32_t v) {
	rpc_set_readahead(nfs->rpc, v);
}

void
nfs_set_debug(struct nfs_context *nfs, int level) {
	rpc_set_debug(nfs->rpc, level);
}

void
nfs_set_dircache(struct nfs_context *nfs, int enabled) {
	nfs->dircache_enabled = enabled;
}

void
nfs_set_autoreconnect(struct nfs_context *nfs, int num_retries) {
	nfs->auto_reconnect = num_retries;
}

int
nfs_set_version(struct nfs_context *nfs, int version) {
	switch (version) {
	case NFS_V3:
	case NFS_V4:
		nfs->version = version;
		break;
	default:
		nfs_set_error(nfs, "NFS version %d is not supported", version);
		return -1;
	}
	return 0;
}

int
nfs_get_version(struct nfs_context *nfs) {
        return nfs->version;
}

void
nfs_set_error(struct nfs_context *nfs, char *error_string, ...)
{
        va_list ap;
	char *str = NULL;

        va_start(ap, error_string);
	str = malloc(1024);
	vsnprintf(str, 1024, error_string, ap);
	if (nfs->rpc->error_string != NULL) {
		free(nfs->rpc->error_string);
	}
	nfs->rpc->error_string = str;
	va_end(ap);
}

struct mount_cb_data {
       rpc_cb cb;
       void *private_data;
       char *server;
};

static void
free_mount_cb_data(struct mount_cb_data *data)
{
	if (data->server != NULL) {
		free(data->server);
		data->server = NULL;
	}

	free(data);
}

static void
mount_export_5_cb(struct rpc_context *rpc, int status, void *command_data,
                  void *private_data)
{
	struct mount_cb_data *data = private_data;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	if (status != RPC_STATUS_SUCCESS) {
		data->cb(rpc, -EFAULT, command_data, data->private_data);
		free_mount_cb_data(data);
		return;
	}

	data->cb(rpc, 0, command_data, data->private_data);
	if (rpc_disconnect(rpc, "normal disconnect") != 0) {
		rpc_set_error(rpc, "Failed to disconnect\n");
	}
	free_mount_cb_data(data);
}

static void
mount_export_4_cb(struct rpc_context *rpc, int status, void *command_data,
                  void *private_data)
{
	struct mount_cb_data *data = private_data;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	/* Dont want any more callbacks even if the socket is closed */
	rpc->connect_cb = NULL;

	if (status != RPC_STATUS_SUCCESS) {
		data->cb(rpc, -EFAULT, command_data, data->private_data);
		free_mount_cb_data(data);
		return;
	}

	if (rpc_mount3_export_async(rpc, mount_export_5_cb, data) != 0) {
		data->cb(rpc, -ENOMEM, command_data, data->private_data);
		free_mount_cb_data(data);
		return;
	}
}

int
mount_getexports_async(struct rpc_context *rpc, const char *server, rpc_cb cb,
                       void *private_data)
{
	struct mount_cb_data *data;

	assert(rpc->magic == RPC_CONTEXT_MAGIC);

	data = malloc(sizeof(struct mount_cb_data));
	if (data == NULL) {
		return -1;
	}
	memset(data, 0, sizeof(struct mount_cb_data));
	data->cb           = cb;
	data->private_data = private_data;
	data->server       = strdup(server);
	if (data->server == NULL) {
		free_mount_cb_data(data);
		return -1;
	}
	if (rpc_connect_program_async(rpc, data->server, MOUNT_PROGRAM,
                                      MOUNT_V3, mount_export_4_cb, data) != 0) {
		rpc_set_error(rpc, "Failed to start connection. %s",
                              rpc_get_error(rpc));
		free_mount_cb_data(data);
		return -1;
	}

	return 0;
}

struct rpc_context *
nfs_get_rpc_context(struct nfs_context *nfs)
{
	assert(nfs->rpc->magic == RPC_CONTEXT_MAGIC);
	return nfs->rpc;
}

const char *
nfs_get_server(struct nfs_context *nfs) {
	return nfs->server;
}

const char *
nfs_get_export(struct nfs_context *nfs) {
	return nfs->export;
}

const struct nfs_fh *
nfs_get_rootfh(struct nfs_context *nfs) {
      return &nfs->rootfh;
}

struct nfs_fh *
nfs_get_fh(struct nfsfh *nfsfh) {
       return &nfsfh->fh;
}

uint16_t
nfs_umask(struct nfs_context *nfs, uint16_t mask) {
	 uint16_t tmp = nfs->mask;
	 nfs->mask = mask;
	 return tmp;
}

/*
* Sets timeout for nfs apis
*/
void
nfs_set_timeout(struct nfs_context *nfs,int timeout)
{
	 rpc_set_timeout(nfs->rpc,timeout);
}

/*
* Gets timeout for nfs apis
*/
int
nfs_get_timeout(struct nfs_context *nfs)
{
	return rpc_get_timeout(nfs->rpc);
}

int
rpc_null_async(struct rpc_context *rpc, int program, int version, rpc_cb cb,
               void *private_data)
{
	struct rpc_pdu *pdu;

	pdu = rpc_allocate_pdu(rpc, program, version, 0, cb, private_data,
                               (zdrproc_t)zdr_void, 0);
	if (pdu == NULL) {
		rpc_set_error(rpc, "Out of memory. Failed to allocate pdu "
                              "for NULL call");
		return -1;
	}

	if (rpc_queue_pdu(rpc, pdu) != 0) {
		rpc_set_error(rpc, "Out of memory. Failed to queue pdu "
                              "for NULL call");
		return -1;
	}

	return 0;
}
