summaryrefslogtreecommitdiffstats
path: root/xlators/nfs/server/src/nfs3-helpers.c
diff options
context:
space:
mode:
authorShehjar Tikoo <shehjart@gluster.com>2010-03-31 07:27:05 +0000
committerAnand V. Avati <avati@dev.gluster.com>2010-03-31 07:44:09 -0700
commitf66f04e3af1f31a4c51b6a87f5e32ea2e3c7b438 (patch)
tree450da8640fd35525a3b288893f86fbd33b0bd47e /xlators/nfs/server/src/nfs3-helpers.c
parente63053803dca86b4283b71bc4ef4cb180ec72d89 (diff)
nfs: Add NFSv3 protocol support
Signed-off-by: Shehjar Tikoo <shehjart@gluster.com> Signed-off-by: Anand V. Avati <avati@dev.gluster.com> BUG: 399 (NFS translator with Mount v3 and NFS v3 support) URL: http://bugs.gluster.com/cgi-bin/bugzilla3/show_bug.cgi?id=399
Diffstat (limited to 'xlators/nfs/server/src/nfs3-helpers.c')
-rw-r--r--xlators/nfs/server/src/nfs3-helpers.c2825
1 files changed, 2825 insertions, 0 deletions
diff --git a/xlators/nfs/server/src/nfs3-helpers.c b/xlators/nfs/server/src/nfs3-helpers.c
new file mode 100644
index 000000000..5b2fdaca4
--- /dev/null
+++ b/xlators/nfs/server/src/nfs3-helpers.c
@@ -0,0 +1,2825 @@
+/*
+ Copyright (c) 2010 Gluster, Inc. <http://www.gluster.com>
+ This file is part of GlusterFS.
+
+ GlusterFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ GlusterFS 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+
+#include "xlator.h"
+#include "nfs3.h"
+#include "nfs3-fh.h"
+#include "msg-nfs3.h"
+#include "rbthash.h"
+#include "nfs-fops.h"
+#include "nfs-inodes.h"
+#include "nfs-generics.h"
+#include "nfs3-helpers.h"
+#include "iatt.h"
+#include <string.h>
+
+#define nfs3_call_resume(cst) \
+ do { \
+ if (((cst)) && (cst)->resume_fn) \
+ (cst)->resume_fn (cst); \
+ } while (0) \
+
+#define nfs3_call_resume_estale(csta) \
+ do { \
+ (csta)->resolve_ret = -1; \
+ (csta)->resolve_errno = ESTALE; \
+ nfs3_call_resume (csta); \
+ } while (0) \
+
+struct nfs3stat_strerror {
+ nfsstat3 stat;
+ char strerror[100];
+};
+
+
+struct nfs3stat_strerror nfs3stat_strerror_table[] = {
+ { NFS3_OK, "Call completed successfully." },
+ { NFS3ERR_PERM, "Not owner" },
+ { NFS3ERR_NOENT, "No such file or directory" },
+ { NFS3ERR_IO, "I/O error" },
+ { NFS3ERR_NXIO, "I/O error" },
+ { NFS3ERR_ACCES, "Permission denied" },
+ { NFS3ERR_EXIST, "File exists" },
+ { NFS3ERR_XDEV, "Attempt to do a cross-device hard link"},
+ { NFS3ERR_NODEV, "No such device" },
+ { NFS3ERR_NOTDIR, "Not a directory" },
+ { NFS3ERR_ISDIR, "Not a directory" },
+ { NFS3ERR_INVAL, "Invalid argument for operation" },
+ { NFS3ERR_FBIG, "File too large" },
+ { NFS3ERR_NOSPC, "No space left on device" },
+ { NFS3ERR_ROFS, "Read-only file system" },
+ { NFS3ERR_MLINK, "Too many hard links" },
+ { NFS3ERR_NAMETOOLONG, "Filename in operation was too long" },
+ { NFS3ERR_NOTEMPTY, "Directory not empty" },
+ { NFS3ERR_DQUOT, "Resource (quota) hard limit exceeded" },
+ { NFS3ERR_STALE, "Invalid file handle" },
+ { NFS3ERR_REMOTE, "Too many levels of remote in path" },
+ { NFS3ERR_BADHANDLE, "Illegal NFS file handle" },
+ { NFS3ERR_NOT_SYNC, "Update synchronization mismatch detected" },
+ { NFS3ERR_BAD_COOKIE, "READDIR or READDIRPLUS cookie is stale"},
+ { NFS3ERR_NOTSUPP, "Operation is not supported" },
+ { NFS3ERR_TOOSMALL, "Buffer or request is too small" },
+ { NFS3ERR_SERVERFAULT, "Error occurred on the server or IO Error" },
+ { NFS3ERR_BADTYPE, "Type not supported by the server" },
+ { NFS3ERR_JUKEBOX, "Cannot complete server initiated request" },
+ { -1, "IO Error" },
+
+};
+
+
+void
+nfs3_map_xlid_to_statdev (struct iatt *ia, uint16_t xlid)
+{
+ if (!ia)
+ return;
+
+ ia->ia_dev = xlid;
+}
+
+
+struct nfs3_fh
+nfs3_extract_nfs3_fh (nfs_fh3 fh)
+{
+ struct nfs3_fh gfh;
+
+ memcpy (&gfh, fh.data.data_val, fh.data.data_len);
+ return gfh;
+}
+
+
+struct nfs3_fh
+nfs3_extract_lookup_fh (lookup3args *args)
+{
+ return nfs3_extract_nfs3_fh(args->what.dir);
+}
+
+
+char *
+nfs3_extract_lookup_name (lookup3args *args)
+{
+ return args->what.name;
+}
+
+
+nfsstat3
+nfs3_errno_to_nfsstat3 (int errnum)
+{
+ nfsstat3 stat = NFS3_OK;
+
+ switch (errnum) {
+
+ case 0:
+ stat = NFS3_OK;
+ break;
+
+ case EPERM:
+ stat = NFS3ERR_PERM;
+ break;
+
+ case ENOENT:
+ stat = NFS3ERR_NOENT;
+ break;
+
+ case EACCES:
+ stat = NFS3ERR_ACCES;
+ break;
+
+ case EEXIST:
+ stat = NFS3ERR_EXIST;
+ break;
+
+ case EXDEV:
+ stat = NFS3ERR_XDEV;
+ break;
+
+ case ENODEV:
+ stat = NFS3ERR_NODEV;
+ break;
+
+ case EIO:
+ stat = NFS3ERR_IO;
+ break;
+
+ case ENXIO:
+ stat = NFS3ERR_NXIO;
+ break;
+
+ case ENOTDIR:
+ stat = NFS3ERR_NOTDIR;
+ break;
+
+ case EISDIR:
+ stat = NFS3ERR_ISDIR;
+ break;
+
+ case EINVAL:
+ stat = NFS3ERR_INVAL;
+ break;
+
+ case ENOSPC:
+ stat = NFS3ERR_NOSPC;
+ break;
+
+ case EROFS:
+ stat = NFS3ERR_ROFS;
+ break;
+
+ case EFBIG:
+ stat = NFS3ERR_FBIG;
+ break;
+
+ case EMLINK:
+ stat = NFS3ERR_MLINK;
+ break;
+
+ case ENAMETOOLONG:
+ stat = NFS3ERR_NAMETOOLONG;
+ break;
+
+ case ENOTEMPTY:
+ stat = NFS3ERR_NOTEMPTY;
+ break;
+
+ case EFAULT:
+ stat = NFS3ERR_SERVERFAULT;
+ break;
+
+ case ENOSYS:
+ stat = NFS3ERR_NOTSUPP;
+ break;
+
+ case EBADF:
+ stat = NFS3ERR_BADTYPE;
+ break;
+
+ case ESTALE:
+ stat = NFS3ERR_STALE;
+
+ default:
+ stat = NFS3ERR_SERVERFAULT;
+ break;
+ }
+
+ return stat;
+}
+
+
+void
+nfs3_fill_lookup3res_error (lookup3res *res, nfsstat3 stat,
+ struct iatt *dirstat)
+{
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (!dirstat) {
+ res->lookup3res_u.resfail.dir_attributes.attributes_follow = FALSE;
+ }
+
+}
+
+fattr3
+nfs3_stat_to_fattr3 (struct iatt *buf)
+{
+ fattr3 fa = {0, };
+
+ if (IA_ISDIR (buf->ia_type))
+ fa.type = NF3DIR;
+ else if (IA_ISREG (buf->ia_type))
+ fa.type = NF3REG;
+ else if (IA_ISCHR (buf->ia_type))
+ fa.type = NF3CHR;
+ else if (IA_ISBLK (buf->ia_type))
+ fa.type = NF3BLK;
+ else if (IA_ISFIFO (buf->ia_type))
+ fa.type = NF3FIFO;
+ else if (IA_ISLNK (buf->ia_type))
+ fa.type = NF3LNK;
+ else if (IA_ISSOCK (buf->ia_type))
+ fa.type = NF3SOCK;
+
+ if (IA_PROT_RUSR (buf->ia_prot))
+ fa.mode |= NFS3MODE_ROWNER;
+ if (IA_PROT_WUSR (buf->ia_prot))
+ fa.mode |= NFS3MODE_WOWNER;
+ if (IA_PROT_XUSR (buf->ia_prot))
+ fa.mode |= NFS3MODE_XOWNER;
+
+ if (IA_PROT_RGRP (buf->ia_prot))
+ fa.mode |= NFS3MODE_RGROUP;
+ if (IA_PROT_WGRP (buf->ia_prot))
+ fa.mode |= NFS3MODE_WGROUP;
+ if (IA_PROT_XGRP (buf->ia_prot))
+ fa.mode |= NFS3MODE_XGROUP;
+
+ if (IA_PROT_ROTH (buf->ia_prot))
+ fa.mode |= NFS3MODE_ROTHER;
+ if (IA_PROT_WOTH (buf->ia_prot))
+ fa.mode |= NFS3MODE_WOTHER;
+ if (IA_PROT_XOTH (buf->ia_prot))
+ fa.mode |= NFS3MODE_XOTHER;
+
+ if (IA_PROT_SUID (buf->ia_prot))
+ fa.mode |= NFS3MODE_SETXUID;
+ if (IA_PROT_SGID (buf->ia_prot))
+ fa.mode |= NFS3MODE_SETXGID;
+ if (IA_PROT_STCKY (buf->ia_prot))
+ fa.mode |= NFS3MODE_SAVESWAPTXT;
+
+ fa.nlink = buf->ia_nlink;
+ fa.uid = buf->ia_uid;
+ fa.gid = buf->ia_gid;
+ fa.size = buf->ia_size;
+ fa.used = (buf->ia_blocks * 512);
+
+ if ((IA_ISCHR (buf->ia_type) || IA_ISBLK (buf->ia_type))) {
+ fa.rdev.specdata1 = ia_major (buf->ia_rdev);
+ fa.rdev.specdata2 = ia_minor (buf->ia_rdev);
+ } else {
+ fa.rdev.specdata1 = 0;
+ fa.rdev.specdata2 = 0;
+ }
+
+ fa.fsid = buf->ia_dev;
+ fa.fileid = buf->ia_ino;
+ /* FIXME: Handle time resolutions for sub-second granularity */
+ if (buf->ia_atime == 9669) {
+ fa.mtime.seconds = 0;
+ fa.mtime.nseconds = 0;
+ fa.atime.seconds = 0;
+ fa.atime.nseconds = 0;
+ } else {
+ fa.mtime.seconds = buf->ia_mtime;
+ fa.mtime.nseconds = 0;
+ fa.atime.seconds = buf->ia_atime;
+ fa.atime.seconds = 0;
+ fa.atime.nseconds = 0;
+ }
+
+ fa.atime.seconds = buf->ia_atime;
+ fa.atime.nseconds = 0;
+
+ fa.ctime.seconds = buf->ia_ctime;
+ fa.ctime.nseconds = 0;
+
+ return fa;
+}
+
+
+post_op_attr
+nfs3_stat_to_post_op_attr (struct iatt *buf)
+{
+ post_op_attr attr = {0, };
+ if (!buf)
+ return attr;
+
+ /* Some performance translators return zero-filled stats when they
+ * do not have up-to-date attributes. Need to handle this by not
+ * returning these zeroed out attrs.
+ */
+ attr.attributes_follow = FALSE;
+ if (nfs_zero_filled_stat (buf))
+ goto out;
+
+ attr.post_op_attr_u.attributes = nfs3_stat_to_fattr3 (buf);
+ attr.attributes_follow = TRUE;
+
+out:
+ return attr;
+}
+
+
+pre_op_attr
+nfs3_stat_to_pre_op_attr (struct iatt *pre)
+{
+ pre_op_attr poa = {0, };
+
+ /* Some performance translators return zero-filled stats when they
+ * do not have up-to-date attributes. Need to handle this by not
+ * returning these zeroed out attrs.
+ */
+ poa.attributes_follow = FALSE;
+ if (nfs_zero_filled_stat (pre))
+ goto out;
+
+ poa.attributes_follow = TRUE;
+ poa.pre_op_attr_u.attributes.size = pre->ia_size;
+ if (pre->ia_atime == 9669)
+ poa.pre_op_attr_u.attributes.mtime.seconds = 0;
+ else
+ poa.pre_op_attr_u.attributes.mtime.seconds = pre->ia_mtime;
+ poa.pre_op_attr_u.attributes.ctime.seconds = pre->ia_ctime;
+
+out:
+ return poa;
+}
+
+void
+nfs3_fill_lookup3res_success (lookup3res *res, nfsstat3 stat,
+ struct nfs3_fh *fh, struct iatt *buf,
+ struct iatt *postparent)
+{
+ post_op_attr obj, dir;
+ uint32_t fhlen = 0;
+
+ res->status = stat;
+ if (fh) {
+ res->lookup3res_u.resok.object.data.data_val = (void *)fh;
+ fhlen = nfs3_fh_compute_size (fh);
+ res->lookup3res_u.resok.object.data.data_len = fhlen;
+ }
+
+ obj.attributes_follow = FALSE;
+ dir.attributes_follow = FALSE;
+ if (buf) {
+ nfs3_map_xlid_to_statdev (buf, fh->xlatorid);
+ obj = nfs3_stat_to_post_op_attr (buf);
+ }
+
+ if (postparent) {
+ nfs3_map_xlid_to_statdev (postparent, fh->xlatorid);
+ dir = nfs3_stat_to_post_op_attr (postparent);
+ }
+
+ res->lookup3res_u.resok.obj_attributes = obj;
+ res->lookup3res_u.resok.dir_attributes = dir;
+}
+
+
+void
+nfs3_fill_lookup3res (lookup3res *res, nfsstat3 stat, struct nfs3_fh *newfh,
+ struct iatt *buf, struct iatt *postparent)
+{
+
+ memset (res, 0, sizeof (*res));
+ if (stat != NFS3_OK)
+ nfs3_fill_lookup3res_error (res, stat, postparent);
+ else
+ nfs3_fill_lookup3res_success (res, stat, newfh, buf,
+ postparent);
+}
+
+struct nfs3_fh
+nfs3_extract_getattr_fh (getattr3args *args)
+{
+ return nfs3_extract_nfs3_fh(args->object);
+}
+
+
+void
+nfs3_fill_getattr3res (getattr3res *res, nfsstat3 stat, struct iatt *buf,
+ uint16_t xlid)
+{
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (buf, xlid);
+ res->getattr3res_u.resok.obj_attributes = nfs3_stat_to_fattr3 (buf);
+
+}
+
+
+struct nfs3_fh
+nfs3_extract_fsinfo_fh (fsinfo3args *args)
+{
+ return nfs3_extract_nfs3_fh (args->fsroot);
+}
+
+
+void
+nfs3_fill_fsinfo3res (struct nfs3_state *nfs3, fsinfo3res *res,
+ nfsstat3 status, struct iatt *fsroot, uint16_t xlid)
+{
+ fsinfo3resok resok = {{0}, };
+ nfstime3 tdelta = GF_NFS3_TIMEDELTA_SECS;
+
+ memset (res, 0, sizeof (*res));
+ res->status = status;
+ if (status != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (fsroot, xlid);
+ resok.obj_attributes = nfs3_stat_to_post_op_attr (fsroot);
+ resok.rtmax = nfs3->readsize;
+ resok.rtpref = nfs3->readsize;
+ resok.rtmult = GF_NFS3_RTMULT;
+ resok.wtmax = nfs3->writesize;
+ resok.wtpref = nfs3->writesize;
+ resok.wtmult = GF_NFS3_WTMULT;
+ resok.dtpref = nfs3->readdirsize;
+ resok.maxfilesize = GF_NFS3_MAXFILE;
+ resok.time_delta = tdelta;
+ resok.properties = GF_NFS3_FS_PROP;
+
+ res->fsinfo3res_u.resok = resok;
+
+}
+
+
+void
+nfs3_prep_lookup3args (lookup3args *args, struct nfs3_fh *fh, char *name)
+{
+ memset (args, 0, sizeof (*args));
+ args->what.dir.data.data_val = (void *)fh;
+ args->what.name = name;
+}
+
+
+void
+nfs3_prep_getattr3args (getattr3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->object.data.data_val = (void *)fh;
+}
+
+
+void
+nfs3_prep_fsinfo3args (fsinfo3args *args, struct nfs3_fh *root)
+{
+ memset (args, 0, sizeof (*args));
+ args->fsroot.data.data_val = (void *)root;
+}
+
+
+char *
+nfsstat3_strerror(int stat)
+{
+ int i;
+ for(i = 0; nfs3stat_strerror_table[i].stat != -1; i++) {
+ if (nfs3stat_strerror_table[i].stat == stat)
+ return nfs3stat_strerror_table[i].strerror;
+ }
+
+ return nfs3stat_strerror_table[i].strerror;
+}
+
+
+
+void
+nfs3_prep_access3args (access3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->object.data.data_val = (void *)fh;
+}
+
+
+uint32_t
+nfs3_owner_accessbits (ia_prot_t prot, ia_type_t type, uint32_t request)
+{
+ uint32_t accresult = 0;
+
+ if (IA_PROT_RUSR (prot) && (request & ACCESS3_READ))
+ accresult |= ACCESS3_READ;
+
+ if (request & ACCESS3_LOOKUP)
+ if ((IA_ISDIR (type)) && (IA_PROT_XUSR (prot)))
+ accresult |= ACCESS3_LOOKUP;
+
+ if ((IA_PROT_WUSR (prot) && (request & ACCESS3_MODIFY)))
+ accresult |= ACCESS3_MODIFY;
+
+ if ((IA_PROT_WUSR (prot) && (request & ACCESS3_EXTEND)))
+ accresult |= ACCESS3_EXTEND;
+
+ /* ACCESS3_DELETE is ignored for now since that requires
+ * knowing the permissions on the parent directory.
+ */
+
+ if (request & ACCESS3_EXECUTE)
+ if (IA_PROT_XUSR (prot) && (!IA_ISDIR (type)))
+ accresult |= ACCESS3_EXECUTE;
+
+ return accresult;
+}
+
+
+uint32_t
+nfs3_group_accessbits (ia_prot_t prot, ia_type_t type, uint32_t request)
+{
+ uint32_t accresult = 0;
+
+ if (IA_PROT_RGRP (prot) && (request & ACCESS3_READ))
+ accresult |= ACCESS3_READ;
+
+ if (request & ACCESS3_LOOKUP)
+ if ((IA_ISDIR (type)) && IA_PROT_RGRP (prot))
+ accresult |= ACCESS3_LOOKUP;
+
+ if (IA_PROT_WGRP (prot) && (request & ACCESS3_MODIFY))
+ accresult |= ACCESS3_MODIFY;
+
+ if (IA_PROT_WGRP (prot) && (request & ACCESS3_EXTEND))
+ accresult |= ACCESS3_EXTEND;
+
+ /* ACCESS3_DELETE is ignored for now since that requires
+ * knowing the permissions on the parent directory.
+ */
+
+ if (request & ACCESS3_EXECUTE)
+ if (IA_PROT_XGRP (prot) && (!IA_ISDIR (type)))
+ accresult |= ACCESS3_EXECUTE;
+
+ return accresult;
+}
+
+
+uint32_t
+nfs3_other_accessbits (ia_prot_t prot, ia_type_t type, uint32_t request)
+{
+ uint32_t accresult = 0;
+
+ if (IA_PROT_ROTH (prot) && (request & ACCESS3_READ))
+ accresult |= ACCESS3_READ;
+
+ if (request & ACCESS3_LOOKUP)
+ if (IA_ISDIR (type) && IA_PROT_ROTH (prot))
+ accresult |= ACCESS3_LOOKUP;
+
+ if (IA_PROT_WOTH (prot) && (request & ACCESS3_MODIFY))
+ accresult |= ACCESS3_MODIFY;
+
+ if (IA_PROT_WOTH (prot) && (request & ACCESS3_EXTEND))
+ accresult |= ACCESS3_EXTEND;
+
+ /* ACCESS3_DELETE is ignored for now since that requires
+ * knowing the permissions on the parent directory.
+ */
+
+ if (request & ACCESS3_EXECUTE)
+ if (IA_PROT_XOTH (prot) && (!IA_ISDIR (type)))
+ accresult |= ACCESS3_EXECUTE;
+
+ return accresult;
+}
+
+
+uint32_t
+nfs3_stat_to_accessbits (struct iatt *buf, uint32_t request, uid_t uid,
+ gid_t gid)
+{
+ uint32_t accresult = 0;
+ ia_prot_t prot = {0, };
+ ia_type_t type = 0;
+
+ prot = buf->ia_prot;
+ type = buf->ia_type;
+
+ if (buf->ia_uid == uid)
+ accresult = nfs3_owner_accessbits (prot, type, request);
+ else if (buf->ia_gid == gid)
+ accresult = nfs3_group_accessbits (prot, type, request);
+ else
+ accresult = nfs3_other_accessbits (prot, type, request);
+
+ return accresult;
+}
+
+
+void
+nfs3_fill_access3res (access3res *res, nfsstat3 status, struct iatt *buf,
+ uint32_t accbits, uid_t uid, gid_t gid, uint16_t xlid)
+{
+ post_op_attr objattr;
+ uint32_t accres = 0;
+
+ memset (res, 0, sizeof (*res));
+ res->status = status;
+ if (status != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (buf, xlid);
+ objattr = nfs3_stat_to_post_op_attr (buf);
+ accres = nfs3_stat_to_accessbits (buf, accbits, uid, gid);
+
+ res->access3res_u.resok.obj_attributes = objattr;
+ res->access3res_u.resok.access = accres;
+}
+
+void
+nfs3_prep_readdir3args (readdir3args *ra, struct nfs3_fh *fh)
+{
+ memset (ra, 0, sizeof (*ra));
+ ra->dir.data.data_val = (void *)fh;
+}
+
+
+entry3 *
+nfs3_fill_entry3 (gf_dirent_t *entry)
+{
+ entry3 *ent = NULL;
+ if (!entry)
+ return NULL;
+
+ ent = CALLOC (1, sizeof (*ent));
+ if (!ent)
+ return NULL;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry: %s", entry->d_name);
+ ent->fileid = entry->d_ino;
+ ent->cookie = entry->d_off;
+ ent->name = CALLOC ((strlen (entry->d_name) + 1), sizeof (char));
+ if (!ent->name) {
+ FREE (ent);
+ ent = NULL;
+ goto err;
+ }
+ strcpy (ent->name, entry->d_name);
+
+err:
+ return ent;
+}
+
+
+void
+nfs3_fill_post_op_fh3 (struct nfs3_fh *fh, post_op_fh3 *pfh)
+{
+ uint32_t fhlen = 0;
+
+ if ((!fh) || (!pfh))
+ return;
+
+ pfh->handle_follows = 1;
+ fhlen = nfs3_fh_compute_size (fh);
+ pfh->post_op_fh3_u.handle.data.data_val = (void *)fh;
+ pfh->post_op_fh3_u.handle.data.data_len = fhlen;
+}
+
+
+post_op_fh3
+nfs3_fh_to_post_op_fh3 (struct nfs3_fh *fh)
+{
+ post_op_fh3 pfh = {0, };
+ char *fhp = NULL;
+
+ if (!fh)
+ return pfh;
+
+ pfh.handle_follows = 1;
+
+ fhp = CALLOC (1, sizeof (*fh));
+ if (!fhp)
+ return pfh;
+
+ memcpy (fhp, fh, sizeof (*fh));
+ nfs3_fill_post_op_fh3 ((struct nfs3_fh *)fhp, &pfh);
+ return pfh;
+}
+
+
+entryp3 *
+nfs3_fill_entryp3 (gf_dirent_t *entry, struct nfs3_fh *dirfh)
+{
+ entryp3 *ent = NULL;
+ struct nfs3_fh newfh = {{0}, };
+
+ if ((!entry) || (!dirfh))
+ return NULL;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry: %s, ino: %"PRIu64,
+ entry->d_name, entry->d_ino);
+ ent = CALLOC (1, sizeof (*ent));
+ if (!ent)
+ return NULL;
+
+ ent->fileid = entry->d_ino;
+ ent->cookie = entry->d_off;
+ ent->name = CALLOC ((strlen (entry->d_name) + 1), sizeof (char));
+ if (!ent->name) {
+ FREE (ent);
+ ent = NULL;
+ goto err;
+ }
+ strcpy (ent->name, entry->d_name);
+
+ nfs3_fh_build_child_fh (dirfh, &entry->d_stat, &newfh);
+ nfs3_map_xlid_to_statdev (&entry->d_stat, dirfh->xlatorid);
+ ent->name_attributes = nfs3_stat_to_post_op_attr (&entry->d_stat);
+ ent->name_handle = nfs3_fh_to_post_op_fh3 (&newfh);
+err:
+ return ent;
+}
+
+
+void
+nfs3_fill_readdir3res (readdir3res *res, nfsstat3 stat, uint64_t cverf,
+ struct iatt *dirstat, gf_dirent_t *entries, count3 count,
+ int is_eof, uint16_t xlid)
+{
+ post_op_attr dirattr;
+ entry3 *ent = NULL;
+ entry3 *headentry = NULL;
+ entry3 *preventry = NULL;
+ count3 filled = 0;
+ gf_dirent_t *listhead = NULL;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (dirstat, xlid);
+ dirattr = nfs3_stat_to_post_op_attr (dirstat);
+ res->readdir3res_u.resok.dir_attributes = dirattr;
+ res->readdir3res_u.resok.reply.eof = (bool_t)is_eof;
+ memcpy (res->readdir3res_u.resok.cookieverf, &cverf, sizeof (cverf));
+
+ filled = NFS3_READDIR_RESOK_SIZE;
+ /* First entry is just the list head */
+ listhead = entries;
+ entries = entries->next;
+ while (((entries) && (entries != listhead)) && (filled < count)) {
+ /*
+ if ((strcmp (entries->d_name, ".") == 0) ||
+ (strcmp (entries->d_name, "..") == 0))
+ goto nextentry;
+ */
+ ent = nfs3_fill_entry3 (entries);
+ if (!ent)
+ break;
+
+ if (!headentry)
+ headentry = ent;
+
+ if (preventry) {
+ preventry->nextentry = ent;
+ preventry = ent;
+ } else
+ preventry = ent;
+
+ filled += NFS3_ENTRY3_FIXED_SIZE + strlen (ent->name);
+//nextentry:
+ entries = entries->next;
+ }
+
+ res->readdir3res_u.resok.reply.entries = headentry;
+
+ return;
+}
+
+
+void
+nfs3_fill_readdirp3res (readdirp3res *res, nfsstat3 stat, struct nfs3_fh *dirfh,
+ uint64_t cverf, struct iatt *dirstat,
+ gf_dirent_t *entries, count3 dircount, count3 maxcount,
+ int is_eof)
+{
+ post_op_attr dirattr;
+ entryp3 *ent = NULL;
+ entryp3 *headentry = NULL;
+ entryp3 *preventry = NULL;
+ count3 filled = 0;
+ gf_dirent_t *listhead = NULL;
+ int fhlen = 0;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (dirstat, dirfh->xlatorid);
+ dirattr = nfs3_stat_to_post_op_attr (dirstat);
+ res->readdirp3res_u.resok.dir_attributes = dirattr;
+ res->readdirp3res_u.resok.reply.eof = (bool_t)is_eof;
+ memcpy (res->readdirp3res_u.resok.cookieverf, &cverf, sizeof (cverf));
+
+ filled = NFS3_READDIR_RESOK_SIZE;
+ /* First entry is just the list head */
+ listhead = entries;
+ entries = entries->next;
+ while (((entries) && (entries != listhead)) && (filled < maxcount)) {
+ /* Linux does not display . and .. entries unless we provide
+ * these entries here.
+ */
+/* if ((strcmp (entries->d_name, ".") == 0) ||
+ (strcmp (entries->d_name, "..") == 0))
+ goto nextentry;
+ */
+ ent = nfs3_fill_entryp3 (entries, dirfh);
+ if (!ent)
+ break;
+
+ if (!headentry)
+ headentry = ent;
+
+ if (preventry) {
+ preventry->nextentry = ent;
+ preventry = ent;
+ } else
+ preventry = ent;
+
+ fhlen = ent->name_handle.post_op_fh3_u.handle.data.data_len;
+ filled += NFS3_ENTRYP3_FIXED_SIZE + fhlen + strlen (ent->name);
+//nextentry:
+ entries = entries->next;
+ }
+
+ res->readdirp3res_u.resok.reply.entries = headentry;
+
+ return;
+}
+
+
+void
+nfs3_prep_readdirp3args (readdirp3args *ra, struct nfs3_fh *fh)
+{
+ memset (ra, 0, sizeof (*ra));
+ ra->dir.data.data_val = (void *)fh;
+}
+
+void
+nfs3_free_readdirp3res (readdirp3res *res)
+{
+ entryp3 *ent = NULL;
+ entryp3 *next = NULL;
+
+ if (!res)
+ return;
+
+ ent = res->readdirp3res_u.resok.reply.entries;
+ while (ent) {
+
+ next = ent->nextentry;
+ FREE (ent->name);
+ FREE (ent->name_handle.post_op_fh3_u.handle.data.data_val);
+ FREE (ent);
+ ent = next;
+ }
+
+ return;
+}
+
+
+void
+nfs3_free_readdir3res (readdir3res *res)
+{
+ entry3 *ent = NULL;
+ entry3 *next = NULL;
+
+ if (!res)
+ return;
+
+ ent = res->readdir3res_u.resok.reply.entries;
+ while (ent) {
+
+ next = ent->nextentry;
+ FREE (ent->name);
+ FREE (ent);
+ ent = next;
+ }
+
+ return;
+}
+
+void
+nfs3_prep_fsstat3args (fsstat3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->fsroot.data.data_val = (char *)fh;
+}
+
+
+void
+nfs3_fill_fsstat3res (fsstat3res *res, nfsstat3 stat, struct statvfs *fsbuf,
+ struct iatt *postbuf, uint16_t xlid)
+{
+ post_op_attr poa;
+ fsstat3resok resok;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (postbuf, xlid);
+ poa = nfs3_stat_to_post_op_attr (postbuf);
+ resok.tbytes = (size3)(fsbuf->f_frsize * fsbuf->f_blocks);
+ resok.fbytes = (size3)(fsbuf->f_bsize * fsbuf->f_bfree);
+ resok.abytes = (size3)(fsbuf->f_bsize * fsbuf->f_bavail);
+ resok.tfiles = (size3)(fsbuf->f_files);
+ resok.ffiles = (size3)(fsbuf->f_ffree);
+ resok.afiles = (size3)(fsbuf->f_favail);
+ resok.invarsec = 0;
+
+ resok.obj_attributes = poa;
+ res->fsstat3res_u.resok = resok;
+}
+
+
+int32_t
+nfs3_sattr3_to_setattr_valid (sattr3 *sattr, struct iatt *buf, mode_t *omode)
+{
+ int32_t valid = 0;
+ ia_prot_t prot = {0, };
+ mode_t mode = 0;
+
+ if (!sattr)
+ return 0;
+
+ if (sattr->mode.set_it) {
+ valid |= GF_SET_ATTR_MODE;
+
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_ROWNER) {
+ mode |= S_IRUSR;
+ prot.owner.read = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_WOWNER) {
+ mode |= S_IWUSR;
+ prot.owner.write = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_XOWNER) {
+ mode |= S_IXUSR;
+ prot.owner.exec = 1;
+ }
+
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_RGROUP) {
+ mode |= S_IRGRP;
+ prot.group.read = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_WGROUP) {
+ mode |= S_IWGRP;
+ prot.group.write = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_XGROUP) {
+ mode |= S_IXGRP;
+ prot.group.exec = 1;
+ }
+
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_ROTHER) {
+ mode |= S_IROTH;
+ prot.other.read = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_WOTHER) {
+ mode |= S_IWOTH;
+ prot.other.write = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_XOTHER) {
+ mode |= S_IXOTH;
+ prot.other.exec = 1;
+ }
+
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_SETXUID) {
+ mode |= S_ISUID;
+ prot.suid = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_SETXGID) {
+ mode |= S_ISGID;
+ prot.sgid = 1;
+ }
+ if (sattr->mode.set_mode3_u.mode & NFS3MODE_SAVESWAPTXT) {
+ mode |= S_ISVTX;
+ prot.sticky = 1;
+ }
+
+ if (buf)
+ buf->ia_prot = prot;
+ /* Create fop still requires the old mode_t style argument. */
+ if (omode)
+ *omode = mode;
+ }
+
+ if (sattr->uid.set_it) {
+ valid |= GF_SET_ATTR_UID;
+ if (buf)
+ buf->ia_uid = sattr->uid.set_uid3_u.uid;
+ }
+
+ if (sattr->gid.set_it) {
+ valid |= GF_SET_ATTR_GID;
+ if (buf)
+ buf->ia_gid = sattr->gid.set_gid3_u.gid;
+ }
+
+ if (sattr->size.set_it) {
+ valid |= GF_SET_ATTR_SIZE;
+ if (buf)
+ buf->ia_size = sattr->size.set_size3_u.size;
+ }
+
+ if (sattr->atime.set_it == SET_TO_CLIENT_TIME) {
+ valid |= GF_SET_ATTR_ATIME;
+ if (buf)
+ buf->ia_atime = sattr->atime.set_atime_u.atime.seconds;
+ }
+
+ if (sattr->atime.set_it == SET_TO_SERVER_TIME) {
+ valid |= GF_SET_ATTR_ATIME;
+ if (buf)
+ buf->ia_atime = time (NULL);
+ }
+
+ if (sattr->mtime.set_it == SET_TO_CLIENT_TIME) {
+ valid |= GF_SET_ATTR_MTIME;
+ if (buf)
+ buf->ia_mtime = sattr->mtime.set_mtime_u.mtime.seconds;
+ }
+
+ if (sattr->mtime.set_it == SET_TO_SERVER_TIME) {
+ valid |= GF_SET_ATTR_MTIME;
+ if (buf)
+ buf->ia_mtime = time (NULL);
+ }
+
+ return valid;
+}
+
+
+wcc_data
+nfs3_stat_to_wcc_data (struct iatt *pre, struct iatt *post)
+{
+ wcc_data wd = {{0}, };
+
+ if (post)
+ wd.after = nfs3_stat_to_post_op_attr (post);
+ if (pre)
+ wd.before = nfs3_stat_to_pre_op_attr (pre);
+
+ return wd;
+}
+
+void
+nfs3_fill_create3res (create3res *res, nfsstat3 stat, struct nfs3_fh *newfh,
+ struct iatt *newbuf, struct iatt *preparent,
+ struct iatt *postparent)
+{
+ post_op_attr poa = {0, };
+ wcc_data dirwcc = {{0}, };
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_fill_post_op_fh3 (newfh, &res->create3res_u.resok.obj);
+ nfs3_map_xlid_to_statdev (newbuf, newfh->xlatorid);
+ poa = nfs3_stat_to_post_op_attr (newbuf);
+ res->create3res_u.resok.obj_attributes = poa;
+ if (preparent) {
+ nfs3_map_xlid_to_statdev (preparent, newfh->xlatorid);
+ nfs3_map_xlid_to_statdev (postparent, newfh->xlatorid);
+ dirwcc = nfs3_stat_to_wcc_data (preparent, postparent);
+ }
+
+ res->create3res_u.resok.dir_wcc = dirwcc;
+}
+
+void
+nfs3_prep_create3args (create3args *args, struct nfs3_fh *fh, char *name)
+{
+
+ memset (args, 0, sizeof (*args));
+ args->where.dir.data.data_val = (void *)fh;
+ args->where.name = name;
+}
+
+void
+nfs3_prep_setattr3args (setattr3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->object.data.data_val = (void *)fh;
+}
+
+
+void
+nfs3_fill_setattr3res (setattr3res *res, nfsstat3 stat, struct iatt *preop,
+ struct iatt *postop, uint16_t xlid)
+{
+ wcc_data wcc;
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (preop, xlid);
+ nfs3_map_xlid_to_statdev (postop, xlid);
+ wcc = nfs3_stat_to_wcc_data (preop, postop);
+ res->setattr3res_u.resok.obj_wcc = wcc;
+}
+
+
+void
+nfs3_prep_mkdir3args (mkdir3args *args, struct nfs3_fh *dirfh, char *name)
+{
+
+ memset (args, 0, sizeof (*args));
+ args->where.dir.data.data_val = (void *)dirfh;
+ args->where.name = name;
+}
+
+
+void
+nfs3_fill_mkdir3res (mkdir3res *res, nfsstat3 stat, struct nfs3_fh *fh,
+ struct iatt *buf, struct iatt *preparent,
+ struct iatt *postparent)
+{
+ wcc_data dirwcc;
+ post_op_attr poa;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_fill_post_op_fh3 (fh, &res->mkdir3res_u.resok.obj);
+ nfs3_map_xlid_to_statdev (buf, fh->xlatorid);
+ poa = nfs3_stat_to_post_op_attr (buf);
+ nfs3_map_xlid_to_statdev (preparent, fh->xlatorid);
+ nfs3_map_xlid_to_statdev (postparent, fh->xlatorid);
+ dirwcc = nfs3_stat_to_wcc_data (preparent, postparent);
+ res->mkdir3res_u.resok.obj_attributes = poa;
+ res->mkdir3res_u.resok.dir_wcc = dirwcc;
+
+}
+
+
+void
+nfs3_prep_symlink3args (symlink3args *args, struct nfs3_fh *dirfh, char *name,
+ char *target)
+{
+ memset (args, 0, sizeof (*args));
+ args->where.dir.data.data_val = (void *)dirfh;
+ args->where.name = name;
+ args->symlink.symlink_data = target;
+}
+
+
+void
+nfs3_fill_symlink3res (symlink3res *res, nfsstat3 stat, struct nfs3_fh *fh,
+ struct iatt *buf, struct iatt *preparent,
+ struct iatt *postparent)
+{
+ wcc_data dirwcc;
+ post_op_attr poa;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_fill_post_op_fh3 (fh, &res->symlink3res_u.resok.obj);
+ nfs3_map_xlid_to_statdev (buf, fh->xlatorid);
+ poa = nfs3_stat_to_post_op_attr (buf);
+ nfs3_map_xlid_to_statdev (postparent, fh->xlatorid);
+ nfs3_map_xlid_to_statdev (preparent, fh->xlatorid);
+ dirwcc = nfs3_stat_to_wcc_data (preparent, postparent);
+ res->symlink3res_u.resok.obj_attributes = poa;
+ res->symlink3res_u.resok.dir_wcc = dirwcc;
+
+}
+
+
+void
+nfs3_prep_readlink3args (readlink3args *args, struct nfs3_fh *fh)
+{
+
+ memset (args, 0, sizeof (*args));
+ args->symlink.data.data_val = (void *)fh;
+}
+
+
+void
+nfs3_fill_readlink3res (readlink3res *res, nfsstat3 stat, char *path,
+ struct iatt *buf, uint16_t xlid)
+{
+ post_op_attr poa;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (buf, xlid);
+ poa = nfs3_stat_to_post_op_attr (buf);
+ res->readlink3res_u.resok.data = (void *)path;
+ res->readlink3res_u.resok.symlink_attributes = poa;
+}
+
+
+void
+nfs3_prep_mknod3args (mknod3args *args, struct nfs3_fh *fh, char *name)
+{
+ memset (args, 0, sizeof (*args));
+ args->where.dir.data.data_val = (void *)fh;
+ args->where.name = name;
+
+}
+
+void
+nfs3_fill_mknod3res (mknod3res *res, nfsstat3 stat, struct nfs3_fh *fh,
+ struct iatt *buf, struct iatt *preparent,
+ struct iatt *postparent)
+{
+ post_op_attr poa;
+ wcc_data wccdir;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_fill_post_op_fh3 (fh, &res->mknod3res_u.resok.obj);
+ nfs3_map_xlid_to_statdev (buf, fh->xlatorid);
+ poa = nfs3_stat_to_post_op_attr (buf);
+ nfs3_map_xlid_to_statdev (preparent, fh->xlatorid);
+ nfs3_map_xlid_to_statdev (postparent, fh->xlatorid);
+ wccdir = nfs3_stat_to_wcc_data (preparent, postparent);
+ res->mknod3res_u.resok.obj_attributes = poa;
+ res->mknod3res_u.resok.dir_wcc = wccdir;
+
+}
+
+
+void
+nfs3_fill_remove3res (remove3res *res, nfsstat3 stat, struct iatt *preparent,
+ struct iatt *postparent, uint16_t xlid)
+{
+ wcc_data dirwcc;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (preparent, xlid);
+ nfs3_map_xlid_to_statdev (postparent, xlid);
+ dirwcc = nfs3_stat_to_wcc_data (preparent, postparent);
+ res->remove3res_u.resok.dir_wcc = dirwcc;
+}
+
+
+void
+nfs3_prep_remove3args (remove3args *args, struct nfs3_fh *fh, char *name)
+{
+ memset (args, 0, sizeof (*args));
+ args->object.dir.data.data_val = (void *)fh;
+ args->object.name = name;
+}
+
+
+void
+nfs3_prep_rmdir3args (rmdir3args *args, struct nfs3_fh *fh, char *name)
+{
+ memset (args, 0, sizeof (*args));
+ args->object.dir.data.data_val = (void *)fh;
+ args->object.name = name;
+}
+
+
+void
+nfs3_fill_rmdir3res (rmdir3res *res, nfsstat3 stat, struct iatt *preparent,
+ struct iatt *postparent, uint16_t xlid)
+{
+ wcc_data dirwcc;
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (postparent, xlid);
+ nfs3_map_xlid_to_statdev (preparent, xlid);
+ dirwcc = nfs3_stat_to_wcc_data (preparent, postparent);
+ res->rmdir3res_u.resok.dir_wcc = dirwcc;
+}
+
+
+void
+nfs3_prep_link3args (link3args *args, struct nfs3_fh *target,
+ struct nfs3_fh * dirfh, char *name)
+{
+ memset (args, 0, sizeof (*args));
+ args->file.data.data_val = (void *)target;
+ args->link.dir.data.data_val = (void *)dirfh;
+ args->link.name = name;
+}
+
+
+void
+nfs3_fill_link3res (link3res *res, nfsstat3 stat, struct iatt *buf,
+ struct iatt *preparent, struct iatt *postparent,
+ uint16_t xlid)
+{
+ post_op_attr poa;
+ wcc_data dirwcc;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (preparent, xlid);
+ nfs3_map_xlid_to_statdev (postparent, xlid);
+ nfs3_map_xlid_to_statdev (buf, xlid);
+ poa = nfs3_stat_to_post_op_attr (buf);
+ dirwcc = nfs3_stat_to_wcc_data (preparent, postparent);
+ res->link3res_u.resok.file_attributes = poa;
+ res->link3res_u.resok.linkdir_wcc = dirwcc;
+}
+
+
+
+void
+nfs3_prep_rename3args (rename3args *args, struct nfs3_fh *olddirfh,
+ char *oldname, struct nfs3_fh *newdirfh, char *newname)
+{
+ memset (args, 0, sizeof (*args));
+
+ args->from.name = oldname;
+ args->from.dir.data.data_val = (void *)olddirfh;
+ args->to.name = newname;
+ args->to.dir.data.data_val = (void *)newdirfh;
+
+}
+
+
+void
+nfs3_fill_rename3res (rename3res *res, nfsstat3 stat, struct iatt *buf,
+ struct iatt *preoldparent, struct iatt *postoldparent,
+ struct iatt *prenewparent, struct iatt *postnewparent,
+ uint16_t xlid)
+
+{
+ wcc_data dirwcc;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (preoldparent, xlid);
+ nfs3_map_xlid_to_statdev (postoldparent, xlid);
+ nfs3_map_xlid_to_statdev (prenewparent, xlid);
+ nfs3_map_xlid_to_statdev (postnewparent, xlid);
+ nfs3_map_xlid_to_statdev (buf, xlid);
+ dirwcc = nfs3_stat_to_wcc_data (preoldparent, postoldparent);
+ res->rename3res_u.resok.fromdir_wcc = dirwcc;
+ dirwcc = nfs3_stat_to_wcc_data (prenewparent, postnewparent);
+ res->rename3res_u.resok.todir_wcc = dirwcc;
+}
+
+
+void
+nfs3_prep_write3args (write3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->file.data.data_val = (void *)fh;
+}
+
+
+void
+nfs3_fill_write3res (write3res *res, nfsstat3 stat, count3 count,
+ stable_how stable, uint64_t wverf, struct iatt *prestat,
+ struct iatt *poststat, uint16_t xlid)
+{
+ write3resok resok;
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (prestat, xlid);
+ nfs3_map_xlid_to_statdev (poststat, xlid);
+ resok.file_wcc = nfs3_stat_to_wcc_data (prestat, poststat);
+ resok.count = count;
+ resok.committed = stable;
+ memcpy (resok.verf, &wverf, sizeof (wverf));
+
+ res->write3res_u.resok = resok;
+}
+
+
+void
+nfs3_prep_commit3args (commit3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->file.data.data_val = (void *)fh;
+}
+
+
+void
+nfs3_fill_commit3res (commit3res *res, nfsstat3 stat, uint64_t wverf,
+ struct iatt *prestat, struct iatt *poststat,uint16_t xlid)
+{
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (poststat, xlid);
+ nfs3_map_xlid_to_statdev (prestat, xlid);
+ res->commit3res_u.resok.file_wcc = nfs3_stat_to_wcc_data (prestat,
+ poststat);
+ memcpy (res->commit3res_u.resok.verf, &wverf, sizeof (wverf));
+}
+
+void
+nfs3_fill_read3res (read3res *res, nfsstat3 stat, count3 count,
+ struct iatt *poststat, int is_eof, uint16_t xlid)
+{
+ post_op_attr poa;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (poststat, xlid);
+ poa = nfs3_stat_to_post_op_attr (poststat);
+ res->read3res_u.resok.file_attributes = poa;
+ res->read3res_u.resok.count = count;
+ res->read3res_u.resok.eof = is_eof;
+ res->read3res_u.resok.data.data_len = count;
+
+}
+
+
+void
+nfs3_prep_read3args (read3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->file.data.data_val = (void *)fh;
+}
+
+
+void
+nfs3_fill_pathconf3res (pathconf3res *res, nfsstat3 stat, struct iatt *buf,
+ uint16_t xlid)
+{
+ pathconf3resok resok;
+
+ memset (res, 0, sizeof (*res));
+ res->status = stat;
+ if (stat != NFS3_OK)
+ return;
+
+ nfs3_map_xlid_to_statdev (buf, xlid);
+ resok.obj_attributes = nfs3_stat_to_post_op_attr (buf);
+ resok.linkmax = 256;
+ resok.name_max = NFS_NAME_MAX;
+ resok.no_trunc = TRUE;
+ resok.chown_restricted = FALSE;
+ resok.case_insensitive = FALSE;
+ resok.case_preserving = TRUE;
+
+ res->pathconf3res_u.resok = resok;
+}
+
+
+void
+nfs3_prep_pathconf3args (pathconf3args *args, struct nfs3_fh *fh)
+{
+ memset (args, 0, sizeof (*args));
+ args->object.data.data_val = (void *)fh;
+}
+
+
+int
+nfs3_verify_dircookie (struct nfs3_state *nfs3, fd_t *dirfd, cookie3 cookie,
+ uint64_t cverf, nfsstat3 *stat)
+{
+ int ret = -1;
+
+ if ((!nfs3) || (!dirfd))
+ return -1;
+
+ /* Can assume that this is first read on the dir, so cookie check
+ * is successful by default.
+ */
+ if (cookie == 0)
+ return 0;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Verifying cookie: cverf: %"PRIu64
+ ", cookie: %"PRIu64, cverf, cookie);
+ /* The cookie bad, no way cverf will be zero with a non-zero cookie. */
+ if ((cverf == 0) && (cookie != 0)) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Bad cookie requested");
+ if (stat)
+ *stat = NFS3ERR_BAD_COOKIE;
+ goto err;
+ }
+
+ /* Yes, its true, our cookie is simply the fd_t address.
+ * NOTE: We used have the check for cookieverf but VMWare client sends
+ * a readdirp requests even after we've told it that EOF has been
+ * reached on the directory. This causes a problem because we close a
+ * dir fd_t after reaching EOF. The next readdirp sent by VMWare
+ * contains the address of the closed fd_t as cookieverf. Since we
+ * closed that fd_t, this readdirp results in a new opendir which will
+ * give an fd_t that will fail this check below.
+ */
+/* if ((cverf != (uint64_t)dirfd)) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Cookieverf does not match");
+ if (stat)
+ *stat = NFS3ERR_BAD_COOKIE;
+ goto err;
+ }
+*/
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Cookie verified");
+ if (stat)
+ *stat = NFS3_OK;
+ ret = 0;
+err:
+ return ret;
+}
+
+
+/* When remove a file, we need to unref the cached fd for an inode but this
+ * needs to happen only when the file was in fact opened. However, it is
+ * possible that fd_lookup on a file returns an fd because the file was in
+ * process of being created(which also returns an fd) but since this fd was not
+ * opened through this path, in the NFS3 remove path, we'll end up removing the
+ * reference that belongs to someone else. That means, nfs3 remove path should
+ * not unref unless it is sure that the file was cached open also. If it was,
+ * only then perform the fd_unref, else not.
+ *
+ * We determine that using a flag in the inode context.
+ */
+int
+nfs3_set_inode_opened (xlator_t *nfsxl, inode_t *inode)
+{
+ if ((!nfsxl) || (!inode))
+ return -1;
+
+ inode_ctx_put (inode, nfsxl, GF_NFS3_FD_CACHED);
+
+ return 0;
+}
+
+
+/* Returns 1 if inode was cached open, otherwise 0 */
+int
+nfs3_cached_inode_opened (xlator_t *nfsxl, inode_t *inode)
+{
+ int ret = -1;
+ uint64_t cflag = 0;
+
+ if ((!nfsxl) || (!inode))
+ return -1;
+
+ ret = inode_ctx_get (inode, nfsxl, &cflag);
+ if (ret == -1)
+ ret = 0;
+ else if (cflag == GF_NFS3_FD_CACHED)
+ ret = 1;
+
+ return ret;
+}
+
+
+int32_t
+nfs3_dir_open_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
+ int32_t op_ret, int32_t op_errno, fd_t *fd)
+{
+ nfs3_call_state_t *cs = NULL;
+
+ cs = frame->local;
+ if (op_ret == -1) {
+ cs->resolve_ret = -1;
+ cs->resolve_errno = op_errno;
+ nfs3_call_resume (cs);
+ goto err;
+ }
+
+ cs->fd = fd_ref (fd);
+ nfs3_set_inode_opened (cs->nfsx, cs->resolvedloc.inode);
+ gf_log (GF_NFS3, GF_LOG_TRACE, "FD_REF: %d", fd->refcount);
+ nfs3_call_resume (cs);
+err:
+ return 0;
+}
+
+
+int
+__nfs3_dir_open_and_resume (nfs3_call_state_t *cs)
+{
+ nfs_user_t nfu = {0, };
+ int ret = -EFAULT;
+
+ if (!cs)
+ return ret;
+
+ nfs_user_root_create (&nfu);
+ ret = nfs_opendir (cs->vol, &nfu, &cs->resolvedloc, nfs3_dir_open_cbk,
+ cs);
+ return ret;
+}
+
+
+int
+nfs3_dir_open_and_resume (nfs3_call_state_t *cs, nfs3_resume_fn_t resume)
+{
+ fd_t *fd = NULL;
+ int ret = -EFAULT;
+
+ if ((!cs))
+ return ret;
+
+ cs->resume_fn = resume;
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Opening: %s", cs->resolvedloc.path);
+ fd = fd_lookup (cs->resolvedloc.inode, 0);
+ if (fd) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "fd found in state: ref: %d", fd->refcount);
+ cs->fd = fd_ref (fd); /* Gets unrefd when the call state is wiped. */
+ cs->resolve_ret = 0;
+ nfs3_call_resume (cs);
+ ret = 0;
+ goto err;
+ }
+
+ ret = __nfs3_dir_open_and_resume (cs);
+
+err:
+ return ret;
+}
+
+
+int
+nfs3_flush_open_wait_call_states (nfs3_call_state_t *cs, fd_t *openedfd)
+{
+ struct list_head *inode_q = NULL;
+ uint64_t ctxaddr = 0;
+ int ret = 0;
+ nfs3_call_state_t *cstmp = NULL;
+
+ if (!cs)
+ return -1;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Flushing call state");
+ ret = inode_ctx_get (cs->resolvedloc.inode, cs->nfsx, &ctxaddr);
+ if (ret == -1) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "No inode queue present");
+ goto out;
+ }
+
+ inode_q = (struct list_head *)(long)ctxaddr;
+ if (!inode_q)
+ goto out;
+
+ list_for_each_entry_safe (cs, cstmp, inode_q, openwait_q) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Calling resume");
+ cs->resolve_ret = 0;
+ if (openedfd) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Opening uncached fd done: %d",
+ openedfd->refcount);
+ cs->fd = fd_ref (openedfd);
+ }
+ nfs3_call_resume (cs);
+ }
+
+out:
+ return 0;
+}
+
+
+
+int
+__nfs3_fdcache_update_entry (struct nfs3_state *nfs3, fd_t *fd)
+{
+ uint64_t ctxaddr = 0;
+ struct nfs3_fd_entry *fde = NULL;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Updating fd: 0x%lx", (long int)fd);
+ fd_ctx_get (fd, nfs3->nfsx, &ctxaddr);
+ fde = (struct nfs3_fd_entry *)(long)ctxaddr;
+ list_del (&fde->list);
+ list_add_tail (&fde->list, &nfs3->fdlru);
+
+ return 0;
+}
+
+
+int
+nfs3_fdcache_update (struct nfs3_state *nfs3, fd_t *fd)
+{
+ if ((!nfs3) || (!fd))
+ return -1;
+
+ LOCK (&nfs3->fdlrulock);
+ {
+ __nfs3_fdcache_update_entry (nfs3, fd);
+ }
+ UNLOCK (&nfs3->fdlrulock);
+
+ return 0;
+}
+
+
+int
+__nfs3_fdcache_remove_entry (struct nfs3_state *nfs3, struct nfs3_fd_entry *fde)
+{
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Removing fd: 0x%lx: %d",
+ (long int)fde->cachedfd, fde->cachedfd->refcount);
+ list_del (&fde->list);
+ fd_ctx_del (fde->cachedfd, nfs3->nfsx, NULL);
+ fd_unref (fde->cachedfd);
+ FREE (fde);
+ --nfs3->fdcount;
+
+ return 0;
+}
+
+
+int
+nfs3_fdcache_remove (struct nfs3_state *nfs3, fd_t *fd)
+{
+ struct nfs3_fd_entry *fde = NULL;
+ uint64_t ctxaddr = 0;
+
+ if ((!nfs3) || (!fd))
+ return -1;
+
+ LOCK (&nfs3->fdlrulock);
+ {
+ fd_ctx_get (fd, nfs3->nfsx, &ctxaddr);
+ fde = (struct nfs3_fd_entry *)(long)ctxaddr;
+ __nfs3_fdcache_remove_entry (nfs3, fde);
+ }
+ UNLOCK (&nfs3->fdlrulock);
+
+ return 0;
+}
+
+
+int
+__nfs3_fdcache_replace (struct nfs3_state *nfs3)
+{
+ struct nfs3_fd_entry *fde = NULL;
+ struct nfs3_fd_entry *tmp = NULL;
+
+ if (!nfs3)
+ return -1;
+
+ if (nfs3->fdcount <= GF_NFS3_FDCACHE_SIZE)
+ return 0;
+
+ list_for_each_entry_safe (fde, tmp, &nfs3->fdlru, list)
+ break;
+
+ __nfs3_fdcache_remove_entry (nfs3, fde);
+
+ return 0;
+}
+
+
+
+int
+nfs3_fdcache_add (struct nfs3_state *nfs3, fd_t *fd)
+{
+ struct nfs3_fd_entry *fde = NULL;
+ int ret = -1;
+
+ if ((!nfs3) || (!fd))
+ return -1;
+
+ fde = CALLOC (1, sizeof (*fd));
+ if (!fde) {
+ gf_log (GF_NFS3, GF_LOG_ERROR, "fd entry allocation failed");
+ goto out;
+ }
+
+ /* Already refd by caller. */
+ fde->cachedfd = fd;
+ INIT_LIST_HEAD (&fde->list);
+
+ LOCK (&nfs3->fdlrulock);
+ {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Adding fd: 0x%lx",
+ (long int) fd);
+ fd_ctx_set (fd, nfs3->nfsx, (uint64_t)fde);
+ fd_bind (fd);
+ list_add_tail (&fde->list, &nfs3->fdlru);
+ ++nfs3->fdcount;
+ __nfs3_fdcache_replace (nfs3);
+ }
+ UNLOCK (&nfs3->fdlrulock);
+
+out:
+ return ret;
+}
+
+
+int32_t
+nfs3_file_open_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
+ int32_t op_ret, int32_t op_errno, fd_t *fd)
+{
+ nfs3_call_state_t *cs = NULL;
+ struct nfs3_state *nfs3 = NULL;
+
+ cs = frame->local;
+ if (op_ret == -1) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Opening uncached fd failed");
+ cs->resolve_ret = -1;
+ cs->resolve_errno = op_errno;
+ fd = NULL;
+ } else {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Opening uncached fd done: %d",
+ fd->refcount);
+ }
+
+ nfs3 = rpcsvc_request_program_private (cs->req);
+ nfs3_flush_open_wait_call_states (cs, fd);
+ nfs3_fdcache_add (nfs3, fd);
+ return 0;
+}
+
+/* Returns 1 if the current call is the first one to be queued. If so, the
+ * caller will need to send the open fop. If this is a non-first call to be
+ * queued, it means the fd opening is in progress.
+ *
+ * Returns 0, if this is a non-first call.
+ */
+int
+__nfs3_queue_call_state (nfs3_call_state_t *cs)
+{
+ struct list_head *inode_q = NULL;
+ int ret = -1;
+ uint64_t ctxaddr = 0;
+
+ ret = __inode_ctx_get (cs->resolvedloc.inode, cs->nfsx, &ctxaddr);
+ if (ret == 0) {
+ inode_q = (struct list_head *)(long)ctxaddr;
+ goto attach_cs;
+ }
+
+ inode_q = CALLOC (1, sizeof (*inode_q));
+ if (!inode_q)
+ goto err;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Initing inode queue");
+ INIT_LIST_HEAD (inode_q);
+ __inode_ctx_put (cs->resolvedloc.inode, cs->nfsx, (uint64_t)inode_q);
+
+attach_cs:
+ if (list_empty (inode_q)) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "First call in queue");
+ ret = 1;
+ } else
+ ret = 0;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Queueing call state");
+ list_add_tail (&cs->openwait_q, inode_q);
+
+err:
+ return ret;
+}
+
+
+int
+nfs3_queue_call_state (nfs3_call_state_t *cs)
+{
+ int ret = 0;
+ if (!cs)
+ return -1;
+
+ LOCK (&cs->resolvedloc.inode->lock);
+ {
+ ret = __nfs3_queue_call_state (cs);
+ }
+ UNLOCK (&cs->resolvedloc.inode->lock);
+
+ return ret;
+}
+
+
+int
+__nfs3_file_open_and_resume (nfs3_call_state_t *cs)
+{
+ nfs_user_t nfu = {0, };
+ int ret = -EFAULT;
+
+ if (!cs)
+ return ret;
+
+ ret = nfs3_queue_call_state (cs);
+ if (ret != 1) {
+ ret = 0;
+ goto out;
+ }
+
+ nfs_user_root_create (&nfu);
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Opening uncached fd");
+ ret = nfs_open (cs->vol, &nfu, &cs->resolvedloc, O_RDWR,
+ nfs3_file_open_cbk, cs);
+out:
+ return ret;
+}
+
+
+
+int
+nfs3_file_open_and_resume (nfs3_call_state_t *cs, nfs3_resume_fn_t resume)
+{
+ fd_t *fd = NULL;
+ int ret = -EFAULT;
+ struct nfs3_state *nfs3 = NULL;
+
+ if ((!cs))
+ return ret;
+
+ cs->resume_fn = resume;
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Opening: %s", cs->resolvedloc.path);
+ fd = fd_lookup (cs->resolvedloc.inode, 0);
+ if (fd) {
+ nfs3 = rpcsvc_request_program_private (cs->req);
+ /* Already refd by fd_lookup, so no need to ref again. */
+ gf_log (GF_NFS3, GF_LOG_TRACE, "fd found in state: %d",
+ fd->refcount);
+ nfs3_fdcache_update (nfs3, fd);
+ cs->fd = fd; /* Gets unrefd when the call state is wiped. */
+ cs->resolve_ret = 0;
+ nfs3_call_resume (cs);
+ ret = 0;
+ goto err;
+ }
+
+ ret = __nfs3_file_open_and_resume (cs);
+
+err:
+ return ret;
+}
+
+
+void
+nfs3_stat_to_errstr (uint32_t xid, char *op, nfsstat3 stat, int pstat,
+ char *errstr)
+{
+ if ((!op) || (!errstr))
+ return;
+
+ sprintf (errstr, "XID: %x, %s: NFS: %d(%s), POSIX: %d(%s)", xid, op,
+ stat, nfsstat3_strerror (stat), pstat, strerror (pstat));
+}
+
+void
+nfs3_log_common_call (uint32_t xid, char *op, struct nfs3_fh *fh)
+{
+ char fhstr[1024];
+
+ nfs3_fh_to_str (fh, fhstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, %s: args: %s", xid, op,
+ fhstr);
+}
+
+
+void
+nfs3_log_fh_entry_call (uint32_t xid, char *op, struct nfs3_fh *fh,
+ char *name)
+{
+ char fhstr[1024];
+
+ nfs3_fh_to_str (fh, fhstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, %s: args: %s, name: %s", xid,
+ op, fhstr, name);
+}
+
+
+void
+nfs3_log_rename_call (uint32_t xid, struct nfs3_fh *src, char *sname,
+ struct nfs3_fh *dst, char *dname)
+{
+ char sfhstr[1024];
+ char dfhstr[1024];
+
+ nfs3_fh_to_str (src, sfhstr);
+ nfs3_fh_to_str (dst, dfhstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, RENAME: args: Src: %s, "
+ "name: %s, Dst: %s, name: %s", xid, sfhstr, sname, dfhstr,
+ dname);
+}
+
+
+
+void
+nfs3_log_create_call (uint32_t xid, struct nfs3_fh *fh, char *name,
+ createmode3 mode)
+{
+ char fhstr[1024];
+ char *modestr = NULL;
+ char exclmode[] = "EXCLUSIVE";
+ char unchkd[] = "UNCHECKED";
+ char guarded[] = "GUARDED";
+
+ nfs3_fh_to_str (fh, fhstr);
+ if (mode == EXCLUSIVE)
+ modestr = exclmode;
+ else if (mode == GUARDED)
+ modestr = guarded;
+ else
+ modestr = unchkd;
+
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, CREATE: args: %s, name: %s,"
+ " mode: %s", xid, fhstr, name, modestr);
+}
+
+
+void
+nfs3_log_mknod_call (uint32_t xid, struct nfs3_fh *fh, char *name, int type)
+{
+ char fhstr[1024];
+ char *modestr = NULL;
+ char chr[] = "CHAR";
+ char blk[] = "BLK";
+ char sock[] = "SOCK";
+ char fifo[] = "FIFO";
+
+ nfs3_fh_to_str (fh, fhstr);
+ if (type == NF3CHR)
+ modestr = chr;
+ else if (type == NF3BLK)
+ modestr = blk;
+ else if (type == NF3SOCK)
+ modestr = sock;
+ else
+ modestr = fifo;
+
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, MKNOD: args: %s, name: %s,"
+ " type: %s", xid, fhstr, name, modestr);
+}
+
+
+
+void
+nfs3_log_symlink_call (uint32_t xid, struct nfs3_fh *fh, char *name, char *tgt)
+{
+ char fhstr[1024];
+
+ nfs3_fh_to_str (fh, fhstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, SYMLINK: args: %s, name: %s,"
+ " target: %s", xid, fhstr, name, tgt);
+}
+
+
+void
+nfs3_log_link_call (uint32_t xid, struct nfs3_fh *fh, char *name,
+ struct nfs3_fh *tgt)
+{
+ char dfhstr[1024];
+ char tfhstr[1024];
+
+ nfs3_fh_to_str (fh, dfhstr);
+ nfs3_fh_to_str (tgt, tfhstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, LINK: args: %s, name: %s,"
+ " target: %s", xid, dfhstr, name, tfhstr);
+}
+
+
+void
+nfs3_log_rw_call (uint32_t xid, char *op, struct nfs3_fh *fh, offset3 offt,
+ count3 count, int stablewrite)
+{
+ char fhstr[1024];
+
+ nfs3_fh_to_str (fh, fhstr);
+ if (stablewrite == -1)
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, %s: args: %s, offset:"
+ " %"PRIu64", count: %"PRIu64, xid, op, fhstr, offt,
+ count);
+ else
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, %s: args: %s, offset:"
+ " %"PRIu64", count: %"PRIu64", %s", xid, op, fhstr,
+ offt, count,
+ (stablewrite == UNSTABLE)?"UNSTABLE":"STABLE");
+
+}
+
+
+void
+nfs3_log_common_res (uint32_t xid, char *op, nfsstat3 stat, int pstat)
+{
+ char errstr[1024];
+
+ nfs3_stat_to_errstr (xid, op, stat, pstat, errstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s", errstr);
+}
+
+void
+nfs3_log_readlink_res (uint32_t xid, nfsstat3 stat, int pstat, char *linkpath)
+{
+ char errstr[1024];
+
+ nfs3_stat_to_errstr (xid, "READLINK", stat, pstat, errstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s, target: %s", errstr, linkpath);
+
+}
+
+void
+nfs3_log_read_res (uint32_t xid, nfsstat3 stat, int pstat, count3 count,
+ int is_eof)
+{
+ char errstr[1024];
+
+ nfs3_stat_to_errstr (xid, "READ", stat, pstat, errstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s, count: %"PRIu64", is_eof: %d",
+ errstr, count, is_eof);
+}
+
+
+void
+nfs3_log_write_res (uint32_t xid, nfsstat3 stat, int pstat, count3 count,
+ int stable, uint64_t wverf)
+{
+ char errstr[1024];
+
+ nfs3_stat_to_errstr (xid, "WRITE", stat, pstat, errstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s, count: %"PRIu64", %s,wverf: %"PRIu64
+ , errstr, count, (stable == UNSTABLE)?"UNSTABLE":"STABLE",
+ wverf);
+}
+
+
+void
+nfs3_log_newfh_res (uint32_t xid, char *op, nfsstat3 stat, int pstat,
+ struct nfs3_fh *newfh)
+{
+ char errstr[1024];
+ char fhstr[1024];
+
+ nfs3_stat_to_errstr (xid, op, stat, pstat, errstr);
+ nfs3_fh_to_str (newfh, fhstr);
+
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s, %s", errstr, fhstr);
+}
+
+
+void
+nfs3_log_readdir_res (uint32_t xid, nfsstat3 stat, int pstat, uint64_t cverf,
+ count3 count, int is_eof)
+{
+ char errstr[1024];
+
+ nfs3_stat_to_errstr (xid, "READDIR", stat, pstat, errstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s, count: %"PRIu64", cverf: %"PRIu64
+ ", is_eof: %d", errstr, count, cverf, is_eof);
+}
+
+
+void
+nfs3_log_readdirp_res (uint32_t xid, nfsstat3 stat, int pstat, uint64_t cverf,
+ count3 dircount, count3 maxcount, int is_eof)
+{
+ char errstr[1024];
+
+ nfs3_stat_to_errstr (xid, "READDIRPLUS", stat, pstat, errstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s, dircount: %"PRIu64", maxcount: %"
+ PRIu64", cverf: %"PRIu64", is_eof: %d", errstr, dircount,
+ maxcount, cverf, is_eof);
+}
+
+
+void
+nfs3_log_commit_res (uint32_t xid, nfsstat3 stat, int pstat, uint64_t wverf)
+{
+ char errstr[1024];
+
+ nfs3_stat_to_errstr (xid, "COMMIT", stat, pstat, errstr);
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "%s, wverf: %"PRIu64, errstr, wverf);
+}
+
+
+void
+nfs3_log_readdir_call (uint32_t xid, struct nfs3_fh *fh, count3 dircount,
+ count3 maxcount)
+{
+ char fhstr[1024];
+
+ nfs3_fh_to_str (fh, fhstr);
+
+ if (maxcount == 0)
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, READDIR: args: %s,"
+ " count: %d", xid, fhstr, (uint32_t)dircount);
+ else
+ gf_log (GF_NFS3, GF_LOG_DEBUG, "XID: %x, READDIRPLUS: args: %s,"
+ " dircount: %d, maxcount: %d", xid, fhstr,
+ (uint32_t)dircount, (uint32_t)maxcount);
+}
+
+
+int
+nfs3_fh_resolve_inode_done (nfs3_call_state_t *cs, inode_t *inode)
+{
+ int ret = -EFAULT;
+
+ if ((!cs) || (!inode))
+ return ret;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "FH inode resolved");
+ ret = nfs_inode_loc_fill (inode, &cs->resolvedloc);
+ if (ret < 0)
+ goto err;
+
+ nfs3_call_resume (cs);
+
+err:
+ return ret;
+}
+
+#define GF_NFS3_FHRESOLVE_FOUND 1
+#define GF_NFS3_FHRESOLVE_NOTFOUND 2
+#define GF_NFS3_FHRESOLVE_DIRFOUND 3
+
+int
+nfs3_fh_resolve_check_entry (struct nfs3_fh *fh, gf_dirent_t *candidate,
+ int hashidx)
+{
+ struct iatt *ia = NULL;
+ int ret = GF_NFS3_FHRESOLVE_NOTFOUND;
+ nfs3_hash_entry_t entryhash = 0;
+
+ if ((!fh) || (!candidate))
+ return ret;
+
+ if ((strcmp (candidate->d_name, ".") == 0) ||
+ (strcmp (candidate->d_name, "..") == 0))
+ goto found_entry;
+
+ ia = &candidate->d_stat;
+ if ((ia->ia_gen == fh->gen) && (ia->ia_ino == fh->ino)) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Found entry: gen: %"PRId64
+ " ino: %"PRId64", name: %s", ia->ia_gen, ia->ia_ino,
+ candidate->d_name);
+ ret = GF_NFS3_FHRESOLVE_FOUND;
+ goto found_entry;
+ }
+
+ /* This condition ensures that we never have to be afraid of having
+ * a directory hash conflict with a file hash. The consequence of
+ * this condition is that we can now have unlimited files in a directory
+ * and upto 65536 sub-directories in a directory.
+ */
+ if (!IA_ISDIR (candidate->d_stat.ia_type))
+ goto found_entry;
+ entryhash = fh->entryhash[hashidx];
+ if (entryhash == nfs3_fh_hash_entry (ia->ia_ino, ia->ia_gen)) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Found hash match: %s: %d",
+ candidate->d_name, entryhash);
+ ret = GF_NFS3_FHRESOLVE_DIRFOUND;
+ goto found_entry;
+ }
+
+found_entry:
+
+ return ret;
+}
+
+
+int32_t
+nfs3_fh_resolve_entry_lookup_cbk (call_frame_t *frame, void *cookie,
+ xlator_t *this, int32_t op_ret,
+ int32_t op_errno, inode_t *inode,
+ struct iatt *buf, dict_t *xattr,
+ struct iatt *postparent)
+{
+ nfs3_call_state_t *cs = NULL;
+
+ cs = frame->local;
+ cs->resolve_ret = op_ret;
+ cs->resolve_errno = op_errno;
+
+ if (op_ret == -1) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Lookup failed: %s: %s",
+ cs->resolvedloc.path, strerror (op_errno));
+ goto err;
+ } else
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry looked up: %s",
+ cs->resolvedloc.path);
+
+ inode_link (inode, cs->resolvedloc.parent, cs->resolvedloc.name, buf);
+err:
+ nfs3_call_resume (cs);
+ return 0;
+}
+
+
+
+int32_t
+nfs3_fh_resolve_readdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
+ int32_t op_ret, int32_t op_errno,
+ gf_dirent_t *entries);
+
+int
+nfs3_fh_resolve_found_entry (nfs3_call_state_t *cs, gf_dirent_t *candidate)
+{
+ uint64_t dirino = 0;
+ uint64_t dirgen = 0;
+ int ret = 0;
+ nfs_user_t nfu = {0, };
+
+ if ((!cs) || (!candidate))
+ return -EFAULT;
+
+ dirino = cs->resolvedloc.inode->ino;
+ dirgen = cs->resolvedloc.inode->generation;
+
+ nfs_loc_wipe (&cs->resolvedloc);
+ ret = nfs_entry_loc_fill (cs->vol->itable, dirino, dirgen,
+ candidate->d_name, &cs->resolvedloc,
+ NFS_RESOLVE_CREATE);
+ if (ret == -ENOENT) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry not in itable, needs"
+ " lookup");
+ nfs_user_root_create (&nfu);
+ ret = nfs_fop_lookup (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_entry_lookup_cbk,
+ cs);
+ } else {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry got from itable");
+ nfs3_call_resume (cs);
+ }
+
+ return ret;
+}
+
+
+int32_t
+nfs3_fh_resolve_parent_lookup_cbk (call_frame_t *frame, void *cookie,
+ xlator_t *this, int32_t op_ret,
+ int32_t op_errno, inode_t *inode,
+ struct iatt *buf, dict_t *xattr,
+ struct iatt *postparent)
+{
+ nfs3_call_state_t *cs = NULL;
+
+ cs = frame->local;
+ cs->resolve_ret = op_ret;
+ cs->resolve_errno = op_errno;
+
+ if (op_ret == -1) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Lookup failed: %s: %s",
+ cs->resolvedloc.path, strerror (op_errno));
+ nfs3_call_resume (cs);
+ goto err;
+ } else
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry looked up: %s",
+ cs->resolvedloc.path);
+
+ inode_link (inode, cs->resolvedloc.parent, cs->resolvedloc.name, buf);
+ nfs3_fh_resolve_entry_hard (cs);
+
+err:
+ return 0;
+}
+
+
+int
+nfs3_fh_resolve_found_parent (nfs3_call_state_t *cs, gf_dirent_t *candidate)
+{
+ uint64_t dirino = 0;
+ uint64_t dirgen = 0;
+ int ret = 0;
+ nfs_user_t nfu = {0, };
+
+ if ((!cs) || (!candidate))
+ return -EFAULT;
+
+ dirino = cs->resolvedloc.inode->ino;
+ dirgen = cs->resolvedloc.inode->generation;
+
+ nfs_loc_wipe (&cs->resolvedloc);
+ ret = nfs_entry_loc_fill (cs->vol->itable, dirino, dirgen,
+ candidate->d_name, &cs->resolvedloc,
+ NFS_RESOLVE_CREATE);
+ if (ret == -ENOENT) {
+ nfs_user_root_create (&nfu);
+ ret = nfs_fop_lookup (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_parent_lookup_cbk,
+ cs);
+ } else
+ nfs3_fh_resolve_entry_hard (cs);
+
+ return ret;
+}
+
+
+int
+nfs3_fh_resolve_found (nfs3_call_state_t *cs, gf_dirent_t *candidate)
+{
+ int ret = 0;
+
+ if ((!cs) || (!candidate))
+ return -EFAULT;
+
+ if (!cs->resolventry) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Candidate entry was found");
+ ret = nfs3_fh_resolve_found_entry (cs, candidate);
+ } else {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry's parent was found");
+ ret = nfs3_fh_resolve_found_parent (cs, candidate);
+ }
+
+ return ret;
+}
+
+
+int32_t
+nfs3_fh_resolve_opendir_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
+ int32_t op_ret, int32_t op_errno, fd_t *fd)
+{
+ nfs3_call_state_t *cs = NULL;
+ int ret = -EFAULT;
+ nfs_user_t nfu = {0, };
+
+ cs = frame->local;
+ cs->resolve_ret = op_ret;
+ cs->resolve_errno = op_errno;
+
+ if (op_ret == -1) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Dir open failed: %s: %s",
+ cs->resolvedloc.path, strerror (op_errno));
+ nfs3_call_resume (cs);
+ goto err;
+ } else
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Reading directory: %s",
+ cs->resolvedloc.path);
+
+ nfs_user_root_create (&nfu);
+ /* This function can be called in a recursive code path, so if another
+ * directory was opened in an earlier call, we must unref through this
+ * reference before opening another fd_t.
+ */
+ if (cs->resolve_dir_fd)
+ fd_unref (cs->resolve_dir_fd);
+
+ cs->resolve_dir_fd = fd_ref (fd);
+ ret = nfs_readdirp (cs->vol, &nfu, fd, GF_NFS3_DTPREF, 0,
+ nfs3_fh_resolve_readdir_cbk, cs);
+
+err:
+ return ret;
+}
+
+int32_t
+nfs3_fh_resolve_dir_lookup_cbk (call_frame_t *frame, void *cookie,
+ xlator_t *this, int32_t op_ret,int32_t op_errno,
+ inode_t *inode, struct iatt *buf, dict_t *xattr,
+ struct iatt *postparent)
+{
+ nfs3_call_state_t *cs = NULL;
+ nfs_user_t nfu = {0, };
+
+ cs = frame->local;
+ cs->resolve_ret = op_ret;
+ cs->resolve_errno = op_errno;
+
+ if (op_ret == -1) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Lookup failed: %s: %s",
+ cs->resolvedloc.path, strerror (op_errno));
+ nfs3_call_resume (cs);
+ goto err;
+ } else
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Dir will be opened: %s",
+ cs->resolvedloc.path);
+
+ nfs_user_root_create (&nfu);
+ inode_link (inode, cs->resolvedloc.parent, cs->resolvedloc.name, buf);
+ nfs_opendir (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_opendir_cbk, cs);
+
+err:
+ return 0;
+}
+
+
+
+int
+nfs3_fh_resolve_dir_hard (nfs3_call_state_t *cs, uint64_t ino, uint64_t gen,
+ char *entry)
+{
+ int ret = -EFAULT;
+ nfs_user_t nfu = {0, };
+
+ if (!cs)
+ return ret;
+
+ cs->hashidx++;
+ nfs_loc_wipe (&cs->resolvedloc);
+ if (nfs3_fh_hash_index_is_beyond (&cs->resolvefh, cs->hashidx)) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Hash index is beyond: idx %d, "
+ " fh idx: %d", cs->hashidx, cs->resolvefh.hashcount);
+ nfs3_call_resume_estale (cs);
+ ret = 0;
+ goto out;
+ }
+
+ nfs_user_root_create (&nfu);
+ gf_log (GF_NFS3, GF_LOG_TRACE, "FH hard dir resolution: ino:"
+ " %"PRIu64", gen: %"PRIu64", entry: %s, hashidx: %d",
+ ino, gen, entry, cs->hashidx);
+ ret = nfs_entry_loc_fill (cs->vol->itable, ino, gen, entry,
+ &cs->resolvedloc, NFS_RESOLVE_CREATE);
+
+ if (ret == 0) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Dir will be opened: %s",
+ cs->resolvedloc.path);
+ ret = nfs_opendir (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_opendir_cbk, cs);
+ } else if (ret == -ENOENT) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Dir needs lookup: %s",
+ cs->resolvedloc.path);
+ ret = nfs_fop_lookup (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_dir_lookup_cbk, cs);
+ }
+out:
+ return ret;
+}
+
+
+int
+nfs3_fh_resolve_check_response (nfs3_call_state_t *cs, gf_dirent_t *candidate,
+ int response, off_t last_offt)
+{
+ uint64_t dirino = 0;
+ uint64_t dirgen = 0;
+ int ret = -EFAULT;
+ nfs_user_t nfu = {0, };
+
+ if (!cs)
+ return ret;
+
+ dirino = cs->resolvedloc.inode->ino;
+ dirgen = cs->resolvedloc.inode->generation;
+
+ if (response == GF_NFS3_FHRESOLVE_DIRFOUND)
+ ret = nfs3_fh_resolve_dir_hard (cs, dirino, dirgen,
+ candidate->d_name);
+ else if (response == GF_NFS3_FHRESOLVE_FOUND)
+ nfs3_fh_resolve_found (cs, candidate);
+ else if (response == GF_NFS3_FHRESOLVE_NOTFOUND) {
+ nfs_user_root_create (&nfu);
+ ret = nfs_readdirp (cs->vol, &nfu, cs->resolve_dir_fd,
+ GF_NFS3_DTPREF, last_offt,
+ nfs3_fh_resolve_readdir_cbk, cs);
+ }
+
+ return 0;
+}
+
+int
+nfs3_fh_resolve_search_dir (nfs3_call_state_t *cs, gf_dirent_t *entries)
+{
+ gf_dirent_t *candidate = NULL;
+ int ret = GF_NFS3_FHRESOLVE_NOTFOUND;
+ off_t lastoff = 0;
+
+ if ((!cs) || (!entries))
+ return -EFAULT;
+
+ if (list_empty (&entries->list))
+ goto not_found;
+
+ list_for_each_entry (candidate, &entries->list, list) {
+ lastoff = candidate->d_off;
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Candidate: %s, ino: %"PRIu64
+ ", gen: %"PRIu64, candidate->d_name, candidate->d_ino,
+ candidate->d_stat.ia_gen);
+ ret = nfs3_fh_resolve_check_entry (&cs->resolvefh, candidate,
+ cs->hashidx);
+ if (ret != GF_NFS3_FHRESOLVE_NOTFOUND)
+ break;
+ }
+
+not_found:
+ nfs3_fh_resolve_check_response (cs, candidate, ret, lastoff);
+ return ret;
+}
+
+
+int32_t
+nfs3_fh_resolve_readdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
+ int32_t op_ret, int32_t op_errno,
+ gf_dirent_t *entries)
+{
+ nfs3_call_state_t *cs = NULL;
+
+ cs = frame->local;
+ if (op_ret <= 0) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Directory read done: %s: %s",
+ cs->resolvedloc.path, strerror (op_ret));
+ cs->resolve_ret = -1;
+ cs->resolve_errno = ENOENT;
+ nfs3_call_resume (cs);
+ goto err;
+ }
+
+ nfs3_fh_resolve_search_dir (cs, entries);
+
+err:
+ return 0;
+}
+
+/* Needs no extra argument since it knows that the fh to be resolved is in
+ * resolvefh and that it needs to start looking from the root.
+ */
+int
+nfs3_fh_resolve_inode_hard (nfs3_call_state_t *cs)
+{
+ int ret = -EFAULT;
+ nfs_user_t nfu = {0, };
+
+ if (!cs)
+ return ret;
+
+ cs->hashidx++;
+ nfs_loc_wipe (&cs->resolvedloc);
+ if (nfs3_fh_hash_index_is_beyond (&cs->resolvefh, cs->hashidx)) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Hash index is beyond: idx %d, "
+ " fh idx: %d", cs->hashidx, cs->resolvefh.hashcount);
+ nfs3_call_resume_estale (cs);
+ ret = 0;
+ goto out;
+ }
+
+ nfs_user_root_create (&nfu);
+ gf_log (GF_NFS3, GF_LOG_TRACE, "FH hard resolution: ino:"
+ " %"PRIu64", gen: %"PRIu64", hashidx: %d", cs->resolvefh.ino,
+ cs->resolvefh.gen, cs->hashidx);
+ ret = nfs_ino_loc_fill (cs->vol->itable, 1, 0, &cs->resolvedloc);
+
+ if (ret == 0) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Dir will be opened: %s",
+ cs->resolvedloc.path);
+ ret = nfs_opendir (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_opendir_cbk, cs);
+ } else if (ret == -ENOENT) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Dir needs lookup: %s",
+ cs->resolvedloc.path);
+ ret = nfs_fop_lookup (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_dir_lookup_cbk, cs);
+ }
+
+out:
+ return ret;
+}
+
+
+int
+nfs3_fh_resolve_entry_hard (nfs3_call_state_t *cs)
+{
+ int ret = -EFAULT;
+ nfs_user_t nfu = {0, };
+
+ if (!cs)
+ return ret;
+
+ nfs_loc_wipe (&cs->resolvedloc);
+ nfs_user_root_create (&nfu);
+ gf_log (GF_NFS3, GF_LOG_TRACE, "FH hard resolution: ino:"
+ " %"PRIu64", gen: %"PRIu64", entry: %s, hashidx: %d",
+ cs->resolvefh.ino, cs->resolvefh.gen, cs->resolventry,
+ cs->hashidx);
+
+ ret = nfs_entry_loc_fill (cs->vol->itable, cs->resolvefh.ino,
+ cs->resolvefh.gen, cs->resolventry,
+ &cs->resolvedloc, NFS_RESOLVE_CREATE);
+
+ if (ret == -2) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry needs lookup: %s",
+ cs->resolvedloc.path);
+ ret = nfs_fop_lookup (cs->vol, &nfu, &cs->resolvedloc,
+ nfs3_fh_resolve_entry_lookup_cbk, cs);
+ ret = 0;
+ } else if (ret == -1) {
+ gf_log (GF_NFS3, GF_LOG_TRACE, "Entry needs parent lookup: %s",
+ cs->resolvedloc.path);
+ ret = nfs3_fh_resolve_inode_hard (cs);
+ } else if (ret == 0) {
+ cs->resolve_ret = 0;
+ nfs3_call_resume (cs);
+ }
+
+ return ret;
+}
+
+int
+nfs3_fh_resolve_inode (nfs3_call_state_t *cs)
+{
+ inode_t *inode = NULL;
+ int ret = -EFAULT;
+
+ if (!cs)
+ return ret;
+
+ gf_log (GF_NFS3, GF_LOG_TRACE, "FH needs inode resolution");
+ inode = inode_get (cs->vol->itable, cs->resolvefh.ino,
+ cs->resolvefh.gen);
+ if (!inode)
+ ret = nfs3_fh_resolve_inode_hard (cs);
+ else
+ ret = nfs3_fh_resolve_inode_done (cs, inode);
+
+ if (inode)
+ inode_unref (inode);
+
+ return ret;
+}
+
+int
+nfs3_fh_resolve_entry (nfs3_call_state_t *cs)
+{
+ int ret = -EFAULT;
+
+ if (!cs)
+ return ret;
+
+ ret = nfs3_fh_resolve_entry_hard (cs);
+ if (ret < 0) {
+ cs->resolve_ret = -1;
+ cs->resolve_errno = ESTALE;
+ nfs3_call_resume (cs);
+ }
+
+ return 0;
+}
+
+int
+nfs3_fh_resolve_and_resume (nfs3_call_state_t *cs, struct nfs3_fh *fh,
+ char *entry, nfs3_resume_fn_t resum_fn)
+{
+ int ret = -EFAULT;
+
+ if ((!cs) || (!fh))
+ return ret;
+
+ cs->resume_fn = resum_fn;
+ cs->resolvefh = *fh;
+ cs->hashidx = 0;
+
+ if (!entry)
+ ret = nfs3_fh_resolve_inode (cs);
+ else {
+ cs->resolventry = strdup (entry);
+ if (!cs->resolventry)
+ goto err;
+
+ ret = nfs3_fh_resolve_entry (cs);
+ }
+
+err:
+ return ret;
+}
+
+