
/*
 * UNFS3 NFS protocol procedures
 * (C) 2004, Pascal Schmidt
 * Copyright 2014 Karl Mikaelsson <derfian@cendio.se> for Cendio AB
 * see file LICENSE for license details
 */

#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>		       /* needed for statfs() on NetBSD */
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>		       /* dito */
#endif
#ifdef HAVE_SYS_VMOUNT_H
#include <sys/vmount.h>		       /* AIX */
#endif
#include <rpc/rpc.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utime.h>
#ifndef WIN32
#include <sys/socket.h>
#include <sys/un.h>
#endif				       /* WIN32 */

#if HAVE_STATVFS == 1
# include <sys/statvfs.h>
#else
# define statvfs statfs
#endif

#include "nfs.h"
#include "mount.h"
#include "fh.h"
#include "fh_cache.h"
#include "attr.h"
#include "readdir.h"
#include "user.h"
#include "error.h"
#include "fd_cache.h"
#include "daemon.h"
#include "backend.h"
#include "Config/exports.h"
#include "Extras/cluster.h"

/*
 * decompose filehandle and switch user if permitted access
 * otherwise zero result structure and return with error status
 */
#define PREP(p,f) do {						\
                      unfs3_fh_t fh = fh_decode(&f); \
                      switch_to_root();				\
                      p = fh_decomp(f);				\
                      if (exports_options(p, rqstp, NULL, NULL) == -1) { \
                          memset(&result, 0, sizeof(result));	\
                          if (p)				\
                              result.status = NFS3ERR_ACCES;	\
                          else					\
                              result.status = NFS3ERR_STALE;	\
                          return &result;			\
                      }						\
                      if (fh.pwhash != export_password_hash) { \
                          memset(&result, 0, sizeof(result));	\
                          result.status = NFS3ERR_STALE;        \
                          return &result;                       \
                      }                                         \
                      switch_user(rqstp);			\
                  } while (0)

/*
 * cat an object name onto a path, checking for illegal input
 */
nfsstat3 cat_name(const char *path, const char *name, char *result)
{
    char *last;

    if (!path)
	return NFS3ERR_STALE;

    if (!name)
	return NFS3ERR_ACCES;

    if (name[0] == 0 || strchr(name, '/') != NULL)
	return NFS3ERR_ACCES;

    if (strlen(path) + strlen(name) + 2 > NFS_MAXPATHLEN)
	return NFS3ERR_NAMETOOLONG;

    if (strcmp(name, ".") == 0) {
	strcpy(result, path);
	return NFS3_OK;
    }

    /* 
     * Irix clients do lookups for .. and then use the
     * resulting filehandle for more lookups, causing them
     * to get filehandles that fh_decomp_raw will refuse to
     * resolve. Export list handling will also get very
     * confused if we allow such filehandles.
     */
    if (strcmp(name, "..") == 0) {
	last = strrchr(path, '/');
	if (!last || last == path)
	    strcpy(result, "/");
	else {
	    *last = 0;
	    strcpy(result, path);
	    *last = '/';
	}
	return NFS3_OK;
    }

    sprintf(result, "%s/%s", path, name);
    return NFS3_OK;
}

void *nfsproc3_null_3_svc(U(void *argp), U(struct svc_req *rqstp))
{
    static void *result = NULL;

    return &result;
}

GETATTR3res *nfsproc3_getattr_3_svc(GETATTR3args * argp,
				    struct svc_req * rqstp)
{
    static GETATTR3res result;
    char *path;
    post_op_attr post;

    PREP(path, argp->object);
    post = get_post_cached(rqstp);

    result.status = NFS3_OK;
    result.GETATTR3res_u.resok.obj_attributes =
	post.post_op_attr_u.attributes;

    return &result;
}

/*
 * check ctime guard for SETATTR procedure
 */
static nfsstat3 in_sync(sattrguard3 guard, pre_op_attr pre)
{
    if (!pre.attributes_follow)
	return NFS3ERR_STALE;

    if (!guard.check)
	return NFS3_OK;

    if (guard.sattrguard3_u.obj_ctime.seconds !=
	pre.pre_op_attr_u.attributes.ctime.seconds)
	return NFS3ERR_NOT_SYNC;

    return NFS3_OK;
}

SETATTR3res *nfsproc3_setattr_3_svc(SETATTR3args * argp,
				    struct svc_req * rqstp)
{
    static SETATTR3res result;
    pre_op_attr pre;
    char *path;

    PREP(path, argp->object);
    pre = get_pre_cached();
    result.status = join(in_sync(argp->guard, pre), exports_rw());

    if (result.status == NFS3_OK)
	result.status = set_attr(path, argp->object, argp->new_attributes);

    /* overlaps with resfail */
    result.SETATTR3res_u.resok.obj_wcc.before = pre;
    result.SETATTR3res_u.resok.obj_wcc.after = get_post_stat(path, rqstp);

    return &result;
}

LOOKUP3res *nfsproc3_lookup_3_svc(LOOKUP3args * argp, struct svc_req * rqstp)
{
    static LOOKUP3res result;
    unfs3_fh_t *fh;
    static char fhbuf[FH_MAXBUF];
    char *path;
    char obj[NFS_MAXPATHLEN];
    backend_statstruct buf;
    int res;
    uint32 gen;

    PREP(path, argp->what.dir);
    result.status = cat_name(path, argp->what.name, obj);

    cluster_lookup(obj, rqstp, &result.status);

    if (result.status == NFS3_OK) {
	res = backend_lstat(obj, &buf);
	if (res == -1)
	    result.status = lookup_err();
	else {
	    if (strcmp(argp->what.name, ".") == 0 ||
		strcmp(argp->what.name, "..") == 0) {
		fh = fh_comp_ptr(obj, rqstp, 0);
	    } else {
		gen = backend_get_gen(buf, FD_NONE, obj);
		fh = fh_extend(argp->what.dir, buf.st_dev, buf.st_ino, gen);
		fh_cache_add(buf.st_dev, buf.st_ino, obj);
	    }

	    if (fh) {
		result.LOOKUP3res_u.resok.object = fh_encode(fh, fhbuf);
		result.LOOKUP3res_u.resok.obj_attributes =
		    get_post_buf(buf, rqstp);
	    } else {
		/* path was too long */
		result.status = NFS3ERR_NAMETOOLONG;
	    }
	}
    }

    /* overlaps with resfail */
    result.LOOKUP3res_u.resok.dir_attributes = get_post_stat(path, rqstp);

    return &result;
}

ACCESS3res *nfsproc3_access_3_svc(ACCESS3args * argp, struct svc_req * rqstp)
{
    static ACCESS3res result;
    char *path;
    post_op_attr post;
    mode_t mode;
    int newaccess = 0;

    PREP(path, argp->object);
    post = get_post_cached(rqstp);
    mode = post.post_op_attr_u.attributes.mode;

    if (access(path, R_OK) != -1)
        newaccess |= ACCESS3_READ;

    if (access(path, W_OK) != -1)
        newaccess |= ACCESS3_MODIFY | ACCESS3_EXTEND;

    if (access(path, X_OK) != -1) {
        newaccess |= ACCESS3_EXECUTE;
        if (opt_readable_executables)
            newaccess |= ACCESS3_READ;
    }

    /* root is allowed everything */
    if (get_uid(rqstp) == 0)
	newaccess |= ACCESS3_READ | ACCESS3_MODIFY | ACCESS3_EXTEND;

    /* adjust if directory */
    if (post.post_op_attr_u.attributes.type == NF3DIR) {
	if (newaccess & (ACCESS3_READ | ACCESS3_EXECUTE))
	    newaccess |= ACCESS3_LOOKUP;
	if (newaccess & ACCESS3_MODIFY)
	    newaccess |= ACCESS3_DELETE;
	newaccess &= ~ACCESS3_EXECUTE;
    }

    result.status = NFS3_OK;
    result.ACCESS3res_u.resok.access = newaccess & argp->access;
    result.ACCESS3res_u.resok.obj_attributes = post;

    return &result;
}

READLINK3res *nfsproc3_readlink_3_svc(READLINK3args * argp,
				      struct svc_req * rqstp)
{
    static READLINK3res result;
    char *path;
    static char buf[NFS_MAXPATHLEN];
    int res;

    PREP(path, argp->symlink);

    res = backend_readlink(path, buf, NFS_MAXPATHLEN - 1);
    if (res == -1)
	result.status = readlink_err();
    else {
	/* readlink does not NULL-terminate */
	buf[res] = 0;

	result.status = NFS3_OK;
	result.READLINK3res_u.resok.data = buf;
    }

    /* overlaps with resfail */
    result.READLINK3res_u.resok.symlink_attributes =
	get_post_stat(path, rqstp);

    return &result;
}

READ3res *nfsproc3_read_3_svc(READ3args * argp, struct svc_req * rqstp)
{
    static READ3res result;
    char *path;
    int fd, res;
    static char buf[NFS_MAXDATA_TCP + 1];
    unsigned int maxdata;

    if (get_socket_type(rqstp) == SOCK_STREAM)
	maxdata = NFS_MAXDATA_TCP;
    else
	maxdata = NFS_MAXDATA_UDP;

    PREP(path, argp->file);
    result.status = is_reg();

    /* handle reading of executables */
    read_executable(rqstp, st_cache);

    /* handle read of owned files */
    read_by_owner(rqstp, st_cache);

    /* if bigger than rtmax, truncate length */
    if (argp->count > maxdata)
	argp->count = maxdata;

    if (result.status == NFS3_OK) {
	fd = fd_open(path, argp->file, UNFS3_FD_READ, TRUE);
	if (fd != -1) {
	    /* read one more to check for eof */
	    res = backend_pread(fd, buf, argp->count + 1, (off_t)argp->offset);

	    /* eof if we could not read one more */
	    result.READ3res_u.resok.eof = (res <= (int64) argp->count);

	    /* close for real when hitting eof */
	    if (result.READ3res_u.resok.eof)
		fd_close(fd, UNFS3_FD_READ, FD_CLOSE_REAL);
	    else {
		fd_close(fd, UNFS3_FD_READ, FD_CLOSE_VIRT);
		res--;
	    }

	    if (res >= 0) {
		result.READ3res_u.resok.count = res;
		result.READ3res_u.resok.data.data_len = res;
		result.READ3res_u.resok.data.data_val = buf;
	    } else {
		/* error during read() */

		/* EINVAL means unreadable object */
		if (errno == EINVAL)
		    result.status = NFS3ERR_INVAL;
		else
		    result.status = NFS3ERR_IO;
	    }
	} else
	    /* opening for read failed */
	    result.status = read_err();
    }

    /* overlaps with resfail */
    result.READ3res_u.resok.file_attributes = get_post_stat(path, rqstp);

    return &result;
}

WRITE3res *nfsproc3_write_3_svc(WRITE3args * argp, struct svc_req * rqstp)
{
    static WRITE3res result;
    char *path;
    int fd, res, res_close;

    PREP(path, argp->file);
    result.status = join(is_reg(), exports_rw());

    /* handle write of owned files */
    write_by_owner(rqstp, st_cache);

    if (result.status == NFS3_OK) {
	/* We allow caching of the fd only for unstable writes. This is to
	   prevent generating a new write verifier for failed stable writes,
	   when the fd was not in the cache. Besides, for stable writes, the
	   fd will be removed from the cache by fd_close() below, so adding
	   it to and removing it from the cache is just a waste of CPU cycles 
	 */
	fd = fd_open(path, argp->file, UNFS3_FD_WRITE,
		     (argp->stable == UNSTABLE));
	if (fd != -1) {
	    res =
		backend_pwrite(fd, argp->data.data_val, argp->data.data_len,
			       (off_t)argp->offset);

	    /* close for real if not UNSTABLE write */
	    if (argp->stable == UNSTABLE)
		res_close = fd_close(fd, UNFS3_FD_WRITE, FD_CLOSE_VIRT);
	    else
		res_close = fd_close(fd, UNFS3_FD_WRITE, FD_CLOSE_REAL);

	    /* we always do fsync(), never fdatasync() */
	    if (argp->stable == DATA_SYNC)
		argp->stable = FILE_SYNC;

	    if (res != -1 && res_close != -1) {
		result.WRITE3res_u.resok.count = res;
		result.WRITE3res_u.resok.committed = argp->stable;
		memcpy(result.WRITE3res_u.resok.verf, wverf,
		       NFS3_WRITEVERFSIZE);
	    } else {
		/* error during write or close */
		result.status = write_write_err();
	    }
	} else
	    /* could not open for writing */
	    result.status = write_open_err();
    }

    /* overlaps with resfail */
    result.WRITE3res_u.resok.file_wcc.before = get_pre_cached();
    result.WRITE3res_u.resok.file_wcc.after = get_post_stat(path, rqstp);

    return &result;
}

#ifndef WIN32

/*
 * store verifier in atime and mtime 
 */
static int store_create_verifier(char *obj, createverf3 verf)
{
    struct utimbuf ubuf;

    ubuf.actime = verf[0] | verf[1] << 8 | verf[2] << 16 | verf[3] << 24;
    ubuf.modtime = verf[4] | verf[5] << 8 | verf[6] << 16 | verf[7] << 24;

    return backend_utime(obj, &ubuf);
}

/*
 * check if a create verifier matches
 */
static int check_create_verifier(backend_statstruct * buf, createverf3 verf)
{
    return ((buf->st_atime ==
	     (verf[0] | verf[1] << 8 | verf[2] << 16 | verf[3] << 24))
	    && (buf->st_mtime ==
		(verf[4] | verf[5] << 8 | verf[6] << 16 | verf[7] << 24)));
}
#endif				       /* WIN32 */

CREATE3res *nfsproc3_create_3_svc(CREATE3args * argp, struct svc_req * rqstp)
{
    static CREATE3res result;
    char *path;
    char obj[NFS_MAXPATHLEN];
    sattr3 new_attr;
    int fd = -1, res = -1;
    backend_statstruct buf;
    uint32 gen;
    int flags = O_RDWR | O_CREAT | O_TRUNC | O_NONBLOCK;

    PREP(path, argp->where.dir);
    result.status = join(cat_name(path, argp->where.name, obj), exports_rw());

    cluster_create(obj, rqstp, &result.status);

    /* GUARDED and EXCLUSIVE maps to Unix exclusive create */
    if (argp->how.mode != UNCHECKED)
	flags = flags | O_EXCL;

    if (argp->how.mode != EXCLUSIVE) {
	new_attr = argp->how.createhow3_u.obj_attributes;
	result.status = join(result.status, atomic_attr(new_attr));
    }

    /* Try to open the file */
    if (result.status == NFS3_OK) {
	if (argp->how.mode != EXCLUSIVE) {
	    fd = backend_open_create(obj, flags, create_mode(new_attr));
	} else {
	    fd = backend_open_create(obj, flags, create_mode(new_attr));
	}
    }

    if (fd != -1) {
	/* Successful open */
	res = backend_fstat(fd, &buf);
	if (res != -1) {
	    /* Successful stat */
	    if (argp->how.mode == EXCLUSIVE) {
		/* Save verifier in atime and mtime */
		res =
		    backend_store_create_verifier(obj,
						  argp->how.createhow3_u.
						  verf);
	    }
	}

	if (res != -1) {
	    /* So far, so good */
	    gen = backend_get_gen(buf, fd, obj);
	    fh_cache_add(buf.st_dev, buf.st_ino, obj);
	    backend_close(fd);

	    result.CREATE3res_u.resok.obj =
		fh_extend_post(argp->where.dir, buf.st_dev, buf.st_ino, gen);
	    result.CREATE3res_u.resok.obj_attributes =
		get_post_buf(buf, rqstp);
	}

	if (res == -1) {
	    /* backend_fstat() or backend_store_create_verifier() failed */
	    backend_close(fd);
	    result.status = NFS3ERR_IO;
	}

    } else if (result.status == NFS3_OK) {
	/* open() failed */
	if (argp->how.mode == EXCLUSIVE && errno == EEXIST) {
	    /* Check if verifier matches */
	    fd = backend_open(obj, O_NONBLOCK);
	    if (fd != -1) {
		res = backend_fstat(fd, &buf);
	    }

	    if (res != -1) {
		if (backend_check_create_verifier
		    (&buf, argp->how.createhow3_u.verf)) {
		    /* The verifier matched. Return success */
		    gen = backend_get_gen(buf, fd, obj);
		    fh_cache_add(buf.st_dev, buf.st_ino, obj);
		    backend_close(fd);

		    result.CREATE3res_u.resok.obj =
			fh_extend_post(argp->where.dir, buf.st_dev,
				       buf.st_ino, gen);
		    result.CREATE3res_u.resok.obj_attributes =
			get_post_buf(buf, rqstp);
		} else {
		    /* The verifier doesn't match */
		    result.status = NFS3ERR_EXIST;
		}
	    }
	}
	if (res == -1) {
	    result.status = create_err();
	}
    }

    /* overlaps with resfail */
    result.CREATE3res_u.resok.dir_wcc.before = get_pre_cached();
    result.CREATE3res_u.resok.dir_wcc.after = get_post_stat(path, rqstp);

    return &result;
}

MKDIR3res *nfsproc3_mkdir_3_svc(MKDIR3args * argp, struct svc_req * rqstp)
{
    static MKDIR3res result;
    char *path;
    pre_op_attr pre;
    post_op_attr post;
    char obj[NFS_MAXPATHLEN];
    int res;

    PREP(path, argp->where.dir);
    pre = get_pre_cached();
    result.status =
	join3(cat_name(path, argp->where.name, obj),
	      atomic_attr(argp->attributes), exports_rw());

    cluster_create(obj, rqstp, &result.status);

    if (result.status == NFS3_OK) {
	res = backend_mkdir(obj, create_mode(argp->attributes));
	if (res == -1)
	    result.status = mkdir_err();
	else {
	    result.MKDIR3res_u.resok.obj =
		fh_extend_type(argp->where.dir, obj, S_IFDIR);
	    result.MKDIR3res_u.resok.obj_attributes = get_post_cached(rqstp);
	}
    }

    post = get_post_attr(path, argp->where.dir, rqstp);

    /* overlaps with resfail */
    result.MKDIR3res_u.resok.dir_wcc.before = pre;
    result.MKDIR3res_u.resok.dir_wcc.after = post;

    return &result;
}

SYMLINK3res *nfsproc3_symlink_3_svc(SYMLINK3args * argp,
				    struct svc_req * rqstp)
{
    static SYMLINK3res result;
    char *path;
    pre_op_attr pre;
    post_op_attr post;
    char obj[NFS_MAXPATHLEN];
    int res;
    mode_t new_mode;

    PREP(path, argp->where.dir);
    pre = get_pre_cached();
    result.status =
	join3(cat_name(path, argp->where.name, obj),
	      atomic_attr(argp->symlink.symlink_attributes), exports_rw());

    cluster_create(obj, rqstp, &result.status);

    if (argp->symlink.symlink_attributes.mode.set_it == TRUE)
	new_mode = create_mode(argp->symlink.symlink_attributes);
    else {
	/* default rwxrwxrwx */
	new_mode =
	    S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP |
	    S_IROTH | S_IWOTH | S_IXOTH;
    }

    if (result.status == NFS3_OK) {
	umask(~new_mode);
	res = backend_symlink(argp->symlink.symlink_data, obj);
	umask(0);
	if (res == -1)
	    result.status = symlink_err();
	else {
	    result.SYMLINK3res_u.resok.obj =
		fh_extend_type(argp->where.dir, obj, S_IFLNK);
	    result.SYMLINK3res_u.resok.obj_attributes =
		get_post_cached(rqstp);
	}
    }

    post = get_post_attr(path, argp->where.dir, rqstp);

    /* overlaps with resfail */
    result.SYMLINK3res_u.resok.dir_wcc.before = pre;
    result.SYMLINK3res_u.resok.dir_wcc.after = post;

    return &result;
}

#ifndef WIN32

/*
 * create Unix socket
 */
static int mksocket(const char *path, mode_t mode)
{
    int res, sock;
    struct sockaddr_un addr;

    sock = socket(PF_UNIX, SOCK_STREAM, 0);
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, path);
    res = sock;
    if (res != -1) {
	umask(~mode);
	res =
	    bind(sock, (struct sockaddr *) &addr,
		 sizeof(addr.sun_family) + strlen(addr.sun_path));
	umask(0);
	close(sock);
    }
    return res;
}

#endif				       /* WIN32 */

/*
 * check and process arguments to MKNOD procedure
 */
static nfsstat3 mknod_args(mknoddata3 what, const char *obj, mode_t * mode,
			   dev_t * dev)
{
    sattr3 attr;

    /* determine attributes */
    switch (what.type) {
	case NF3REG:
	case NF3DIR:
	case NF3LNK:
	    return NFS3ERR_INVAL;
	case NF3SOCK:
	    if (strlen(obj) + 1 > UNIX_PATH_MAX)
		return NFS3ERR_NAMETOOLONG;
	    /* fall thru */
	case NF3FIFO:
	    attr = what.mknoddata3_u.pipe_attributes;
	    break;
	case NF3BLK:
	case NF3CHR:
	    attr = what.mknoddata3_u.device.dev_attributes;
	    *dev = (what.mknoddata3_u.device.spec.specdata1 << 8)
		+ what.mknoddata3_u.device.spec.specdata2;
	    break;
    }

    *mode = create_mode(attr);

    /* adjust mode for creation of device special files */
    switch (what.type) {
	case NF3CHR:
	    *mode |= S_IFCHR;
	    break;
	case NF3BLK:
	    *mode |= S_IFBLK;
	    break;
	default:
	    break;
    }

    return atomic_attr(attr);
}

MKNOD3res *nfsproc3_mknod_3_svc(MKNOD3args * argp, struct svc_req * rqstp)
{
    static MKNOD3res result;
    char *path;
    pre_op_attr pre;
    post_op_attr post;
    char obj[NFS_MAXPATHLEN];
    int res;
    mode_t new_mode = 0;
    dev_t dev = 0;

    PREP(path, argp->where.dir);
    pre = get_pre_cached();
    result.status =
	join3(cat_name(path, argp->where.name, obj),
	      mknod_args(argp->what, obj, &new_mode, &dev), exports_rw());

    cluster_create(obj, rqstp, &result.status);

    if (result.status == NFS3_OK) {
	if (argp->what.type == NF3CHR || argp->what.type == NF3BLK)
	    res = backend_mknod(obj, new_mode, dev);	/* device */
	else if (argp->what.type == NF3FIFO)
	    res = backend_mkfifo(obj, new_mode);	/* FIFO */
	else
	    res = backend_mksocket(obj, new_mode);	/* socket */

	if (res == -1) {
	    result.status = mknod_err();
	} else {
	    result.MKNOD3res_u.resok.obj =
		fh_extend_type(argp->where.dir, obj,
			       type_to_mode(argp->what.type));
	    result.MKNOD3res_u.resok.obj_attributes = get_post_cached(rqstp);
	}
    }

    post = get_post_attr(path, argp->where.dir, rqstp);

    /* overlaps with resfail */
    result.MKNOD3res_u.resok.dir_wcc.before = pre;
    result.MKNOD3res_u.resok.dir_wcc.after = post;

    return &result;
}

REMOVE3res *nfsproc3_remove_3_svc(REMOVE3args * argp, struct svc_req * rqstp)
{
    static REMOVE3res result;
    char *path;
    char obj[NFS_MAXPATHLEN];
    int res;

    PREP(path, argp->object.dir);
    result.status =
	join(cat_name(path, argp->object.name, obj), exports_rw());

    cluster_lookup(obj, rqstp, &result.status);

    if (result.status == NFS3_OK) {
        change_readdir_cookie();
	res = backend_remove(obj);
	if (res == -1)
	    result.status = remove_err();
    }

    /* overlaps with resfail */
    result.REMOVE3res_u.resok.dir_wcc.before = get_pre_cached();
    result.REMOVE3res_u.resok.dir_wcc.after = get_post_stat(path, rqstp);

    return &result;
}

RMDIR3res *nfsproc3_rmdir_3_svc(RMDIR3args * argp, struct svc_req * rqstp)
{
    static RMDIR3res result;
    char *path;
    char obj[NFS_MAXPATHLEN];
    int res;

    PREP(path, argp->object.dir);
    result.status =
	join(cat_name(path, argp->object.name, obj), exports_rw());

    cluster_lookup(obj, rqstp, &result.status);

    if (result.status == NFS3_OK) {
        change_readdir_cookie();
	res = backend_rmdir(obj);
	if (res == -1)
	    result.status = rmdir_err();
    }

    /* overlaps with resfail */
    result.RMDIR3res_u.resok.dir_wcc.before = get_pre_cached();
    result.RMDIR3res_u.resok.dir_wcc.after = get_post_stat(path, rqstp);

    return &result;
}

RENAME3res *nfsproc3_rename_3_svc(RENAME3args * argp, struct svc_req * rqstp)
{
    static RENAME3res result;
    char *from;
    char *to;
    char from_obj[NFS_MAXPATHLEN];
    char to_obj[NFS_MAXPATHLEN];
    pre_op_attr pre;
    post_op_attr post;
    int res;

    PREP(from, argp->from.dir);
    pre = get_pre_cached();
    result.status =
	join(cat_name(from, argp->from.name, from_obj), exports_rw());

    cluster_lookup(from_obj, rqstp, &result.status);

    to = fh_decomp(argp->to.dir);

    if (result.status == NFS3_OK) {
	result.status =
	    join(cat_name(to, argp->to.name, to_obj),
		 exports_compat(to, rqstp));

	cluster_create(to_obj, rqstp, &result.status);

	if (result.status == NFS3_OK) {
	    change_readdir_cookie();
	    res = backend_rename(from_obj, to_obj);
	    if (res == -1)
		result.status = rename_err();
	}
    }

    post = get_post_attr(from, argp->from.dir, rqstp);

    /* overlaps with resfail */
    result.RENAME3res_u.resok.fromdir_wcc.before = pre;
    result.RENAME3res_u.resok.fromdir_wcc.after = post;
    result.RENAME3res_u.resok.todir_wcc.before = get_pre_cached();
    result.RENAME3res_u.resok.todir_wcc.after = get_post_stat(to, rqstp);

    return &result;
}

LINK3res *nfsproc3_link_3_svc(LINK3args * argp, struct svc_req * rqstp)
{
    static LINK3res result;
    char *path, *old;
    pre_op_attr pre;
    post_op_attr post;
    char obj[NFS_MAXPATHLEN];
    int res;

    PREP(path, argp->link.dir);
    pre = get_pre_cached();
    result.status = join(cat_name(path, argp->link.name, obj), exports_rw());

    cluster_create(obj, rqstp, &result.status);

    old = fh_decomp(argp->file);

    if (old && result.status == NFS3_OK) {
	result.status = exports_compat(old, rqstp);

	if (result.status == NFS3_OK) {
	    res = backend_link(old, obj);
	    if (res == -1)
		result.status = link_err();
	}
    } else if (!old)
	result.status = NFS3ERR_STALE;

    post = get_post_attr(path, argp->link.dir, rqstp);

    /* overlaps with resfail */
    result.LINK3res_u.resok.file_attributes = get_post_stat(old, rqstp);
    result.LINK3res_u.resok.linkdir_wcc.before = pre;
    result.LINK3res_u.resok.linkdir_wcc.after = post;

    return &result;
}

READDIR3res *nfsproc3_readdir_3_svc(READDIR3args * argp,
				    struct svc_req * rqstp)
{
    static READDIR3res result;
    char *path;

    PREP(path, argp->dir);

    result = read_dir(path, argp->cookie, argp->cookieverf, argp->count);
    result.READDIR3res_u.resok.dir_attributes = get_post_stat(path, rqstp);

    return &result;
}

READDIRPLUS3res *nfsproc3_readdirplus_3_svc(U(READDIRPLUS3args * argp),
					    U(struct svc_req * rqstp))
{
    static READDIRPLUS3res result;

    /* 
     * we don't do READDIRPLUS since it involves filehandle and
     * attribute getting which is impossible to do atomically
     * from user-space
     */
    result.status = NFS3ERR_NOTSUPP;
    result.READDIRPLUS3res_u.resfail.dir_attributes.attributes_follow = FALSE;

    return &result;
}

FSSTAT3res *nfsproc3_fsstat_3_svc(FSSTAT3args * argp, struct svc_req * rqstp)
{
    static FSSTAT3res result;
    char *path;
    backend_statvfsstruct buf;
    int res;

    PREP(path, argp->fsroot);

    /* overlaps with resfail */
    result.FSSTAT3res_u.resok.obj_attributes = get_post_cached(rqstp);

    res = backend_statvfs(path, &buf);
    if (res == -1) {
	/* statvfs fell on its nose */
	if ((exports_opts & OPT_REMOVABLE) && export_point(path)) {
	    /* Removable media export point; probably no media inserted.
	       Return dummy values. */
	    result.status = NFS3_OK;
	    result.FSSTAT3res_u.resok.tbytes = 0;
	    result.FSSTAT3res_u.resok.fbytes = 0;
	    result.FSSTAT3res_u.resok.abytes = 0;
	    result.FSSTAT3res_u.resok.tfiles = 0;
	    result.FSSTAT3res_u.resok.ffiles = 0;
	    result.FSSTAT3res_u.resok.afiles = 0;
	    result.FSSTAT3res_u.resok.invarsec = 0;
	} else {
	    result.status = NFS3ERR_IO;
	}
    } else {
	result.status = NFS3_OK;
	result.FSSTAT3res_u.resok.tbytes =
	    (uint64) buf.f_blocks * buf.f_frsize;
	result.FSSTAT3res_u.resok.fbytes = 
	    (uint64) buf.f_bfree * buf.f_frsize;
	result.FSSTAT3res_u.resok.abytes =
	    (uint64) buf.f_bavail * buf.f_frsize;
	result.FSSTAT3res_u.resok.tfiles = buf.f_files;
	result.FSSTAT3res_u.resok.ffiles = buf.f_ffree;
	result.FSSTAT3res_u.resok.afiles = buf.f_ffree;
	result.FSSTAT3res_u.resok.invarsec = 0;
    }

    return &result;
}

FSINFO3res *nfsproc3_fsinfo_3_svc(FSINFO3args * argp, struct svc_req * rqstp)
{
    static FSINFO3res result;
    char *path;
    unsigned int maxdata;

    if (get_socket_type(rqstp) == SOCK_STREAM)
	maxdata = NFS_MAXDATA_TCP;
    else
	maxdata = NFS_MAXDATA_UDP;

    PREP(path, argp->fsroot);

    result.FSINFO3res_u.resok.obj_attributes = get_post_cached(rqstp);

    result.status = NFS3_OK;
    result.FSINFO3res_u.resok.rtmax = maxdata;
    result.FSINFO3res_u.resok.rtpref = maxdata;
    result.FSINFO3res_u.resok.rtmult = 4096;
    result.FSINFO3res_u.resok.wtmax = maxdata;
    result.FSINFO3res_u.resok.wtpref = maxdata;
    result.FSINFO3res_u.resok.wtmult = 4096;
    result.FSINFO3res_u.resok.dtpref = 4096;
    result.FSINFO3res_u.resok.maxfilesize = ~0ULL;
    result.FSINFO3res_u.resok.time_delta.seconds = backend_time_delta_seconds;
    result.FSINFO3res_u.resok.time_delta.nseconds = 0;
    result.FSINFO3res_u.resok.properties = backend_fsinfo_properties;

    return &result;
}

PATHCONF3res *nfsproc3_pathconf_3_svc(PATHCONF3args * argp,
				      struct svc_req * rqstp)
{
    static PATHCONF3res result;
    char *path;

    PREP(path, argp->object);

    result.PATHCONF3res_u.resok.obj_attributes = get_post_cached(rqstp);

    result.status = NFS3_OK;
    result.PATHCONF3res_u.resok.linkmax = 0xFFFFFFFF;
    result.PATHCONF3res_u.resok.name_max = NFS_MAXPATHLEN;
    result.PATHCONF3res_u.resok.no_trunc = TRUE;
    result.PATHCONF3res_u.resok.chown_restricted = FALSE;
    result.PATHCONF3res_u.resok.case_insensitive =
	backend_pathconf_case_insensitive;
    result.PATHCONF3res_u.resok.case_preserving = TRUE;

    return &result;
}

COMMIT3res *nfsproc3_commit_3_svc(COMMIT3args * argp, struct svc_req * rqstp)
{
    static COMMIT3res result;
    char *path;
    int res;

    PREP(path, argp->file);
    result.status = join(is_reg(), exports_rw());

    if (result.status == NFS3_OK) {
	res = fd_sync(argp->file);
	if (res != -1)
	    memcpy(result.COMMIT3res_u.resok.verf, wverf, NFS3_WRITEVERFSIZE);
	else
	    /* error during fsync() or close() */
	    result.status = NFS3ERR_IO;
    }

    /* overlaps with resfail */
    result.COMMIT3res_u.resfail.file_wcc.before = get_pre_cached();
    result.COMMIT3res_u.resfail.file_wcc.after = get_post_stat(path, rqstp);

    return &result;
}
