diff options
Diffstat (limited to 'xlators/nfs/server/src')
36 files changed, 29255 insertions, 0 deletions
diff --git a/xlators/nfs/server/src/Makefile.am b/xlators/nfs/server/src/Makefile.am new file mode 100644 index 00000000000..01071a79a21 --- /dev/null +++ b/xlators/nfs/server/src/Makefile.am @@ -0,0 +1,38 @@ +if WITH_SERVER +xlator_LTLIBRARIES = server.la +endif + +xlatordir = $(libdir)/glusterfs/$(PACKAGE_VERSION)/xlator/nfs +nfsrpclibdir = $(top_srcdir)/rpc/rpc-lib/src +server_la_LDFLAGS = -module \ + -export-symbols $(top_srcdir)/xlators/nfs/server/src/nfsserver.sym \ + $(GF_XLATOR_LDFLAGS) + +server_la_SOURCES = nfs.c nfs-common.c nfs-fops.c nfs-inodes.c \ + nfs-generics.c mount3.c nfs3-fh.c nfs3.c nfs3-helpers.c nlm4.c \ + nlmcbk_svc.c mount3udp_svc.c acl3.c netgroups.c exports.c \ + mount3-auth.c auth-cache.c + +server_la_LIBADD = $(top_builddir)/libglusterfs/src/libglusterfs.la \ + $(top_builddir)/api/src/libgfapi.la \ + $(top_builddir)/rpc/rpc-lib/src/libgfrpc.la \ + $(top_builddir)/rpc/xdr/src/libgfxdr.la + +noinst_HEADERS = nfs.h nfs-common.h nfs-fops.h nfs-inodes.h nfs-generics.h \ + mount3.h nfs3-fh.h nfs3.h nfs3-helpers.h nfs-mem-types.h nlm4.h \ + acl3.h netgroups.h exports.h mount3-auth.h auth-cache.h nfs-messages.h + +AM_CPPFLAGS = $(GF_CPPFLAGS) \ + -DLIBDIR=\"$(libdir)/glusterfs/$(PACKAGE_VERSION)/auth\" \ + -I$(top_srcdir)/libglusterfs/src -I$(top_srcdir)/api/src \ + -I$(top_srcdir)/rpc/xdr/src/ -I$(top_builddir)/rpc/xdr/src/ \ + -I$(nfsrpclibdir) -I$(CONTRIBDIR)/rbtree \ + -DDATADIR=\"$(localstatedir)\" + +AM_CFLAGS = -Wall $(GF_CFLAGS) + +AM_LDFLAGS = -L$(xlatordir) + +CLEANFILES = + +EXTRA_DIST = nfsserver.sym diff --git a/xlators/nfs/server/src/acl3.c b/xlators/nfs/server/src/acl3.c new file mode 100644 index 00000000000..7e3bbf16086 --- /dev/null +++ b/xlators/nfs/server/src/acl3.c @@ -0,0 +1,933 @@ +/* + * Copyright (c) 2012-2013 Red Hat, Inc. <http://www.redhat.com> + * This file is part of GlusterFS. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 3 or any later version (LGPLv3 or + * later), or the GNU General Public License, version 2 (GPLv2), in all + * cases as published by the Free Software Foundation. + */ + +#include <glusterfs/defaults.h> +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include "nfs.h" +#include <glusterfs/mem-pool.h> +#include <glusterfs/logging.h> +#include "nfs-fops.h" +#include "nfs3.h" +#include "nfs-mem-types.h" +#include "nfs3-helpers.h" +#include "nfs3-fh.h" +#include "nfs-generics.h" +#include "acl3.h" +#include <glusterfs/byte-order.h> +#include <glusterfs/compat-errno.h> +#include "nfs-messages.h" + +static int +acl3_nfs_acl_to_xattr(aclentry *ace, void *xattrbuf, int aclcount, int defacl); + +static int +acl3_nfs_acl_from_xattr(aclentry *ace, void *xattrbuf, int bufsize, int defacl); + +typedef ssize_t (*acl3_serializer)(struct iovec outmsg, void *args); + +extern void +nfs3_call_state_wipe(nfs3_call_state_t *cs); + +extern nfs3_call_state_t * +nfs3_call_state_init(struct nfs3_state *s, rpcsvc_request_t *req, xlator_t *v); + +extern int +nfs3_fh_validate(struct nfs3_fh *fh); + +extern void +nfs3_stat_to_fattr3(struct iatt *buf, fattr3 *fa); + +#define acl3_validate_nfs3_state(request, state, status, label, retval) \ + do { \ + state = rpcsvc_request_program_private(request); \ + if (!state) { \ + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_STATE_MISSING, \ + "NFSv3 state " \ + "missing from RPC request"); \ + rpcsvc_request_seterr(req, SYSTEM_ERR); \ + status = NFS3ERR_SERVERFAULT; \ + goto label; \ + } \ + } while (0); + +#define acl3_validate_gluster_fh(handle, status, errlabel) \ + do { \ + if (!nfs3_fh_validate(handle)) { \ + gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_BAD_HANDLE, \ + "Bad Handle"); \ + status = NFS3ERR_BADHANDLE; \ + goto errlabel; \ + } \ + } while (0) + +extern xlator_t * +nfs3_fh_to_xlator(struct nfs3_state *nfs3, struct nfs3_fh *fh); + +#define acl3_map_fh_to_volume(nfs3state, handle, req, volume, status, label) \ + do { \ + char exportid[256], gfid[256]; \ + rpc_transport_t *trans = NULL; \ + volume = nfs3_fh_to_xlator((nfs3state), handle); \ + if (!volume) { \ + gf_uuid_unparse(handle->exportid, exportid); \ + gf_uuid_unparse(handle->gfid, gfid); \ + trans = rpcsvc_request_transport(req); \ + gf_msg(GF_ACL, GF_LOG_ERROR, 0, NFS_MSG_FH_TO_VOL_FAIL, \ + "Failed to map " \ + "FH to vol: client=%s, exportid=%s, gfid=%s", \ + trans->peerinfo.identifier, exportid, gfid); \ + gf_msg(GF_ACL, GF_LOG_ERROR, ESTALE, NFS_MSG_VOLUME_ERROR, \ + "Stale nfs client %s must be trying to " \ + "connect to a deleted volume, please " \ + "unmount it.", \ + trans->peerinfo.identifier); \ + status = NFS3ERR_STALE; \ + goto label; \ + } else { \ + gf_msg_trace(GF_ACL, 0, "FH to Volume: %s", volume->name); \ + rpcsvc_request_set_private(req, volume); \ + } \ + } while (0); + +#define acl3_volume_started_check(nfs3state, vlm, rtval, erlbl) \ + do { \ + if ((!nfs_subvolume_started(nfs_state(nfs3state->nfsx), vlm))) { \ + gf_msg(GF_ACL, GF_LOG_ERROR, 0, NFS_MSG_VOL_DISABLE, \ + "Volume is disabled: %s", vlm->name); \ + rtval = RPCSVC_ACTOR_IGNORE; \ + goto erlbl; \ + } \ + } while (0) + +#define acl3_check_fh_resolve_status(cst, nfstat, erlabl) \ + do { \ + xlator_t *xlatorp = NULL; \ + char buf[256], gfid[GF_UUID_BUF_SIZE]; \ + rpc_transport_t *trans = NULL; \ + if ((cst)->resolve_ret < 0) { \ + trans = rpcsvc_request_transport(cst->req); \ + xlatorp = nfs3_fh_to_xlator(cst->nfs3state, &cst->resolvefh); \ + gf_uuid_unparse(cst->resolvefh.gfid, gfid); \ + snprintf(buf, sizeof(buf), "(%s) %s : %s", \ + trans->peerinfo.identifier, \ + xlatorp ? xlatorp->name : "ERR", gfid); \ + gf_msg(GF_ACL, GF_LOG_ERROR, cst->resolve_errno, \ + NFS_MSG_RESOLVE_FH_FAIL, \ + "Unable to resolve " \ + "FH: %s", \ + buf); \ + nfstat = nfs3_errno_to_nfsstat3(cst->resolve_errno); \ + goto erlabl; \ + } \ + } while (0) + +#define acl3_handle_call_state_init(nfs3state, calls, rq, v, opstat, errlabel) \ + do { \ + calls = nfs3_call_state_init((nfs3state), (rq), v); \ + if (!calls) { \ + gf_msg(GF_ACL, GF_LOG_ERROR, 0, NFS_MSG_INIT_CALL_STAT_FAIL, \ + "Failed to " \ + "init call state"); \ + opstat = NFS3ERR_SERVERFAULT; \ + rpcsvc_request_seterr(req, SYSTEM_ERR); \ + goto errlabel; \ + } \ + } while (0) + +int +acl3svc_submit_reply(rpcsvc_request_t *req, void *arg, acl3_serializer sfunc) +{ + struct iovec outmsg = { + 0, + }; + struct iobuf *iob = NULL; + struct nfs3_state *nfs3 = NULL; + int ret = -1; + ssize_t msglen = 0; + struct iobref *iobref = NULL; + + if (!req) + return -1; + + nfs3 = (struct nfs3_state *)rpcsvc_request_program_private(req); + if (!nfs3) { + gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "mount state not found"); + goto ret; + } + + /* First, get the io buffer into which the reply in arg will + * be serialized. + */ + iob = iobuf_get(nfs3->iobpool); + if (!iob) { + gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobuf"); + goto ret; + } + + iobuf_to_iovec(iob, &outmsg); + /* Use the given serializer to translate the give C structure in arg + * to XDR format which will be written into the buffer in outmsg. + */ + msglen = sfunc(outmsg, arg); + if (msglen < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_ENCODE_MSG_FAIL, + "Failed to encode message"); + goto ret; + } + outmsg.iov_len = msglen; + + iobref = iobref_new(); + if (iobref == NULL) { + gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobref"); + goto ret; + } + + ret = iobref_add(iobref, iob); + if (ret) { + gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to add iob to iobref"); + goto ret; + } + + /* Then, submit the message for transmission. */ + ret = rpcsvc_submit_message(req, &outmsg, 1, NULL, 0, iobref); + if (ret == -1) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_REP_SUBMIT_FAIL, + "Reply submission failed"); + goto ret; + } + + ret = 0; +ret: + if (iob) + iobuf_unref(iob); + if (iobref) + iobref_unref(iobref); + + return ret; +} + +int +acl3svc_null(rpcsvc_request_t *req) +{ + struct iovec dummyvec = { + 0, + }; + + if (!req) { + gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Got NULL request!"); + return 0; + } + rpcsvc_submit_generic(req, &dummyvec, 1, NULL, 0, NULL); + return 0; +} + +int +acl3_getacl_reply(rpcsvc_request_t *req, getaclreply *reply) +{ + acl3svc_submit_reply(req, (void *)reply, + (acl3_serializer)xdr_serialize_getaclreply); + return 0; +} + +int +acl3_setacl_reply(rpcsvc_request_t *req, setaclreply *reply) +{ + acl3svc_submit_reply(req, (void *)reply, + (acl3_serializer)xdr_serialize_setaclreply); + return 0; +} + +/* acl3_getacl_cbk: fetch and decode the ACL in the POSIX_ACL_ACCESS_XATTR + * + * The POSIX_ACL_ACCESS_XATTR can be set on files and directories. + */ +int +acl3_getacl_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *dict, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + data_t *data = NULL; + getaclreply *getaclreply = NULL; + int aclcount = 0; + int defacl = 1; /* DEFAULT ACL */ + + if (!frame->local) { + gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid argument, frame->local NULL"); + return -EINVAL; + } + cs = frame->local; + getaclreply = &cs->args.getaclreply; + if ((op_ret < 0) && (op_errno != ENODATA && op_errno != ENOATTR)) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto err; + } else if (!dict) { + /* no ACL has been set */ + stat = NFS3_OK; + goto err; + } + + getaclreply->aclentry.aclentry_val = cs->aclentry; + + /* getfacl: NFS USER ACL */ + data = dict_get(dict, POSIX_ACL_ACCESS_XATTR); + if (data && data->data) { + aclcount = acl3_nfs_acl_from_xattr(cs->aclentry, data->data, data->len, + !defacl); + if (aclcount < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, aclcount, NFS_MSG_GET_USER_ACL_FAIL, + "Failed to get USER ACL"); + stat = nfs3_errno_to_nfsstat3(-aclcount); + goto err; + } + getaclreply->aclcount = aclcount; + getaclreply->aclentry.aclentry_len = aclcount; + } + + acl3_getacl_reply(cs->req, getaclreply); + nfs3_call_state_wipe(cs); + return 0; + +err: + if (getaclreply) + getaclreply->status = stat; + acl3_getacl_reply(cs->req, getaclreply); + nfs3_call_state_wipe(cs); + return 0; +} + +/* acl3_default_getacl_cbk: fetch and decode the ACL set in the + * POSIX_ACL_DEFAULT_XATTR xattr. + * + * The POSIX_ACL_DEFAULT_XATTR xattr is only set on directories, not on files. + * + * When done with POSIX_ACL_DEFAULT_XATTR, we also need to get and decode the + * ACL that can be set in POSIX_ACL_DEFAULT_XATTR. + */ +int +acl3_default_getacl_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *dict, + dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + data_t *data = NULL; + getaclreply *getaclreply = NULL; + int aclcount = 0; + int defacl = 1; /* DEFAULT ACL */ + nfs_user_t nfu = { + 0, + }; + int ret = -1; + + if (!frame->local) { + gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid argument, frame->local NULL"); + return -EINVAL; + } + cs = frame->local; + getaclreply = &cs->args.getaclreply; + if ((op_ret < 0) && (op_errno != ENODATA && op_errno != ENOATTR)) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto err; + } else if (!dict) { + /* no ACL has been set */ + stat = NFS3_OK; + goto err; + } + + getaclreply->daclentry.daclentry_val = cs->daclentry; + + /* getfacl: NFS DEFAULT ACL */ + data = dict_get(dict, POSIX_ACL_DEFAULT_XATTR); + if (data && data->data) { + aclcount = acl3_nfs_acl_from_xattr(cs->daclentry, data->data, data->len, + defacl); + if (aclcount < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, aclcount, NFS_MSG_GET_DEF_ACL_FAIL, + "Failed to get DEFAULT ACL"); + stat = nfs3_errno_to_nfsstat3(-aclcount); + goto err; + } + + getaclreply->daclcount = aclcount; + getaclreply->daclentry.daclentry_len = aclcount; + } + + getaclreply->attr_follows = TRUE; + nfs_request_user_init(&nfu, cs->req); + ret = nfs_getxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + POSIX_ACL_ACCESS_XATTR, NULL, acl3_getacl_cbk, cs); + if (ret < 0) { + stat = nfs3_errno_to_nfsstat3(-ret); + goto err; + } + + return 0; + +err: + if (getaclreply) + getaclreply->status = stat; + acl3_getacl_reply(cs->req, getaclreply); + nfs3_call_state_wipe(cs); + return 0; +} + +int +acl3_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, + int32_t op_errno, struct iatt *buf, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + getaclreply *getaclreply = NULL; + int ret = -1; + nfs_user_t nfu = { + 0, + }; + uint64_t deviceid = 0; + + if (!frame->local) { + gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid argument, frame->local NULL"); + return EINVAL; + } + + cs = frame->local; + getaclreply = &cs->args.getaclreply; + + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto err; + } + + /* Fill the attrs before xattrs */ + getaclreply->attr_follows = TRUE; + deviceid = nfs3_request_xlator_deviceid(cs->req); + nfs3_map_deviceid_to_statdev(buf, deviceid); + nfs3_stat_to_fattr3(buf, &(getaclreply->attr)); + + nfs_request_user_init(&nfu, cs->req); + if (buf->ia_type == IA_IFDIR) { + ret = nfs_getxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + POSIX_ACL_DEFAULT_XATTR, NULL, + acl3_default_getacl_cbk, cs); + } else { + ret = nfs_getxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + POSIX_ACL_ACCESS_XATTR, NULL, acl3_getacl_cbk, cs); + } + + if (ret < 0) { + stat = nfs3_errno_to_nfsstat3(-ret); + goto err; + } + + return 0; +err: + getaclreply->status = stat; + acl3_getacl_reply(cs->req, getaclreply); + nfs3_call_state_wipe(cs); + return 0; +} + +int +acl3_getacl_resume(void *carg) +{ + int ret = -1; + nfs3_call_state_t *cs = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + acl3_check_fh_resolve_status(cs, stat, acl3err); + nfs_request_user_init(&nfu, cs->req); + + ret = nfs_stat(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, acl3_stat_cbk, + cs); + stat = -ret; +acl3err: + if (ret < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, stat, NFS_MSG_OPEN_FAIL, + "unable to open_and_resume"); + cs->args.getaclreply.status = nfs3_errno_to_nfsstat3(stat); + acl3_getacl_reply(cs->req, &cs->args.getaclreply); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +acl3svc_getacl(rpcsvc_request_t *req) +{ + xlator_t *vol = NULL; + struct nfs_state *nfs = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + int ret = RPCSVC_ACTOR_ERROR; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + struct nfs3_fh fh, *fhp = NULL; + getaclargs getaclargs; + getaclreply getaclreply; + + if (!req) + return ret; + + acl3_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + memset(&getaclargs, 0, sizeof(getaclargs)); + memset(&getaclreply, 0, sizeof(getaclreply)); + getaclargs.fh.n_bytes = (char *)&fh; + if (xdr_to_getaclargs(req->msg[0], &getaclargs) <= 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + /* Validate ACL mask */ + if (getaclargs.mask & ~(NFS_ACL | NFS_ACLCNT | NFS_DFACL | NFS_DFACLCNT)) { + stat = NFS3ERR_INVAL; + goto acl3err; + } + + fhp = &fh; + acl3_validate_gluster_fh(&fh, stat, acl3err); + acl3_map_fh_to_volume(nfs->nfs3state, fhp, req, vol, stat, acl3err); + acl3_volume_started_check(nfs3, vol, ret, rpcerr); + acl3_handle_call_state_init(nfs->nfs3state, cs, req, vol, stat, acl3err); + + cs->vol = vol; + cs->args.getaclreply.mask = getaclargs.mask; + + ret = nfs3_fh_resolve_and_resume(cs, fhp, NULL, acl3_getacl_resume); + stat = nfs3_errno_to_nfsstat3(-ret); + +acl3err: + if (ret < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR, + "unable to resolve and resume"); + getaclreply.status = stat; + acl3_getacl_reply(req, &getaclreply); + nfs3_call_state_wipe(cs); + return 0; + } + +rpcerr: + return ret; +} + +int +acl3_setacl_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) +{ + nfs3_call_state_t *cs = NULL; + cs = frame->local; + if (op_ret < 0) { + nfsstat3 status = nfs3_cbk_errno_status(op_ret, op_errno); + cs->args.setaclreply.status = status; + } + + acl3_setacl_reply(cs->req, &cs->args.setaclreply); + + nfs3_call_state_wipe(cs); + + return 0; +} + +int +acl3_setacl_resume(void *carg) +{ + int ret = -1; + nfs3_call_state_t *cs = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs_user_t nfu = { + 0, + }; + dict_t *xattr = NULL; + + if (!carg) + return ret; + cs = (nfs3_call_state_t *)carg; + acl3_check_fh_resolve_status(cs, stat, acl3err); + nfs_request_user_init(&nfu, cs->req); + xattr = dict_new(); + if (xattr == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL, + "dict allocation failed"); + goto acl3err; + } + + if (cs->aclcount) + ret = dict_set_static_bin(xattr, POSIX_ACL_ACCESS_XATTR, cs->aclxattr, + posix_acl_xattr_size(cs->aclcount)); + if (cs->daclcount) + ret = dict_set_static_bin(xattr, POSIX_ACL_DEFAULT_XATTR, cs->daclxattr, + posix_acl_xattr_size(cs->daclcount)); + + ret = nfs_setxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, xattr, 0, + NULL, acl3_setacl_cbk, cs); + dict_unref(xattr); + +acl3err: + if (ret < 0) { + stat = -ret; + gf_msg(GF_ACL, GF_LOG_ERROR, stat, NFS_MSG_OPEN_FAIL, + "unable to open_and_resume"); + cs->args.setaclreply.status = nfs3_errno_to_nfsstat3(stat); + acl3_setacl_reply(cs->req, &cs->args.setaclreply); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +acl3svc_setacl(rpcsvc_request_t *req) +{ + xlator_t *vol = NULL; + struct nfs_state *nfs = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + int ret = RPCSVC_ACTOR_ERROR; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + struct nfs3_fh fh; + struct nfs3_fh *fhp = NULL; + setaclargs setaclargs; + setaclreply setaclreply; + aclentry *daclentry = NULL; + aclentry *aclentry = NULL; + int aclerrno = 0; + int defacl = 1; + + if (!req) + return ret; + aclentry = GF_CALLOC(NFS_ACL_MAX_ENTRIES, sizeof(*aclentry), gf_nfs_mt_arr); + if (!aclentry) { + goto rpcerr; + } + daclentry = GF_CALLOC(NFS_ACL_MAX_ENTRIES, sizeof(*daclentry), + gf_nfs_mt_arr); + if (!daclentry) { + goto rpcerr; + } + + acl3_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + memset(&setaclargs, 0, sizeof(setaclargs)); + memset(&setaclreply, 0, sizeof(setaclreply)); + memset(&fh, 0, sizeof(fh)); + setaclargs.fh.n_bytes = (char *)&fh; + setaclargs.aclentry.aclentry_val = aclentry; + setaclargs.daclentry.daclentry_val = daclentry; + if (xdr_to_setaclargs(req->msg[0], &setaclargs) <= 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + /* Validate ACL mask */ + if (setaclargs.mask & ~(NFS_ACL | NFS_ACLCNT | NFS_DFACL | NFS_DFACLCNT)) { + stat = NFS3ERR_INVAL; + goto acl3err; + } + + fhp = &fh; + acl3_validate_gluster_fh(fhp, stat, acl3err); + acl3_map_fh_to_volume(nfs->nfs3state, fhp, req, vol, stat, acl3err); + acl3_volume_started_check(nfs3, vol, ret, rpcerr); + acl3_handle_call_state_init(nfs->nfs3state, cs, req, vol, stat, acl3err); + + cs->vol = vol; + cs->aclcount = setaclargs.aclcount; + cs->daclcount = setaclargs.daclcount; + + /* setfacl: NFS USER ACL */ + aclerrno = acl3_nfs_acl_to_xattr(aclentry, cs->aclxattr, cs->aclcount, + !defacl); + if (aclerrno < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, -aclerrno, NFS_MSG_SET_USER_ACL_FAIL, + "Failed to set USER ACL"); + stat = nfs3_errno_to_nfsstat3(-aclerrno); + goto acl3err; + } + + /* setfacl: NFS DEFAULT ACL */ + aclerrno = acl3_nfs_acl_to_xattr(daclentry, cs->daclxattr, cs->daclcount, + defacl); + if (aclerrno < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, -aclerrno, NFS_MSG_SET_DEF_ACL_FAIL, + "Failed to set DEFAULT ACL"); + stat = nfs3_errno_to_nfsstat3(-aclerrno); + goto acl3err; + } + + ret = nfs3_fh_resolve_and_resume(cs, fhp, NULL, acl3_setacl_resume); + stat = nfs3_errno_to_nfsstat3(-ret); + +acl3err: + if (ret < 0) { + gf_msg(GF_ACL, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR, + "unable to resolve and resume"); + setaclreply.status = stat; + acl3_setacl_reply(req, &setaclreply); + nfs3_call_state_wipe(cs); + GF_FREE(aclentry); + GF_FREE(daclentry); + return 0; + } + +rpcerr: + if (ret < 0) + nfs3_call_state_wipe(cs); + if (aclentry) + GF_FREE(aclentry); + if (daclentry) + GF_FREE(daclentry); + return ret; +} + +static rpcsvc_actor_t acl3svc_actors[ACL3_PROC_COUNT] = { + {"NULL", acl3svc_null, NULL, ACL3_NULL, DRC_NA, 0}, + {"GETACL", acl3svc_getacl, NULL, ACL3_GETACL, DRC_NA, 0}, + {"SETACL", acl3svc_setacl, NULL, ACL3_SETACL, DRC_NA, 0}, +}; + +static rpcsvc_program_t acl3prog = { + .progname = "ACL3", + .prognum = ACL_PROGRAM, + .progver = ACLV3_VERSION, + .progport = GF_NFS3_PORT, + .actors = acl3svc_actors, + .numactors = ACL3_PROC_COUNT, + .min_auth = AUTH_NULL, +}; + +rpcsvc_program_t * +acl3svc_init(xlator_t *nfsx) +{ + struct nfs3_state *ns = NULL; + struct nfs_state *nfs = NULL; + dict_t *options = NULL; + int ret = -1; + static gf_boolean_t acl3_inited = _gf_false; + + /* Already inited */ + if (acl3_inited) + return &acl3prog; + + nfs = (struct nfs_state *)nfsx->private; + + ns = nfs->nfs3state; + if (!ns) { + gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_ACL_INIT_FAIL, + "ACL3 init failed"); + goto err; + } + acl3prog.private = ns; + + options = dict_new(); + if (options == NULL) { + gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL, + "dict allocation failed"); + goto err; + } + + ret = dict_set_str(options, "transport.socket.listen-port", GF_ACL3_PORT); + if (ret == -1) + goto err; + ret = dict_set_str(options, "transport-type", "socket"); + if (ret == -1) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + if (nfs->allow_insecure) { + ret = dict_set_str(options, "rpc-auth-allow-insecure", "on"); + if (ret == -1) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + ret = dict_set_str(options, "rpc-auth.ports.insecure", "on"); + if (ret == -1) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + } + + ret = dict_set_str(options, "transport.address-family", "inet"); + if (ret == -1) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + ret = rpcsvc_create_listeners(nfs->rpcsvc, options, "ACL"); + if (ret == -1) { + gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_LISTENERS_CREATE_FAIL, + "Unable to create listeners"); + goto err; + } + + if (options) + dict_unref(options); + + acl3_inited = _gf_true; + return &acl3prog; +err: + if (options) + dict_unref(options); + return NULL; +} + +static int +acl3_nfs_acl_to_xattr(aclentry *ace, /* ACL entries to be read */ + void *xattrbuf, /* XATTR buf to be populated */ + int aclcount, /* No of ACLs to be read */ + int defacl) /* 1 if DEFAULT ACL */ +{ + int idx = 0; + posix_acl_xattr_header *xheader = NULL; + posix_acl_xattr_entry *xentry = NULL; + + if ((!ace) || (!xattrbuf)) + return (-EINVAL); + + /* ACL count is ZERO, nothing to do */ + if (!aclcount) + return (0); + + if ((aclcount < 0) || (aclcount > NFS_ACL_MAX_ENTRIES)) + return (-EINVAL); + + xheader = (posix_acl_xattr_header *)(xattrbuf); + xentry = (posix_acl_xattr_entry *)(xheader + 1); + + /* + * For "default ACL", NFSv3 handles the 'type' differently + * i.e. by logical OR'ing 'type' with NFS_ACL_DEFAULT. + * Which the backend File system does not understand and + * that needs to be masked OFF. + */ + xheader->version = POSIX_ACL_XATTR_VERSION; + + for (idx = 0; idx < aclcount; idx++) { + xentry->tag = ace->type; + if (defacl) + xentry->tag &= ~NFS_ACL_DEFAULT; + xentry->perm = ace->perm; + + switch (xentry->tag) { + case POSIX_ACL_USER: + case POSIX_ACL_GROUP: + if (xentry->perm & ~S_IRWXO) + return (-EINVAL); + xentry->id = ace->uid; + break; + case POSIX_ACL_USER_OBJ: + case POSIX_ACL_GROUP_OBJ: + case POSIX_ACL_OTHER: + if (xentry->perm & ~S_IRWXO) + return (-EINVAL); + xentry->id = POSIX_ACL_UNDEFINED_ID; + break; + case POSIX_ACL_MASK: + /* Solaris sometimes sets additional bits in + * the mask. + */ + xentry->perm &= S_IRWXO; + xentry->id = POSIX_ACL_UNDEFINED_ID; + break; + default: + return (-EINVAL); + } + + xentry++; + ace++; + } + + /* SUCCESS */ + return (0); +} + +static int +acl3_nfs_acl_from_xattr(aclentry *ace, /* ACL entries to be filled */ + void *xattrbuf, /* XATTR buf to be read */ + int bufsize, /* Size of XATTR buffer */ + int defacl) /* 1 if DEFAULT ACL */ +{ + int idx = 0; + ssize_t aclcount = 0; + posix_acl_xattr_header *xheader = NULL; + posix_acl_xattr_entry *xentry = NULL; + + if ((!xattrbuf) || (!ace)) + return (-EINVAL); + + aclcount = posix_acl_xattr_count(bufsize); + if ((aclcount < 0) || (aclcount > NFS_ACL_MAX_ENTRIES)) + return (-EINVAL); + + xheader = (posix_acl_xattr_header *)(xattrbuf); + xentry = (posix_acl_xattr_entry *)(xheader + 1); + + /* Check for supported POSIX ACL xattr version */ + if (xheader->version != POSIX_ACL_XATTR_VERSION) + return (-ENOSYS); + + for (idx = 0; idx < (int)aclcount; idx++) { + ace->type = xentry->tag; + if (defacl) { + /* + * SET the NFS_ACL_DEFAULT flag for default + * ACL which was masked OFF during setfacl(). + */ + ace->type |= NFS_ACL_DEFAULT; + } + ace->perm = (xentry->perm & S_IRWXO); + + switch (xentry->tag) { + case POSIX_ACL_USER: + case POSIX_ACL_GROUP: + ace->uid = xentry->id; + break; + case POSIX_ACL_USER_OBJ: + case POSIX_ACL_GROUP_OBJ: + case POSIX_ACL_MASK: + case POSIX_ACL_OTHER: + ace->uid = POSIX_ACL_UNDEFINED_ID; + break; + default: + return (-EINVAL); + } + + xentry++; + ace++; + } + + /* SUCCESS: ACL count */ + return aclcount; +} diff --git a/xlators/nfs/server/src/acl3.h b/xlators/nfs/server/src/acl3.h new file mode 100644 index 00000000000..762fbb04a0f --- /dev/null +++ b/xlators/nfs/server/src/acl3.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012 Red Hat, Inc. <http://www.redhat.com> + * This file is part of GlusterFS. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 3 or any later version (LGPLv3 or + * later), or the GNU General Public License, version 2 (GPLv2), in all + * cases as published by the Free Software Foundation. + */ + +#ifndef _ACL3_H +#define _ACL3_H + +#include <glusterfs/glusterfs-acl.h> + +#define ACL3_NULL 0 +#define ACL3_GETACL 1 +#define ACL3_SETACL 2 +#define ACL3_PROC_COUNT 3 + +#define GF_ACL3_PORT "38469" +#define GF_ACL GF_NFS "-ACL" + +/* Flags for the getacl/setacl mode */ +#define NFS_ACL 0x0001 +#define NFS_ACLCNT 0x0002 +#define NFS_DFACL 0x0004 +#define NFS_DFACLCNT 0x0008 + +/* + * NFSv3, identifies the default ACL by NFS_ACL_DEFAULT. Gluster + * NFS needs to mask it OFF before sending it up to POSIX layer + * or File system layer. + */ +#define NFS_ACL_DEFAULT 0x1000 + +#define NFS_ACL_MAX_ENTRIES 1024 + +rpcsvc_program_t * +acl3svc_init(xlator_t *nfsx); + +#endif diff --git a/xlators/nfs/server/src/auth-cache.c b/xlators/nfs/server/src/auth-cache.c new file mode 100644 index 00000000000..ffbf5b6cad6 --- /dev/null +++ b/xlators/nfs/server/src/auth-cache.c @@ -0,0 +1,496 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include <glusterfs/refcount.h> +#include "auth-cache.h" +#include "nfs3.h" +#include "exports.h" +#include "nfs-messages.h" + +enum auth_cache_lookup_results { + ENTRY_FOUND = 0, + ENTRY_NOT_FOUND = -1, + ENTRY_EXPIRED = -2, +}; + +struct auth_cache_entry { + GF_REF_DECL; /* refcounting */ + data_t *data; /* data_unref() on refcount == 0 */ + + time_t timestamp; + struct export_item *item; +}; + +/* Given a filehandle and an ip, creates a colon delimited hashkey. + */ +static char * +make_hashkey(struct nfs3_fh *fh, const char *host) +{ + char *hashkey = NULL; + char exportid[256] = { + 0, + }; + char mountid[256] = { + 0, + }; + size_t nbytes = 0; + + gf_uuid_unparse(fh->exportid, exportid); + gf_uuid_unparse(fh->mountid, mountid); + + nbytes = strlen(exportid) + strlen(host) + strlen(mountid) + 3; + hashkey = GF_MALLOC(nbytes, gf_common_mt_char); + if (!hashkey) + return NULL; + + snprintf(hashkey, nbytes, "%s:%s:%s", exportid, mountid, host); + + return hashkey; +} + +/** + * auth_cache_init -- Initialize an auth cache and set the ttl_sec + * + * @ttl_sec : The TTL to set in seconds + * + * @return : allocated auth cache struct, NULL if allocation failed. + */ +struct auth_cache * +auth_cache_init(time_t ttl_sec) +{ + struct auth_cache *cache = GF_CALLOC(1, sizeof(*cache), + gf_nfs_mt_auth_cache); + + GF_VALIDATE_OR_GOTO("auth-cache", cache, out); + + cache->cache_dict = dict_new(); + if (!cache->cache_dict) { + GF_FREE(cache); + cache = NULL; + goto out; + } + + LOCK_INIT(&cache->lock); + cache->ttl_sec = ttl_sec; +out: + return cache; +} + +/* auth_cache_entry_free -- called by refcounting subsystem on refcount == 0 + * + * @to_free: auth_cache_entry that has refcount == 0 and needs to get free'd + */ +void +auth_cache_entry_free(void *to_free) +{ + struct auth_cache_entry *entry = to_free; + data_t *entry_data = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS, entry, out); + GF_VALIDATE_OR_GOTO(GF_NFS, entry->data, out); + + entry_data = entry->data; + /* set data_t->data to NULL, otherwise data_unref() tries to free it */ + entry_data->data = NULL; + data_unref(entry_data); + + GF_FREE(entry); +out: + return; +} + +/** + * auth_cache_entry_init -- Initialize an auth cache entry + * + * @return: Pointer to an allocated auth cache entry, NULL if allocation + * failed. + */ +static struct auth_cache_entry * +auth_cache_entry_init() +{ + struct auth_cache_entry *entry = NULL; + + entry = GF_CALLOC(1, sizeof(*entry), gf_nfs_mt_auth_cache_entry); + if (!entry) + gf_msg(GF_NFS, GF_LOG_WARNING, ENOMEM, NFS_MSG_NO_MEMORY, + "failed to allocate entry"); + else + GF_REF_INIT(entry, auth_cache_entry_free); + + return entry; +} + +/** + * auth_cache_add -- Add an auth_cache_entry to the cache->dict + * + * @return: 0 on success, non-zero otherwise. + */ +static int +auth_cache_add(struct auth_cache *cache, char *hashkey, + struct auth_cache_entry *entry) +{ + int ret = -1; + data_t *entry_data = NULL; + int hashkey_len; + GF_VALIDATE_OR_GOTO(GF_NFS, cache, out); + GF_VALIDATE_OR_GOTO(GF_NFS, cache->cache_dict, out); + + /* FIXME: entry is passed as parameter, this can never fail? */ + entry = GF_REF_GET(entry); + if (!entry) { + /* entry does not have any references */ + ret = -1; + goto out; + } + + entry_data = bin_to_data(entry, sizeof(*entry)); + if (!entry_data) { + ret = -1; + GF_REF_PUT(entry); + goto out; + } + + /* we'll take an extra ref on the data_t, it gets unref'd when the + * auth_cache_entry is released */ + entry->data = data_ref(entry_data); + + hashkey_len = strlen(hashkey); + LOCK(&cache->lock); + { + ret = dict_setn(cache->cache_dict, hashkey, hashkey_len, entry_data); + } + UNLOCK(&cache->lock); + + if (ret) { + /* adding to dict failed */ + GF_REF_PUT(entry); + } +out: + return ret; +} + +/** + * _auth_cache_expired -- Check if the auth_cache_entry has expired + * + * The auth_cache->lock should have been taken when this function is called. + * + * @return: true when the auth_cache_entry is expired, false otherwise. + */ +static int +_auth_cache_expired(struct auth_cache *cache, struct auth_cache_entry *entry) +{ + return ((gf_time() - entry->timestamp) > cache->ttl_sec); +} + +/** + * auth_cache_get -- Get the @hashkey entry from the cache->cache_dict + * + * @cache: The auth_cache that should contain the @entry. + * @haskkey: The key associated with the auth_cache_entry. + * @entry: The found auth_cache_entry, unmodified if not found/expired. + * + * The using the cache->dict requires locking, this function takes care of + * that. When the entry is found, but has expired, it will be removed from the + * cache_dict. + * + * @return: 0 when found, ENTRY_NOT_FOUND or ENTRY_EXPIRED otherwise. + */ +static enum auth_cache_lookup_results +auth_cache_get(struct auth_cache *cache, char *hashkey, + struct auth_cache_entry **entry) +{ + enum auth_cache_lookup_results ret = ENTRY_NOT_FOUND; + data_t *entry_data = NULL; + struct auth_cache_entry *lookup_res = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS, cache, out); + GF_VALIDATE_OR_GOTO(GF_NFS, cache->cache_dict, out); + GF_VALIDATE_OR_GOTO(GF_NFS, hashkey, out); + + LOCK(&cache->lock); + { + entry_data = dict_get(cache->cache_dict, hashkey); + if (!entry_data) + goto unlock; + + /* FIXME: this is dangerous use of entry_data */ + lookup_res = GF_REF_GET((struct auth_cache_entry *)entry_data->data); + if (lookup_res == NULL) { + /* entry has been free'd */ + ret = ENTRY_EXPIRED; + goto unlock; + } + + if (_auth_cache_expired(cache, lookup_res)) { + ret = ENTRY_EXPIRED; + GF_REF_PUT(lookup_res->item); + lookup_res->item = NULL; + + /* free entry and remove from the cache */ + GF_FREE(lookup_res); + entry_data->data = NULL; + dict_del(cache->cache_dict, hashkey); + + goto unlock; + } + + *entry = lookup_res; + ret = ENTRY_FOUND; + } +unlock: + UNLOCK(&cache->lock); + +out: + return ret; +} + +/** + * auth_cache_lookup -- Lookup an item from the cache + * + * @cache: cache to lookup from + * @fh : FH to use in lookup + * @host_addr: Address to use in lookup + * @timestamp: The timestamp to set when lookup succeeds + * @can_write: Is the host authorized to write to the filehandle? + * + * If the current time - entry time of the cache entry > ttl_sec, + * we remove the element from the dict and return ENTRY_EXPIRED. + * + * @return: ENTRY_EXPIRED if entry expired + * ENTRY_NOT_FOUND if entry not found in dict + * 0 if found + */ +enum auth_cache_lookup_results +auth_cache_lookup(struct auth_cache *cache, struct nfs3_fh *fh, + const char *host_addr, time_t *timestamp, + gf_boolean_t *can_write) +{ + char *hashkey = NULL; + struct auth_cache_entry *lookup_res = NULL; + enum auth_cache_lookup_results ret = ENTRY_NOT_FOUND; + + GF_VALIDATE_OR_GOTO(GF_NFS, cache, out); + GF_VALIDATE_OR_GOTO(GF_NFS, fh, out); + GF_VALIDATE_OR_GOTO(GF_NFS, host_addr, out); + GF_VALIDATE_OR_GOTO(GF_NFS, timestamp, out); + GF_VALIDATE_OR_GOTO(GF_NFS, can_write, out); + + hashkey = make_hashkey(fh, host_addr); + if (!hashkey) { + ret = -ENOMEM; + goto out; + } + + ret = auth_cache_get(cache, hashkey, &lookup_res); + switch (ret) { + case ENTRY_FOUND: + *timestamp = lookup_res->timestamp; + *can_write = lookup_res->item->opts->rw; + GF_REF_PUT(lookup_res); + break; + + case ENTRY_NOT_FOUND: + gf_msg_debug(GF_NFS, 0, "could not find entry for %s", host_addr); + break; + + case ENTRY_EXPIRED: + gf_msg_debug(GF_NFS, 0, "entry for host %s has expired", host_addr); + break; + } + +out: + GF_FREE(hashkey); + + return ret; +} + +/* auth_cache_entry_purge -- free up the auth_cache_entry + * + * This gets called through dict_foreach() by auth_cache_purge(). Each + * auth_cache_entry has a refcount which needs to be decremented. Once the + * auth_cache_entry reaches refcount == 0, auth_cache_entry_free() will call + * data_unref() to free the associated data_t. + * + * @d: dict that gets purged by auth_cache_purge() + * @k: hashkey of the current entry + * @v: data_t of the current entry + */ +int +auth_cache_entry_purge(dict_t *d, char *k, data_t *v, void *_unused) +{ + struct auth_cache_entry *entry = (struct auth_cache_entry *)v->data; + + if (entry) + GF_REF_PUT(entry); + + return 0; +} + +/** + * auth_cache_purge -- Purge the dict in the cache and create a new empty one. + * + * @cache: Cache to purge + * + */ +void +auth_cache_purge(struct auth_cache *cache) +{ + dict_t *new_cache_dict = dict_new(); + dict_t *old_cache_dict = NULL; + + if (!cache || !new_cache_dict) + goto out; + + LOCK(&cache->lock); + { + old_cache_dict = cache->cache_dict; + cache->cache_dict = new_cache_dict; + } + UNLOCK(&cache->lock); + + /* walk all entries and refcount-- with GF_REF_PUT() */ + dict_foreach(old_cache_dict, auth_cache_entry_purge, NULL); + dict_unref(old_cache_dict); +out: + return; +} + +/** + * is_nfs_fh_cached_and_writeable -- Checks if an NFS FH is cached for the given + * host + * @cache: The fh cache + * @host_addr: Address to use in lookup + * @fh: The fh to use in lookup + * + * + * @return: TRUE if cached, FALSE otherwise + * + */ +gf_boolean_t +is_nfs_fh_cached(struct auth_cache *cache, struct nfs3_fh *fh, + const char *host_addr) +{ + int ret = 0; + time_t timestamp = 0; + gf_boolean_t cached = _gf_false; + gf_boolean_t can_write = _gf_false; + + if (!fh) + goto out; + + ret = auth_cache_lookup(cache, fh, host_addr, ×tamp, &can_write); + cached = (ret == ENTRY_FOUND); + +out: + return cached; +} + +/** + * is_nfs_fh_cached_and_writeable -- Checks if an NFS FH is cached for the given + * host and writable + * @cache: The fh cache + * @host_addr: Address to use in lookup + * @fh: The fh to use in lookup + * + * + * @return: TRUE if cached & writable, FALSE otherwise + * + */ +gf_boolean_t +is_nfs_fh_cached_and_writeable(struct auth_cache *cache, struct nfs3_fh *fh, + const char *host_addr) +{ + int ret = 0; + time_t timestamp = 0; + gf_boolean_t cached = _gf_false; + gf_boolean_t writable = _gf_false; + + if (!fh) + goto out; + + ret = auth_cache_lookup(cache, fh, host_addr, ×tamp, &writable); + cached = ((ret == ENTRY_FOUND) && writable); + +out: + return cached; +} + +/** + * cache_nfs_fh -- Places the nfs file handle in the underlying dict as we are + * using as our cache. The key is "exportid:gfid:host_addr", the + * value is an entry struct containing the export item that + * was authorized for the operation and the file handle that was + * authorized. + * + * @cache: The cache to place fh's in + * @fh : The fh to cache + * @host_addr: The address of the host + * @export_item: The export item that was authorized + * + */ +int +cache_nfs_fh(struct auth_cache *cache, struct nfs3_fh *fh, + const char *host_addr, struct export_item *export_item) +{ + int ret = -EINVAL; + char *hashkey = NULL; + time_t timestamp = 0; + gf_boolean_t can_write = _gf_false; + struct auth_cache_entry *entry = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS, host_addr, out); + GF_VALIDATE_OR_GOTO(GF_NFS, cache, out); + GF_VALIDATE_OR_GOTO(GF_NFS, fh, out); + + /* If we could already find it in the cache, just return */ + ret = auth_cache_lookup(cache, fh, host_addr, ×tamp, &can_write); + if (ret == 0) { + gf_msg_trace(GF_NFS, 0, + "found cached auth/fh for host " + "%s", + host_addr); + goto out; + } + + hashkey = make_hashkey(fh, host_addr); + if (!hashkey) { + ret = -ENOMEM; + goto out; + } + + entry = auth_cache_entry_init(); + if (!entry) { + ret = -ENOMEM; + goto out; + } + + entry->timestamp = gf_time(); + /* Update entry->item if it is pointing to a different export_item */ + if (entry->item && entry->item != export_item) { + GF_REF_PUT(entry->item); + } + entry->item = GF_REF_GET(export_item); + + ret = auth_cache_add(cache, hashkey, entry); + GF_REF_PUT(entry); + if (ret) + goto out; + + gf_msg_trace(GF_NFS, 0, "Caching file-handle (%s)", host_addr); + ret = 0; + +out: + GF_FREE(hashkey); + + return ret; +} diff --git a/xlators/nfs/server/src/auth-cache.h b/xlators/nfs/server/src/auth-cache.h new file mode 100644 index 00000000000..4c9d09207f8 --- /dev/null +++ b/xlators/nfs/server/src/auth-cache.h @@ -0,0 +1,52 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _AUTH_CACHE_H_ +#define _AUTH_CACHE_H_ + +#include "nfs-mem-types.h" +#include "exports.h" +#include <glusterfs/dict.h> +#include "nfs3.h" + +struct auth_cache { + gf_lock_t lock; /* locking for the dict (and entries) */ + dict_t *cache_dict; /* Dict holding fh -> authcache_entry */ + time_t ttl_sec; /* TTL of the auth cache in seconds */ +}; + +/* Initializes the cache */ +struct auth_cache * +auth_cache_init(time_t ttl_sec); + +/* Inserts FH into cache */ +int +cache_nfs_fh(struct auth_cache *cache, struct nfs3_fh *fh, + const char *host_addr, struct export_item *export_item); + +/* Checks if the filehandle cached & writable */ +gf_boolean_t +is_nfs_fh_cached_and_writeable(struct auth_cache *cache, struct nfs3_fh *fh, + const char *host_addr); + +/* Checks if the filehandle is cached */ +gf_boolean_t +is_nfs_fh_cached(struct auth_cache *cache, struct nfs3_fh *fh, + const char *host_addr); + +/* Purge the cache */ +void +auth_cache_purge(struct auth_cache *cache); + +#endif /* _AUTH_CACHE_H_ */ diff --git a/xlators/nfs/server/src/exports.c b/xlators/nfs/server/src/exports.c new file mode 100644 index 00000000000..d7e39934851 --- /dev/null +++ b/xlators/nfs/server/src/exports.c @@ -0,0 +1,1484 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "exports.h" +#include <glusterfs/hashfn.h> +#include <glusterfs/parse-utils.h> +#include "nfs-messages.h" + +static void +_exp_dict_destroy(dict_t *ng_dict); +static void +_export_options_print(const struct export_options *opts); +static void +_export_options_deinit(struct export_options *opts); +static void +_export_dir_deinit(struct export_dir *dir); + +static struct parser *netgroup_parser; +static struct parser *hostname_parser; +static struct parser *options_parser; + +/** + * _exp_init_parsers -- Initialize parsers to be used in this file + * + * @return: success: 0 + * failure: -1 + */ +static int +_exp_init_parsers() +{ + int ret = -1; + + netgroup_parser = parser_init(NETGROUP_REGEX_PATTERN); + if (!netgroup_parser) + goto out; + + hostname_parser = parser_init(HOSTNAME_REGEX_PATTERN); + if (!hostname_parser) + goto out; + + options_parser = parser_init(OPTIONS_REGEX_PATTERN); + if (!options_parser) + goto out; + + ret = 0; +out: + return ret; +} + +/** + * _exp_deinit_parsers -- Free parsers used in this file + */ +static void +_exp_deinit_parsers() +{ + parser_deinit(netgroup_parser); + parser_deinit(hostname_parser); + parser_deinit(options_parser); +} + +/** + * _export_file_init -- Initialize an exports file structure. + * + * @return : success: Pointer to an allocated exports file struct + * failure: NULL + * + * Not for external use. + */ +struct exports_file * +_exports_file_init() +{ + struct exports_file *file = NULL; + + file = GF_CALLOC(1, sizeof(*file), gf_common_mt_nfs_exports); + if (!file) { + gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate file struct!"); + goto out; + } + + file->exports_dict = dict_new(); + file->exports_map = dict_new(); + if (!file->exports_dict || !file->exports_map) { + gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate dict!"); + goto free_and_out; + } + + goto out; + +free_and_out: + if (file->exports_dict) + dict_unref(file->exports_dict); + + GF_FREE(file); + file = NULL; +out: + return file; +} + +/** + * _exp_file_dict_destroy -- Delete each item in the dict + * + * @dict : Dict to free elements from + * @key : Key in the dict we are on + * @val : Value associated with that dict + * @tmp : Not used + * + * Not for external use. + */ +static int +_exp_file_dict_destroy(dict_t *dict, char *key, data_t *val, void *tmp) +{ + struct export_dir *dir = NULL; + + GF_VALIDATE_OR_GOTO(GF_EXP, dict, out); + + if (val) { + dir = (struct export_dir *)val->data; + + if (dir) { + _export_dir_deinit(dir); + val->data = NULL; + } + dict_del(dict, key); + } + +out: + return 0; +} + +/** + * _exp_file_deinit -- Free memory used by an export file + * + * @expfile : Pointer to the exports file to free + * + * Externally usable. + */ +void +exp_file_deinit(struct exports_file *expfile) +{ + if (!expfile) + goto out; + + if (expfile->exports_dict) { + dict_foreach(expfile->exports_dict, _exp_file_dict_destroy, NULL); + dict_unref(expfile->exports_dict); + } + + if (expfile->exports_map) { + dict_foreach(expfile->exports_map, _exp_file_dict_destroy, NULL); + dict_unref(expfile->exports_map); + } + + GF_FREE(expfile->filename); + GF_FREE(expfile); +out: + return; +} + +/** + * _export_dir_init -- Initialize an export directory structure. + * + * @return : success: Pointer to an allocated exports directory struct + * failure: NULL + * + * Not for external use. + */ +static struct export_dir * +_export_dir_init() +{ + struct export_dir *expdir = GF_CALLOC(1, sizeof(*expdir), + gf_common_mt_nfs_exports); + + if (!expdir) + gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate export dir structure!"); + + return expdir; +} + +/** + * _export_dir_deinit -- Free memory used by an export dir + * + * @expdir : Pointer to the export directory to free + * + * Not for external use. + */ +static void +_export_dir_deinit(struct export_dir *dir) +{ + GF_VALIDATE_OR_GOTO(GF_EXP, dir, out); + GF_FREE(dir->dir_name); + _exp_dict_destroy(dir->netgroups); + dict_unref(dir->netgroups); + _exp_dict_destroy(dir->hosts); + dict_unref(dir->hosts); + GF_FREE(dir); + +out: + return; +} + +/** + * _export_item_print -- Print the elements in the export item. + * + * @expdir : Pointer to the item struct to print out. + * + * Not for external use. + */ +static void +_export_item_print(const struct export_item *item) +{ + GF_VALIDATE_OR_GOTO(GF_EXP, item, out); + printf("%s", item->name); + _export_options_print(item->opts); +out: + return; +} + +/** + * _export_item_deinit -- Free memory used by an export item + * + * @expdir : Pointer to the export item to free + * + * Not for external use. + */ +static void +_export_item_deinit(struct export_item *item) +{ + if (!item) + return; + + _export_options_deinit(item->opts); + GF_FREE(item->name); + GF_FREE(item); +} + +/** + * _export_item_init -- Initialize an export item structure + * + * @return : success: Pointer to an allocated exports item struct + * failure: NULL + * + * Not for external use. + */ +static struct export_item * +_export_item_init() +{ + struct export_item *item = GF_CALLOC(1, sizeof(*item), + gf_common_mt_nfs_exports); + + if (item) { + GF_REF_INIT(item, _export_item_deinit); + } else { + gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate export item!"); + } + + return item; +} + +/** + * _export_host_init -- Initialize an export options struct + * + * @return : success: Pointer to an allocated options struct + * failure: NULL + * + * Not for external use. + */ +static struct export_options * +_export_options_init() +{ + struct export_options *opts = GF_CALLOC(1, sizeof(*opts), + gf_common_mt_nfs_exports); + + if (!opts) + gf_msg(GF_EXP, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate options structure!"); + + return opts; +} + +/** + * _export_options_deinit -- Free memory used by a options struct + * + * @expdir : Pointer to the options struct to free + * + * Not for external use. + */ +static void +_export_options_deinit(struct export_options *opts) +{ + if (!opts) + return; + + GF_FREE(opts->anon_uid); + GF_FREE(opts->sec_type); + GF_FREE(opts); +} + +/** + * _export_options_print -- Print the elements in the options struct. + * + * @expdir : Pointer to the options struct to print out. + * + * Not for external use. + */ +static void +_export_options_print(const struct export_options *opts) +{ + GF_VALIDATE_OR_GOTO(GF_EXP, opts, out); + + printf("("); + if (opts->rw) + printf("rw,"); + else + printf("ro,"); + + if (opts->nosuid) + printf("nosuid,"); + + if (opts->root) + printf("root,"); + + if (opts->anon_uid) + printf("anonuid=%s,", opts->anon_uid); + + if (opts->sec_type) + printf("sec=%s,", opts->sec_type); + + printf(") "); +out: + return; +} + +/** + * __exp_dict_free_walk -- Delete each item in the dict + * + * @dict : Dict to free elements from + * @key : Key in the dict we are on + * @val : Value associated with that dict + * @tmp : Not used + * + * Passed as a function pointer to dict_foreach() + * + * Not for external use. + */ +static int +__exp_dict_free_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + if (val) { + GF_REF_PUT((struct export_item *)val->data); + val->data = NULL; + dict_del(dict, key); + } + return 0; +} + +/** + * _exp_dict_destroy -- Delete all the items from this dict + * through the helper function above. + * + * @ng_dict : Dict to free + * + * Not for external use. + */ +static void +_exp_dict_destroy(dict_t *ng_dict) +{ + if (!ng_dict) + goto out; + + dict_foreach(ng_dict, __exp_dict_free_walk, NULL); +out: + return; +} + +/** + * exp_file_dir_from_uuid -- Using a uuid as the key, retrieve an exports + * directory from the file. + * + * @file: File to retrieve data from + * @export_uuid: UUID of the export (mountid in the NFS xlator) + * + * @return : success: Pointer to an export dir struct + * failure: NULL + */ +struct export_dir * +exp_file_dir_from_uuid(const struct exports_file *file, + const uuid_t export_uuid) +{ + char export_uuid_str[512] = { + 0, + }; + data_t *dirdata = NULL; + struct export_dir *dir = NULL; + + gf_uuid_unparse(export_uuid, export_uuid_str); + + dirdata = dict_get(file->exports_map, export_uuid_str); + if (dirdata) + dir = (struct export_dir *)dirdata->data; + + return dir; +} + +/** + * _exp_file_insert -- Insert the exports directory into the file structure + * using the directory as a dict. Also hashes the dirname, + * stores it in a uuid type, converts the uuid type to a + * string and uses that as the key to the exports map. + * The exports map maps an export "uuid" to an export + * directory struct. + * + * @file : Exports file struct to insert into + * @dir : Export directory to insert + * + * Not for external use. + */ +static void +_exp_file_insert(struct exports_file *file, struct export_dir *dir) +{ + data_t *dirdata = NULL; + uint32_t hashedval = 0; + uuid_t export_uuid = { + 0, + }; + char export_uuid_str[512] = { + 0, + }; + char *dirdup = NULL; + + GF_VALIDATE_OR_GOTO(GF_EXP, file, out); + GF_VALIDATE_OR_GOTO(GF_EXP, dir, out); + + dirdata = bin_to_data(dir, sizeof(*dir)); + dict_set(file->exports_dict, dir->dir_name, dirdata); + + dirdup = strdupa(dir->dir_name); + while (strlen(dirdup) > 0 && dirdup[0] == '/') + dirdup++; + + hashedval = SuperFastHash(dirdup, strlen(dirdup)); + memset(export_uuid, 0, sizeof(export_uuid)); + memcpy(export_uuid, &hashedval, sizeof(hashedval)); + gf_uuid_unparse(export_uuid, export_uuid_str); + + dict_set(file->exports_map, export_uuid_str, dirdata); +out: + return; +} + +/** + * __exp_item_print_walk -- Print all the keys and values in the dict + * + * @dict : the dict to walk + * @key : the key in the dict we are currently on + * @val : the value in the dict associated with the key + * @tmp : Additional parameter data (not used) + * + * Passed as a function pointer to dict_foreach (). + * + * Not for external use. + */ +static int +__exp_item_print_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + if (val) + _export_item_print((struct export_item *)val->data); + + return 0; +} + +/** + * __exp_file_print_walk -- Print all the keys and values in the dict + * + * @dict : the dict to walk + * @key : the key in the dict we are currently on + * @val : the value in the dict associated with the key + * @tmp : Additional parameter data (not used) + * + * Passed as a function pointer to dict_foreach (). + * + * Not for external use. + */ +static int +__exp_file_print_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + if (val) { + struct export_dir *dir = (struct export_dir *)val->data; + + printf("%s ", key); + + if (dir->netgroups) + dict_foreach(dir->netgroups, __exp_item_print_walk, NULL); + + if (dir->hosts) + dict_foreach(dir->hosts, __exp_item_print_walk, NULL); + + printf("\n"); + } + return 0; +} + +/** + * exp_file_print -- Print out the contents of the exports file + * + * @file : Exports file to print + * + * Not for external use. + */ +void +exp_file_print(const struct exports_file *file) +{ + GF_VALIDATE_OR_GOTO(GF_EXP, file, out); + dict_foreach(file->exports_dict, __exp_file_print_walk, NULL); +out: + return; +} + +#define __exp_line_get_opt_val(val, equals, ret, errlabel) \ + do { \ + (val) = (equals) + 1; \ + if (!(*(val))) { \ + (ret) = 1; \ + goto errlabel; \ + } \ + } while (0) + +enum gf_exp_parse_status { + GF_EXP_PARSE_SUCCESS = 0, + GF_EXP_PARSE_ITEM_NOT_FOUND = 1, + GF_EXP_PARSE_ITEM_FAILURE = 2, + GF_EXP_PARSE_ITEM_NOT_IN_MOUNT_STATE = 3, + GF_EXP_PARSE_LINE_IGNORING = 4, +}; + +/** + * __exp_line_opt_key_value_parse -- Parse the key-value options in the options + * string. + * + * Given a string like (sec=sys,anonuid=0,rw), to parse, this function + * will get called once with 'sec=sys' and again with 'anonuid=0'. + * It will check for the '=', make sure there is data to be read + * after the '=' and copy the data into the options struct. + * + * @option : An option string like sec=sys or anonuid=0 + * @opts : Pointer to an struct export_options that holds all the export + * options. + * + * @return: success: GF_EXP_PARSE_SUCCESS + * failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure, + * -EINVAL on bad args, -ENOMEM on allocation errors. + * + * Not for external use. + */ +static int +__exp_line_opt_key_value_parse(char *option, struct export_options *opts) +{ + char *equals = NULL; + char *right = NULL; + char *strmatch = option; + int ret = -EINVAL; + + GF_VALIDATE_OR_GOTO(GF_EXP, option, out); + GF_VALIDATE_OR_GOTO(GF_EXP, opts, out); + + equals = strchr(option, '='); + if (!equals) { + ret = GF_EXP_PARSE_ITEM_FAILURE; + goto out; + } + + *equals = 0; + /* Now that an '=' has been found the left side is the option and + * the right side is the value. We simply have to compare those and + * extract it. + */ + if (strcmp(strmatch, "anonuid") == 0) { + *equals = '='; + /* Get the value for this option */ + __exp_line_get_opt_val(right, equals, ret, out); + opts->anon_uid = gf_strdup(right); + GF_CHECK_ALLOC(opts->anon_uid, ret, out); + } else if (strcmp(strmatch, "sec") == 0) { + *equals = '='; + /* Get the value for this option */ + __exp_line_get_opt_val(right, equals, ret, out); + opts->sec_type = gf_strdup(right); + GF_CHECK_ALLOC(opts->sec_type, ret, out); + } else { + *equals = '='; + ret = GF_EXP_PARSE_ITEM_FAILURE; + goto out; + } + + ret = GF_EXP_PARSE_SUCCESS; +out: + return ret; +} + +/** + * __exp_line_opt_parse -- Parse the options part of an + * exports or netgroups string. + * + * @opt_str : The option string to parse + * @exp_opts : Double pointer to the options we are going + * to allocate and setup. + * + * + * @return: success: GF_EXP_PARSE_SUCCESS + * failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure, + * -EINVAL on bad args, -ENOMEM on allocation errors. + * + * Not for external use. + */ +static int +__exp_line_opt_parse(const char *opt_str, struct export_options **exp_opts) +{ + struct export_options *opts = NULL; + char *strmatch = NULL; + int ret = -EINVAL; + char *equals = NULL; + + ret = parser_set_string(options_parser, opt_str); + if (ret < 0) + goto out; + + while ((strmatch = parser_get_next_match(options_parser))) { + if (!opts) { + /* If the options have not been allocated, + * allocate it. + */ + opts = _export_options_init(); + if (!opts) { + ret = -ENOMEM; + parser_unset_string(options_parser); + GF_FREE(strmatch); + goto out; + } + } + + /* First, check for all the boolean options Second, check for + * an '=', and check the available options there. The string + * parsing here gets slightly messy, but the concept itself + * is pretty simple. + */ + equals = strchr(strmatch, '='); + if (strcmp(strmatch, "root") == 0) + opts->root = _gf_true; + else if (strcmp(strmatch, "ro") == 0) + opts->rw = _gf_false; + else if (strcmp(strmatch, "rw") == 0) + opts->rw = _gf_true; + else if (strcmp(strmatch, "nosuid") == 0) + opts->nosuid = _gf_true; + else if (equals) { + ret = __exp_line_opt_key_value_parse(strmatch, opts); + if (ret < 0) { + /* This means invalid key value options were + * specified, or memory allocation failed. + * The ret value gets bubbled up to the caller. + */ + GF_FREE(strmatch); + parser_unset_string(options_parser); + _export_options_deinit(opts); + goto out; + } + } else { + /* Cannot change to gf_msg. + * gf_msg not giving output to STDOUT + * Bug id : BZ1215017 + */ + gf_log(GF_EXP, GF_LOG_WARNING, + "Could not find any valid options for " + "string: %s", + strmatch); + } + GF_FREE(strmatch); + } + + if (!opts) { + /* If opts is not allocated + * that means no matches were found + * which is a parse error. Not marking + * it as "not found" because it is a parse + * error to not have options. + */ + ret = GF_EXP_PARSE_ITEM_FAILURE; + parser_unset_string(options_parser); + goto out; + } + + *exp_opts = opts; + parser_unset_string(options_parser); + ret = GF_EXP_PARSE_SUCCESS; +out: + return ret; +} + +/** + * __exp_line_ng_host_str_parse -- Parse the netgroup or host string + * + * e.g. @mygroup(<options>), parsing @mygroup and (<options>) + * or myhost001.dom(<options>), parsing myhost001.dom and (<options>) + * + * @line : The line to parse + * @exp_item : Double pointer to a struct export_item + * + * @return: success: GF_PARSE_SUCCESS + * failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure, + * -EINVAL on bad args, -ENOMEM on allocation errors. + * + * Not for external use. + */ +static int +__exp_line_ng_host_str_parse(char *str, struct export_item **exp_item) +{ + struct export_item *item = NULL; + int ret = -EINVAL; + char *parens = NULL; + char *optstr = NULL; + struct export_options *exp_opts = NULL; + char *item_name = NULL; + + GF_VALIDATE_OR_GOTO(GF_EXP, str, out); + GF_VALIDATE_OR_GOTO(GF_EXP, exp_item, out); + + /* A netgroup/host string looks like this: + * @test(sec=sys,rw,anonuid=0) or host(sec=sys,rw,anonuid=0) + * We want to extract the name, 'test' or 'host' + * Again, we could setup a regex and use it here, + * but its simpler to find the '(' and copy until + * there. + */ + parens = strchr(str, '('); + if (!parens) { + /* Parse error if there are no parens. */ + ret = GF_EXP_PARSE_ITEM_FAILURE; + goto out; + } + + *parens = '\0'; /* Temporarily terminate it so we can do a copy */ + + if (strlen(str) > FQDN_MAX_LEN) { + ret = GF_EXP_PARSE_ITEM_FAILURE; + goto out; + } + + /* Strip leading whitespaces */ + while (*str == ' ' || *str == '\t') + str++; + + item_name = gf_strdup(str); + GF_CHECK_ALLOC(item_name, ret, out); + + gf_msg_trace(GF_EXP, 0, "found hostname/netgroup: %s", item_name); + + /* Initialize an export item for this */ + item = _export_item_init(); + GF_CHECK_ALLOC(item, ret, free_and_out); + item->name = item_name; + + *parens = '('; /* Restore the string */ + + /* Options start at the parentheses */ + optstr = parens; + + ret = __exp_line_opt_parse(optstr, &exp_opts); + if (ret != 0) { + /* Bubble up the error to the caller */ + GF_REF_PUT(item); + goto out; + } + + item->opts = exp_opts; + + *exp_item = item; + + ret = GF_EXP_PARSE_SUCCESS; + goto out; + +free_and_out: + GF_FREE(item_name); +out: + return ret; +} + +/** + * __exp_line_ng_parse -- Extract the netgroups in the line + * and call helper functions to parse + * the string. + * + * The call chain goes like this: + * + * 1) __exp_line_ng_parse ("/test @test(sec=sys,rw,anonuid=0)") + * 2) __exp_line_ng_str_parse ("@test(sec=sys,rw,anonuid=0)"); + * 3) __exp_line_opt_parse("(sec=sys,rw,anonuid=0)"); + * + * + * @line : The line to parse + * @ng_dict : Double pointer to the dict we want to + * insert netgroups into. + * + * Allocates the dict, extracts netgroup strings from the line, + * parses them into a struct export_item structure and inserts + * them in the dict. + * + * @return: success: GF_EXP_PARSE_SUCCESS + * failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure, + * GF_EXP_PARSE_ITEM_NOT_FOUND if the netgroup was not found + * -EINVAL on bad args, -ENOMEM on allocation errors. + * + * Not for external use. + */ +static int +__exp_line_ng_parse(const char *line, dict_t **ng_dict) +{ + dict_t *netgroups = NULL; + char *strmatch = NULL; + int ret = -EINVAL; + struct export_item *exp_ng = NULL; + data_t *ngdata = NULL; + + GF_VALIDATE_OR_GOTO(GF_EXP, line, out); + GF_VALIDATE_OR_GOTO(GF_EXP, ng_dict, out); + + *ng_dict = NULL; /* Will be set if parse is successful */ + + /* Initialize a parser with the line to parse + * and the regex used to parse it. + */ + ret = parser_set_string(netgroup_parser, line); + if (ret < 0) { + goto out; + } + + gf_msg_trace(GF_EXP, 0, "parsing line: %s", line); + + while ((strmatch = parser_get_next_match(netgroup_parser))) { + if (!netgroups) { + /* Allocate a new dict to store the netgroups. */ + netgroups = dict_new(); + if (!netgroups) { + ret = -ENOMEM; + goto free_and_out; + } + } + + gf_msg_trace(GF_EXP, 0, "parsing netgroup: %s", strmatch); + + ret = __exp_line_ng_host_str_parse(strmatch, &exp_ng); + + if (ret != 0) { + /* Parsing or other critical errors. + * caller will handle return value. + */ + _exp_dict_destroy(netgroups); + goto free_and_out; + } + + ngdata = bin_to_data(exp_ng, sizeof(*exp_ng)); + dict_set(netgroups, exp_ng->name, ngdata); + + /* Free this matched string and continue parsing. */ + GF_FREE(strmatch); + } + + /* If the netgroups dict was not allocated, then we know that + * no matches were found. + */ + if (!netgroups) { + ret = GF_EXP_PARSE_ITEM_NOT_FOUND; + parser_unset_string(netgroup_parser); + goto out; + } + + ret = GF_EXP_PARSE_SUCCESS; + *ng_dict = netgroups; + +free_and_out: + parser_unset_string(netgroup_parser); + GF_FREE(strmatch); +out: + return ret; +} + +/** + * __exp_line_host_parse -- Extract the hosts in the line + * and call helper functions to parse + * the string. + * + * The call chain goes like this: + * + * 1) __exp_line_host_parse ("/test hostip(sec=sys,rw,anonuid=0)") + * 2) __exp_line_ng_host_str_parse ("hostip(sec=sys,rw,anonuid=0)"); + * 3) __exp_line_opt_parse("(sec=sys,rw,anonuid=0)"); + * + * + * @line : The line to parse + * @ng_dict : Double pointer to the dict we want to + * insert hosts into. + * + * Allocates the dict, extracts host strings from the line, + * parses them into a struct export_item structure and inserts + * them in the dict. + * + * @return: success: GF_EXP_PARSE_SUCCESS + * failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure, + * GF_EXP_PARSE_ITEM_NOT_FOUND if the host was not found, + * -EINVAL on bad args, -ENOMEM on allocation errors. + * + * Not for external use. + */ +static int +__exp_line_host_parse(const char *line, dict_t **host_dict) +{ + dict_t *hosts = NULL; + char *strmatch = NULL; + int ret = -EINVAL; + struct export_item *exp_host = NULL; + data_t *hostdata = NULL; + + GF_VALIDATE_OR_GOTO(GF_EXP, line, out); + GF_VALIDATE_OR_GOTO(GF_EXP, host_dict, out); + + *host_dict = NULL; + + /* Initialize a parser with the line to parse and the regex used to + * parse it. + */ + ret = parser_set_string(hostname_parser, line); + if (ret < 0) { + goto out; + } + + gf_msg_trace(GF_EXP, 0, "parsing line: %s", line); + + while ((strmatch = parser_get_next_match(hostname_parser))) { + if (!hosts) { + /* Allocate a new dictto store the netgroups. */ + hosts = dict_new(); + GF_CHECK_ALLOC(hosts, ret, free_and_out); + } + + gf_msg_trace(GF_EXP, 0, "parsing hostname: %s", strmatch); + + ret = __exp_line_ng_host_str_parse(strmatch, &exp_host); + + if (ret != 0) { + /* Parsing or other critical error, free allocated + * memory and exit. The caller will handle the errors. + */ + _exp_dict_destroy(hosts); + goto free_and_out; + } + + /* Insert export item structure into the hosts dict. */ + hostdata = bin_to_data(exp_host, sizeof(*exp_host)); + dict_set(hosts, exp_host->name, hostdata); + + /* Free this matched string and continue parsing.*/ + GF_FREE(strmatch); + } + + /* If the hosts dict was not allocated, then we know that + * no matches were found. + */ + if (!exp_host) { + ret = GF_EXP_PARSE_ITEM_NOT_FOUND; + parser_unset_string(hostname_parser); + goto out; + } + + ret = GF_EXP_PARSE_SUCCESS; + *host_dict = hosts; + +free_and_out: + parser_unset_string(hostname_parser); + GF_FREE(strmatch); +out: + return ret; +} + +/** + * __exp_line_dir_parse -- Extract directory name from a line in the exports + * file. + * + * @line : The line to parse + * @dirname : Double pointer to the string we need to hold the directory name. + * If the parsing failed, the string will point to NULL, otherwise + * it will point to a valid memory region that is allocated by + * this function. + * @check_ms: If this variable is set then we cross check the directory line + * with what's in gluster's vol files and reject them if they don't + * match. + * + * @return : success: GF_EXP_PARSE_SUCCESS + * failure: GF_EXP_PARSE_ITEM_FAILURE on parse failure, + * -EINVAL on bad arguments, -ENOMEM on allocation failures, + * GF_EXP_PARSE_ITEM_NOT_IN_MOUNT_STATE if we failed to match + * with gluster's mountstate. + * + * The caller is responsible for freeing memory allocated by this function + * + * Not for external use. + */ +static int +__exp_line_dir_parse(const char *line, char **dirname, struct mount3_state *ms) +{ + char *dir = NULL; + char *delim = NULL; + int ret = -EINVAL; + char *linedup = NULL; + struct mnt3_export *mnt3export = NULL; + size_t dirlen = 0; + + GF_VALIDATE_OR_GOTO(GF_EXP, line, out); + GF_VALIDATE_OR_GOTO(GF_EXP, dirname, out); + + /* Duplicate the line because we don't + * want to modify the original string. + */ + linedup = strdupa(line); + + /* We use strtok_r () here to split the string by space/tab and get the + * the result. We only need the first result of the split. + * a simple task. It is worth noting that dirnames always have to be + * validated against gluster's vol files so if they don't + * match it will be rejected. + */ + dir = linedup; + delim = linedup + strcspn(linedup, " \t"); + *delim = 0; + + if (ms) { + /* Match the directory name with an existing + * export in the mount state. + */ + mnt3export = mnt3_mntpath_to_export(ms, dir, _gf_true); + if (!mnt3export) { + gf_msg_debug(GF_EXP, 0, + "%s not in mount state, " + "ignoring!", + dir); + ret = GF_EXP_PARSE_ITEM_NOT_IN_MOUNT_STATE; + goto out; + } + } + + /* Directories can be 1024 bytes in length, check + * that the argument provided adheres to + * that restriction. + */ + if (strlen(dir) > DIR_MAX_LEN) { + ret = -EINVAL; + goto out; + } + + /* Copy the result of the split */ + dir = gf_strdup(dir); + GF_CHECK_ALLOC(dir, ret, out); + + /* Ensure that trailing slashes are stripped before storing the name */ + dirlen = strlen(dir); + if (dirlen > 0 && dir[dirlen - 1] == '/') + dir[dirlen - 1] = '\0'; + + /* Set the argument to point to the allocated string */ + *dirname = dir; + ret = GF_EXP_PARSE_SUCCESS; +out: + return ret; +} + +/** + * _exp_line_parse -- Parse a line in an exports file into a structure + * that holds all the parts of the line. An exports + * structure has a dict of netgroups and a dict of hosts. + * + * An export line looks something like this /test @test(sec=sys,rw,anonuid=0) + * or /test @test(sec=sys,rw,anonuid=0) hostA(sec=sys,rw,anonuid=0), etc. + * + * We use regexes to parse the line into three separate pieces: + * 1) The directory (exports.h -- DIRECTORY_REGEX_PATTERN) + * 2) The netgroup if it exists (exports.h -- NETGROUP_REGEX_PATTERN) + * 3) The host if it exists (exports.h -- HOST_REGEX_PATTERN) + * + * In this case, the netgroup would be @test(sec=sys,rw,anonuid=0) + * and the host would be hostA(sec=sys,rw,anonuid=0). + * + * @line : The line to parse + * @dir : Double pointer to the struct we need to parse the line into. + * If the parsing failed, the struct will point to NULL, + * otherwise it will point to a valid memory region that is + * allocated by this function. + * @parse_full : This parameter tells us whether we should parse all the lines + * in the file, even if they are not present in gluster's config. + * The gluster config holds the volumes that it exports so + * if parse_full is set to FALSE then we will ensure that + * the export file structure holds only those volumes + * that gluster has exported. It is important to note that + * If gluster exports a volume named '/test', '/test' and all + * of its subdirectories that may be in the exports file + * are valid exports. + * @ms : The mount state that holds the list of volumes that gluster + * currently exports. + * + * @return : success: GF_EXP_PARSE_SUCCESS on success, -EINVAL on bad arguments, + * -ENOMEM on memory allocation errors, + * GF_EXP_PARSE_LINE_IGNORING if we ignored the line, + * GF_EXP_PARSE_ITEM_FAILURE if there was error parsing + * failure: NULL + * + * The caller is responsible for freeing memory allocated by this function + * The caller should free this memory using the _exp_dir_deinit () function. + * + * Not for external use. + */ +static int +_exp_line_parse(const char *line, struct export_dir **dir, + gf_boolean_t parse_full, struct mount3_state *ms) +{ + struct export_dir *expdir = NULL; + char *dirstr = NULL; + dict_t *netgroups = NULL; + dict_t *hosts = NULL; + int ret = -EINVAL; + gf_boolean_t netgroups_failed = _gf_false; + + GF_VALIDATE_OR_GOTO(GF_EXP, line, out); + GF_VALIDATE_OR_GOTO(GF_EXP, dir, out); + + if (*line == '#' || *line == ' ' || *line == '\t' || *line == '\0' || + *line == '\n') { + ret = GF_EXP_PARSE_LINE_IGNORING; + goto out; + } + + expdir = _export_dir_init(); + if (!expdir) { + *dir = NULL; + ret = -ENOMEM; + goto out; + } + + /* Get the directory string from the line */ + ret = __exp_line_dir_parse(line, &dirstr, ms); + if (ret < 0) { + gf_msg(GF_EXP, GF_LOG_ERROR, 0, NFS_MSG_PARSE_DIR_FAIL, + "Parsing directory failed: %s", strerror(-ret)); + /* If parsing the directory failed, + * we should simply fail because there's + * nothing else we can extract from the string to make + * the data valuable. + */ + goto free_and_out; + } + + /* Set the dir str */ + expdir->dir_name = dirstr; + + /* Parse the netgroup part of the string */ + ret = __exp_line_ng_parse(line, &netgroups); + if (ret < 0) { + gf_msg(GF_EXP, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Critical error: %s", strerror(-ret)); + /* Return values less than 0 + * indicate critical failures (null parameters, + * failure to allocate memory, etc). + */ + goto free_and_out; + } + if (ret != 0) { + if (ret == GF_EXP_PARSE_ITEM_FAILURE) + /* Cannot change to gf_msg. + * gf_msg not giving output to STDOUT + * Bug id : BZ1215017 + */ + gf_log(GF_EXP, GF_LOG_WARNING, "Error parsing netgroups for: %s", + line); + /* Even though parsing failed for the netgroups we should let + * host parsing proceed. + */ + netgroups_failed = _gf_true; + } + + /* Parse the host part of the string */ + ret = __exp_line_host_parse(line, &hosts); + if (ret < 0) { + gf_msg(GF_EXP, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Critical error: %s", strerror(-ret)); + goto free_and_out; + } + if (ret != 0) { + if (ret == GF_EXP_PARSE_ITEM_FAILURE) + gf_msg(GF_EXP, GF_LOG_WARNING, 0, NFS_MSG_PARSE_FAIL, + "Error parsing hosts for: %s", line); + /* If netgroups parsing failed, AND + * host parsing failed, then there's something really + * wrong with this line, so we're just going to + * log it and fail out. + */ + if (netgroups_failed) + goto free_and_out; + } + + expdir->hosts = hosts; + expdir->netgroups = netgroups; + *dir = expdir; + goto out; + +free_and_out: + _export_dir_deinit(expdir); +out: + return ret; +} + +struct export_item * +exp_dir_get_netgroup(const struct export_dir *expdir, const char *netgroup) +{ + struct export_item *lookup_res = NULL; + data_t *dict_res = NULL; + + GF_VALIDATE_OR_GOTO(GF_EXP, expdir, out); + GF_VALIDATE_OR_GOTO(GF_EXP, netgroup, out); + + if (!expdir->netgroups) + goto out; + + dict_res = dict_get(expdir->netgroups, (char *)netgroup); + if (!dict_res) { + gf_msg_debug(GF_EXP, 0, "%s not found for %s", netgroup, + expdir->dir_name); + goto out; + } + + lookup_res = (struct export_item *)dict_res->data; +out: + return lookup_res; +} +/** + * exp_dir_get_host -- Given a host string and an exports directory structure, + * find and return an struct export_item structure that + * represents the requested host. + * + * @expdir: Export directory to lookup from + * @host : Host string to lookup + * + * @return: success: Pointer to a export item structure + * failure: NULL + */ +struct export_item * +exp_dir_get_host(const struct export_dir *expdir, const char *host) +{ + struct export_item *lookup_res = NULL; + data_t *dict_res = NULL; + + GF_VALIDATE_OR_GOTO(GF_EXP, expdir, out); + GF_VALIDATE_OR_GOTO(GF_EXP, host, out); + + if (!expdir->hosts) + goto out; + + dict_res = dict_get(expdir->hosts, (char *)host); + if (!dict_res) { + gf_msg_debug(GF_EXP, 0, "%s not found for %s", host, expdir->dir_name); + + /* Check if wildcards are allowed for the host */ + dict_res = dict_get(expdir->hosts, "*"); + if (!dict_res) { + goto out; + } + } + + lookup_res = (struct export_item *)dict_res->data; +out: + return lookup_res; +} + +/** + * exp_file_get_dir -- Return an export dir given a directory name + * Does a lookup from the dict in the file structure. + * + * @file : Exports file structure to lookup from + * @dir : Directory name to lookup + * + * @return : success: Pointer to an export directory structure + * failure: NULL + */ +struct export_dir * +exp_file_get_dir(const struct exports_file *file, const char *dir) +{ + struct export_dir *lookup_res = NULL; + data_t *dict_res = NULL; + char *dirdup = NULL; + size_t dirlen = 0; + + GF_VALIDATE_OR_GOTO(GF_EXP, file, out); + GF_VALIDATE_OR_GOTO(GF_EXP, dir, out); + + dirlen = strlen(dir); + if (dirlen <= 0) + goto out; + + dirdup = (char *)dir; /* Point at the directory */ + + /* If directories don't contain a leading slash */ + if (*dir != '/') { + dirdup = alloca(dirlen + 2); /* Leading slash & null byte */ + snprintf(dirdup, dirlen + 2, "/%s", dir); + } + + dict_res = dict_get(file->exports_dict, dirdup); + if (!dict_res) { + gf_msg_debug(GF_EXP, 0, "%s not found in %s", dirdup, file->filename); + goto out; + } + + lookup_res = (struct export_dir *)dict_res->data; +out: + return lookup_res; +} + +/** + * exp_file_parse -- Parse an exports file into a structure + * that can be looked up through simple + * function calls. + * + * @filepath: Path to the exports file + * @ms : Current mount state (useful to match with gluster vol files) + * + * @return : success: 0 + * failure: -1 on parsing failure, -EINVAL on bad arguments, + * -ENOMEM on allocation failures. + * + * The caller is responsible for freeing memory allocated by this function. + * The caller should free this memory using the exp_file_deinit () function. + * Calling GF_FREE ( ) on the pointer will NOT free all the allocated memory. + * + * Externally usable. + */ +int +exp_file_parse(const char *filepath, struct exports_file **expfile, + struct mount3_state *ms) +{ + FILE *fp = NULL; + struct exports_file *file = NULL; + size_t len = 0; + int ret = -EINVAL; + unsigned long line_number = 0; + char *line = NULL; + struct export_dir *expdir = NULL; + + /* Sets whether we we should parse the entire file or just that which + * is present in the mount state */ + gf_boolean_t parse_complete_file = _gf_false; + + GF_VALIDATE_OR_GOTO(GF_EXP, expfile, parse_done); + + if (!ms) { + /* If mount state is null that means that we + * should go through and parse the whole file + * since we don't have anything to compare against. + */ + parse_complete_file = _gf_true; + } + + fp = fopen(filepath, "r"); + if (!fp) { + ret = -errno; + goto parse_done; + } + + ret = _exp_init_parsers(); + if (ret < 0) + goto parse_done; + + /* Process the file line by line, with each line being parsed into + * an struct export_dir struct. If 'parse_complete_file' is set to TRUE + * then + */ + while (getline(&line, &len, fp) != -1) { + line_number++; /* Keeping track of line number allows us to + * to log which line numbers were wrong + */ + strtok(line, "\n"); /* removes the newline character from + * the line + */ + + /* Parse the line from the file into an struct export_dir + * structure. The process is as follows: + * Given a line like : + * "/vol @test(sec=sys,rw,anonuid=0) 10.35.11.31(sec=sys,rw)" + * + * This function will allocate an export dir and set its name + * to '/vol', using the function _exp_line_dir_parse (). + * + * Then it will extract the netgroups from the line, in this + * case it would be '@test(sec=sys,rw,anonuid=0)', and set the + * item structure's name to '@test'. + * It will also extract the options from that string and parse + * them into an struct export_options which will be pointed + * to by the item structure. This will be put into a dict + * which will be pointed to by the export directory structure. + * + * The same process happens above for the host string + * '10.35.11.32(sec=sys,rw)' + */ + ret = _exp_line_parse(line, &expdir, parse_complete_file, ms); + if (ret == -ENOMEM) { + /* If we get memory allocation errors, we really should + * not continue parsing, so just free the allocated + * memory and exit. + */ + goto free_and_done; + } + + if (ret < 0) { + gf_msg(GF_EXP, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse line #%lu", line_number); + continue; /* Skip entering this line and continue */ + } + + if (ret == GF_EXP_PARSE_LINE_IGNORING) { + /* This just means the line was empty or started with a + * '#' or a ' ' and we are ignoring it. + */ + gf_msg_debug(GF_EXP, 0, + "Ignoring line #%lu because it started " + "with a %c", + line_number, *line); + continue; + } + + if (!file) { + file = _exports_file_init(); + GF_CHECK_ALLOC_AND_LOG(GF_EXP, file, ret, + "Allocation error while " + "allocating file struct", + free_and_done); + + file->filename = gf_strdup(filepath); + GF_CHECK_ALLOC_AND_LOG(GF_EXP, file, ret, + "Allocation error while " + "duping filepath", + free_and_done); + } + + /* If the parsing is successful store the export directory + * in the file structure. + */ + _exp_file_insert(file, expdir); + } + + /* line got allocated through getline(), don't use GF_FREE() for it */ + free(line); + + *expfile = file; + goto parse_done; + +free_and_done: + if (file) + exp_file_deinit(file); + _export_dir_deinit(expdir); + +parse_done: + if (fp) + fclose(fp); + _exp_deinit_parsers(); + return ret; +} diff --git a/xlators/nfs/server/src/exports.h b/xlators/nfs/server/src/exports.h new file mode 100644 index 00000000000..ad35ad8c3a0 --- /dev/null +++ b/xlators/nfs/server/src/exports.h @@ -0,0 +1,93 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _EXPORTS_H_ +#define _EXPORTS_H_ + +#include "nfs-mem-types.h" +#include <glusterfs/dict.h> +#include "nfs.h" + +#define GF_EXP GF_NFS "-exports" + +#define NETGROUP_REGEX_PATTERN "(@([a-zA-Z0-9\\(=, .])+)())" +#define HOSTNAME_REGEX_PATTERN "[[:space:]]([a-zA-Z0-9.\\(=,*/:)-]+)" +#define OPTIONS_REGEX_PATTERN "([a-zA-Z0-9=\\.]+)" + +#define NETGROUP_MAX_LEN 128 +#define FQDN_MAX_LEN 256 + +#define SEC_OPTION_MAX 10 +#define UID_MAX_LEN 6 + +#define DIR_MAX_LEN 1024 + +/* The following 2 definitions are in mount3.h + * but we don't want to include it because mount3.h + * depends on structs in this file so we get a cross + * dependency. + */ +struct mount3_state; + +extern struct mnt3_export * +mnt3_mntpath_to_export(struct mount3_state *ms, const char *dirpath, + gf_boolean_t export_parsing_match); + +struct export_options { + gf_boolean_t rw; /* Read-write option */ + gf_boolean_t nosuid; /* nosuid option */ + gf_boolean_t root; /* root option */ + char *anon_uid; /* anonuid option */ + char *sec_type; /* X, for sec=X */ +}; + +struct export_item { + char *name; /* Name of the export item */ + struct export_options *opts; /* NFS Options */ + GF_REF_DECL; +}; + +struct export_dir { + char *dir_name; /* Directory */ + dict_t *netgroups; /* Dict of netgroups */ + dict_t *hosts; /* Dict of hosts */ +}; + +struct exports_file { + char *filename; /* Filename */ + dict_t *exports_dict; /* Dict of export_dir_t */ + dict_t *exports_map; /* Map of SuperFastHash(<export>) -> expdir */ +}; + +void +exp_file_deinit(struct exports_file *expfile); + +int +exp_file_parse(const char *filepath, struct exports_file **expfile, + struct mount3_state *ms); + +struct export_dir * +exp_file_get_dir(const struct exports_file *file, const char *dir); + +struct export_item * +exp_dir_get_host(const struct export_dir *expdir, const char *host); + +struct export_item * +exp_dir_get_netgroup(const struct export_dir *expdir, const char *netgroup); + +struct export_dir * +exp_file_dir_from_uuid(const struct exports_file *file, + const uuid_t export_uuid); + +#endif /* _EXPORTS_H_ */ diff --git a/xlators/nfs/server/src/mount3-auth.c b/xlators/nfs/server/src/mount3-auth.c new file mode 100644 index 00000000000..10e57c84cdb --- /dev/null +++ b/xlators/nfs/server/src/mount3-auth.c @@ -0,0 +1,642 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +/* This file contains code for handling mount authentication. + * The primary structure here is 'mnt3_auth_params' which contains + * 3 important fields: 1) Pointer to a netgroups file struct, 2) Pointer to an + * exports file struct. 3) Pointer to a mount state struct. + * + * - The auth parameter struct belongs to a mount state so the mount state + * pointer represents the mount state that this auth parameter struct belongs + * to. + * + * - Currently, the only supported mount auth parameters are an exports file + * and a netgroups file. The two pointers in the struct represent the files + * we are to authenticate against. + * + * - To initialize a struct, make a call to mnt3_auth_params_init () with a mnt + * state as a parameter. + * + * - To set an exports file authentication parameter, call + * mnt3_auth_set_exports_auth () with an exports file as a parameter. + * + * - Same goes for the netgroups file parameter, except use the netgroups file + * as the parameter. + */ + +#include "mount3-auth.h" +#include "exports.h" +#include "netgroups.h" +#include <glusterfs/mem-pool.h> +#include "nfs-messages.h" + +/** + * mnt3_auth_params_init -- Initialize the mount3 authorization parameters + * and return the allocated struct. The mount3_state + * parameter is pointed to by a field in the struct. + * + * @ms: Mount state that is needed for auth. + * + * @return: success: Pointer to the allocated struct + * failure: NULL + * + * For external use. + */ +struct mnt3_auth_params * +mnt3_auth_params_init(struct mount3_state *ms) +{ + struct mnt3_auth_params *auth_params = NULL; + + auth_params = GF_MALLOC(sizeof(*auth_params), gf_nfs_mt_mnt3_auth_params); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, auth_params, out); + + auth_params->ngfile = NULL; + auth_params->expfile = NULL; + auth_params->ms = ms; +out: + return auth_params; +} + +/** + * mnt3_auth_params_deinit -- Free the memory used by the struct. + * + * @auth_params: Pointer to the struct we want to free + * + * For external use. + */ +void +mnt3_auth_params_deinit(struct mnt3_auth_params *auth_params) +{ + if (!auth_params) + goto out; + + /* Atomically set the auth params in the mount state to NULL + * so subsequent fops will be denied while the auth params + * are being cleaned up. + */ + (void)__sync_lock_test_and_set(&auth_params->ms->auth_params, NULL); + + ng_file_deinit(auth_params->ngfile); + exp_file_deinit(auth_params->expfile); + auth_params->ms = NULL; + GF_FREE(auth_params); +out: + return; +} + +/** + * mnt3_set_exports_auth -- Set the exports auth file + * + * @auth_params : Pointer to the auth params struct + * @filename : File name to load from disk and parse + * + * @return : success: 0 + * failure: -1 + * + * For external use. + */ +int +mnt3_auth_set_exports_auth(struct mnt3_auth_params *auth_params, + const char *filename) +{ + struct exports_file *expfile = NULL; + struct exports_file *oldfile = NULL; + int ret = -EINVAL; + + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, auth_params, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, filename, out); + + /* Parse the exports file and set the auth parameter */ + ret = exp_file_parse(filename, &expfile, auth_params->ms); + if (ret < 0) { + gf_msg(GF_MNT_AUTH, GF_LOG_ERROR, 0, NFS_MSG_LOAD_PARSE_ERROR, + "Failed to load & parse file" + " %s, see logs for more information", + filename); + goto out; + } + + /* Atomically set the file pointer */ + oldfile = __sync_lock_test_and_set(&auth_params->expfile, expfile); + exp_file_deinit(oldfile); + ret = 0; +out: + return ret; +} + +/** + * mnt3_set_netgroups_auth -- Set netgroups auth file + * + * @auth_params : Pointer to the auth params struct. + * @filename : File name to load from disk and parse + * + * @return : success: 0 + * failure: -1 + * + * For external use. + */ +int +mnt3_auth_set_netgroups_auth(struct mnt3_auth_params *auth_params, + const char *filename) +{ + struct netgroups_file *ngfile = NULL; + struct netgroups_file *oldfile = NULL; + int ret = -EINVAL; + + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, auth_params, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, filename, out); + + ngfile = ng_file_parse(filename); + if (!ngfile) { + gf_msg(GF_MNT_AUTH, GF_LOG_ERROR, 0, NFS_MSG_LOAD_PARSE_ERROR, + "Failed to load file %s, see logs for more " + "information", + filename); + ret = -1; + goto out; + } + + /* Atomically set the file pointer */ + oldfile = __sync_lock_test_and_set(&auth_params->ngfile, ngfile); + ng_file_deinit(oldfile); + ret = 0; +out: + return ret; +} + +/* Struct used to pass parameters to + * _mnt3_auth_subnet_match () which + * checks if an IP matches a subnet + */ +struct _mnt3_subnet_match_s { + char *ip; /* IP address to match */ + struct export_item **host; /* Host structure to set */ +}; + +/** + * _mnt3_auth_subnet_match -- Check if an ip (specified in the parameter tmp) + * is in the subnet specified by key. + * + * @dict: The dict to walk + * @key : The key we are on + * @val : The value we are on + * @tmp : Parameter that points to the above struct + * + */ +static int +_mnt3_auth_subnet_match(dict_t *dict, char *key, data_t *val, void *tmp) +{ + struct _mnt3_subnet_match_s *match = NULL; + + match = (struct _mnt3_subnet_match_s *)tmp; + + if (!match) + return 0; + + if (!match->host) + return 0; + + if (!match->ip) + return 0; + + /* Already found the host */ + if (*(match->host)) + return 0; + + /* Don't process anything that's not in CIDR */ + if (!strchr(key, '/')) + return 0; + + /* Strip out leading whitespaces */ + while (*key == ' ') + key++; + + /* If we found that the IP was in the network, set the host + * to point to the value in the dict. + */ + if (gf_is_ip_in_net(key, match->ip)) { + *(match->host) = (struct export_item *)val->data; + } + return 0; +} + +/** + * _find_host_in_export -- Find a host in the exports file. + * + * Case 1: FH is non-null + * ----------------------- + * The lookup process is two-step: The FH has a mountid which represents the + * export that was mounted by the client. The export is defined as an entry in + * the exports file. The FH's 'mountid' is hashed in the exports file to lookup + * an export directory. + * + * Case 2: FH is null + * ------------------- + * The lookup process is two-step: You need a directory and a hostname + * to do the lookup. We first lookup the export directory in the file + * and then do a lookup on the directory to find the host. If the host + * is not found, we must finally check for subnets and then do a match. + * + * @file: Exports file to lookup in + * @dir : Directory to do the lookup + * @host: Host to lookup in the directory + * + * Not for external use. + */ +static struct export_item * +_mnt3_auth_check_host_in_export(const struct exports_file *file, + const char *dir, const char *host, + struct nfs3_fh *fh) +{ + struct export_dir *expdir = NULL; + struct export_item *host_s = NULL; + struct _mnt3_subnet_match_s snet_match_s = { + 0, + }; + + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, file, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, host, out); + + /* If the filehandle is defined, use that to perform authentication. + * All file operations that need authentication must follow this + * code path. + */ + if (fh) { + expdir = exp_file_dir_from_uuid(file, fh->mountid); + if (!expdir) + goto out; + } else { + /* Get the exports directory from the exports file */ + expdir = exp_file_get_dir(file, dir); + if (!expdir) + goto out; + } + + /* Extract the host from the export directory */ + host_s = exp_dir_get_host(expdir, host); + if (!host_s) + goto subnet_match; + else + goto out; + + /* If the host is not found, we need to walk through the hosts + * in the exports directory and see if any of the "hosts" are actually + * networks (e.g. 10.5.153.0/24). If they are we should match the + * incoming network. + */ +subnet_match: + if (!expdir->hosts) + goto out; + snet_match_s.ip = (char *)host; + snet_match_s.host = &host_s; + dict_foreach(expdir->hosts, _mnt3_auth_subnet_match, &snet_match_s); +out: + return host_s; +} + +/* This struct represents all the parameters necessary to search through a + * netgroups file to find a host. + */ +struct ng_auth_search { + const char *search_for; /* strings to search for */ + gf_boolean_t found; /* mark true once found */ + const struct netgroups_file *file; /* netgroups file to search */ + const char *expdir; + struct export_item *expitem; /* pointer to the export */ + const struct exports_file *expfile; + gf_boolean_t _is_host_dict; /* searching a host dict? */ + struct netgroup_entry *found_entry; /* the entry we found! */ +}; + +/** + * __netgroup_dict_search -- Function to search the netgroups dict. + * + * @dict: The dict we are walking + * @key : The key we are on + * @val : The value associated with that key + * @data: Additional parameters. We pass a pointer to ng_auth_search_s + * + * This is passed as a function pointer to dict_foreach (). + * + * Not for external use. + */ +static int +__netgroup_dict_search(dict_t *dict, char *key, data_t *val, void *data) +{ + struct ng_auth_search *ngsa = NULL; + struct netgroup_entry *ngentry = NULL; + data_t *hdata = NULL; + + /* 'ngsa' is the search params */ + ngsa = (struct ng_auth_search *)data; + ngentry = (struct netgroup_entry *)val->data; + + if (ngsa->_is_host_dict) { + /* If are on a host dict, we can simply hash the search key + * against the host dict and see if we find anything. + */ + hdata = dict_get(dict, (char *)ngsa->search_for); + if (hdata) { + /* If it was found, log the message, mark the search + * params dict as found and return. + */ + gf_msg_debug(GF_MNT_AUTH, errno, + "key %s was hashed " + "and found", + key); + ngsa->found = _gf_true; + ngsa->found_entry = (struct netgroup_entry *)hdata->data; + goto out; + } + } + + /* If the key is what we are searching for, mark the item as + * found and return. + */ + if (strcmp(key, ngsa->search_for) == 0) { + ngsa->found = _gf_true; + ngsa->found_entry = ngentry; + goto out; + } + + /* If we have a netgroup hosts dict, then search the dict using this + * same function. + */ + if (ngentry->netgroup_hosts) { + ngsa->_is_host_dict = _gf_true; + dict_foreach(ngentry->netgroup_hosts, __netgroup_dict_search, ngsa); + } + + /* If that search was successful, just return */ + if (ngsa->found) + goto out; + + /* If we have a netgroup dict, then search the dict using this same + * function. + */ + if (ngentry->netgroup_ngs) { + ngsa->_is_host_dict = _gf_false; + dict_foreach(ngentry->netgroup_ngs, __netgroup_dict_search, ngsa); + } +out: + return 0; +} + +/** + * __export_dir_lookup_netgroup -- Function to search an exports directory + * for a host name. + * + * This function walks all the netgroups & hosts in an export directory + * and tries to match it with the search key. This function calls the above + * netgroup search function to search through the netgroups. + * + * This function is very similar to the above function, but both are necessary + * since we are walking two different dicts. For each netgroup in _this_ dict + * (the exports dict) we are going to find the corresponding netgroups dict + * and walk that (nested) structure until we find the host we are looking for. + * + * @dict: The dict we are walking + * @key : The key we are on + * @val : The value associated with that key + * @data: Additional parameters. We pass a pointer to ng_auth_search_s + * + * This is passed as a function pointer to dict_foreach (). + * + * Not for external use. + */ +static int +__export_dir_lookup_netgroup(dict_t *dict, char *key, data_t *val, void *data) +{ + struct ng_auth_search *ngsa = NULL; /* Search params */ + struct netgroups_file *nfile = NULL; /* Netgroups file to search */ + struct netgroup_entry *ngentry = NULL; /* Entry in the netgroups file */ + struct export_dir *tmpdir = NULL; + + ngsa = (struct ng_auth_search *)data; + nfile = (struct netgroups_file *)ngsa->file; + + GF_ASSERT((*key == '@')); + + /* We use ++key here because keys start with '@' for ngs */ + ngentry = ng_file_get_netgroup(nfile, (key + 1)); + if (!ngentry) { + gf_msg_debug(GF_MNT_AUTH, 0, "%s not found in %s", key, + nfile->filename); + goto out; + } + + tmpdir = exp_file_get_dir(ngsa->expfile, ngsa->expdir); + if (!tmpdir) + goto out; + + ngsa->expitem = exp_dir_get_netgroup(tmpdir, key); + if (!ngsa->expitem) + goto out; + + /* Run through the host dict */ + if (ngentry->netgroup_hosts) { + ngsa->_is_host_dict = _gf_true; + dict_foreach(ngentry->netgroup_hosts, __netgroup_dict_search, ngsa); + } + + /* If the above search was successful, just return */ + if (ngsa->found) + goto out; + + /* Run through the netgroups dict */ + if (ngentry->netgroup_ngs) { + ngsa->_is_host_dict = _gf_false; + dict_foreach(ngentry->netgroup_ngs, __netgroup_dict_search, ngsa); + } +out: + return 0; +} + +/** + * _mnt3_auth_setup_search_param -- This function sets up an ng_auth_search + * struct with host and file as the parameters. + * Host is what we are searching for and file + * is what we are searching in. + * @params: Search params to setup + * @host : The host to set + * @nfile : The netgroups file to set + * + */ +void +_mnt3_auth_setup_search_params(struct ng_auth_search *params, const char *host, + const char *dir, + const struct netgroups_file *nfile, + const struct exports_file *expfile) +{ + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, params, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, host, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, nfile, out); + + params->search_for = host; + params->found = _gf_false; + params->file = nfile; + params->_is_host_dict = _gf_false; + params->found_entry = NULL; + params->expitem = NULL; + params->expfile = expfile; + params->expdir = dir; +out: + return; +} + +/** + * _mnt3_auth_find_host_in_netgroup -- Given a host name for an directory + * find if that hostname is in the + * directory's dict of netgroups. + * @nfile: Netgroups file to search + * @efile: Exports file to search + * @dir : The exports directory name (used to lookup in exports file) + * @host : The host we are searching for + * + * Search procedure: + * + * - Lookup directory string against exports file structure, + * get an exports directory structure. + * - Walk the export file structure's netgroup dict. This dict + * holds each netgroup that is authorized to mount that directory. + * - We then have to walk the netgroup structure, which is a set of + * nested dicts until we find the host we are looking for. + * + * @return: success: Pointer to the netgroup entry found + * failure: NULL + * + * Not for external use. + */ +static struct netgroup_entry * +_mnt3_auth_check_host_in_netgroup(const struct mnt3_auth_params *auth_params, + struct nfs3_fh *fh, const char *host, + const char *dir, struct export_item **item) +{ + struct export_dir *expdir = NULL; + struct ng_auth_search ngsa = { + 0, + }; + struct netgroup_entry *found_entry = NULL; + struct exports_file *efile = auth_params->expfile; + struct netgroups_file *nfile = auth_params->ngfile; + + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, nfile, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, efile, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, host, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, item, out); + + if (fh) { + expdir = exp_file_dir_from_uuid(efile, fh->mountid); + if (!expdir) + goto out; + } else { + /* Get the exports directory */ + expdir = exp_file_get_dir(efile, dir); + if (!expdir) + goto out; + } + + /* Setup search struct */ + _mnt3_auth_setup_search_params(&ngsa, host, expdir->dir_name, nfile, efile); + + /* Do the search */ + dict_foreach(expdir->netgroups, __export_dir_lookup_netgroup, &ngsa); + found_entry = ngsa.found_entry; + *item = ngsa.expitem; +out: + return found_entry; +} + +/** + * check_rw_access -- Checks if the export item + * has read-write access. + * + * @host_item : The export item to check + * + * @return -EROFS if it does not have rw access, 0 otherwise + * + */ +int +check_rw_access(struct export_item *item) +{ + struct export_options *opts = NULL; + int ret = -EROFS; + + if (!item) + goto out; + + opts = item->opts; + if (!opts) + goto out; + + if (opts->rw) + ret = 0; +out: + return ret; +} + +/** + * mnt3_auth_host -- Check if a host is authorized for a directory + * + * @auth_params : Auth parameters to authenticate against + * @host: Host requesting the directory + * @dir : Directory that the host requests + * @fh : The filehandle passed from an fop to authenticate + * + * 'fh' is null on mount requests and 'dir' is null on fops + * + * Procedure: + * + * - Check if the host is in the exports directory. + * - If not, check if the host is in the netgroups file for the + * netgroups authorized for the exports. + * + * @return: 0 if authorized + * -EACCES for completely unauthorized fop + * -EROFS for unauthorized write operations (rm, mkdir, write) * + */ +int +mnt3_auth_host(const struct mnt3_auth_params *auth_params, const char *host, + struct nfs3_fh *fh, const char *dir, gf_boolean_t is_write_op, + struct export_item **save_item) +{ + int auth_status_code = -EACCES; + struct export_item *item = NULL; + + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, auth_params, out); + GF_VALIDATE_OR_GOTO(GF_MNT_AUTH, host, out); + + /* Find the host in the exports file */ + item = _mnt3_auth_check_host_in_export(auth_params->expfile, dir, host, fh); + if (item) { + auth_status_code = (is_write_op) ? check_rw_access(item) : 0; + goto out; + } + + /* Find the host in the netgroups file for the exports directory */ + if (_mnt3_auth_check_host_in_netgroup(auth_params, fh, host, dir, &item)) { + auth_status_code = (is_write_op) ? check_rw_access(item) : 0; + goto out; + } + +out: + if (save_item) + *save_item = item; + + return auth_status_code; +} diff --git a/xlators/nfs/server/src/mount3-auth.h b/xlators/nfs/server/src/mount3-auth.h new file mode 100644 index 00000000000..e50af5847d3 --- /dev/null +++ b/xlators/nfs/server/src/mount3-auth.h @@ -0,0 +1,59 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _MOUNT3_AUTH +#define _MOUNT3_AUTH + +#include "nfs-mem-types.h" +#include "netgroups.h" +#include "exports.h" +#include "mount3.h" +#include "nfs.h" + +#define GF_MNT_AUTH GF_NFS "-mount3-auth" + +struct mnt3_auth_params { + struct netgroups_file *ngfile; /* The netgroup file to auth against */ + struct exports_file *expfile; /* The export file to auth against */ + struct mount3_state *ms; /* The mount state that owns this */ +}; + +/* Initialize auth params struct */ +struct mnt3_auth_params * +mnt3_auth_params_init(struct mount3_state *ms); + +/* Set the netgroups file to use in the auth */ +int +mnt3_auth_set_netgroups_auth(struct mnt3_auth_params *aps, + const char *filename); + +/* Set the exports file to use in the auth */ +int +mnt3_auth_set_exports_auth(struct mnt3_auth_params *aps, const char *filename); + +/* Check if a host is authorized to perform a mount / nfs-fop */ +int +mnt3_auth_host(const struct mnt3_auth_params *aps, const char *host, + struct nfs3_fh *fh, const char *dir, gf_boolean_t is_write_op, + struct export_item **save_item); + +/* Free resources used by the auth params struct */ +void +mnt3_auth_params_deinit(struct mnt3_auth_params *aps); + +int +mnt3_auth_fop_options_verify(const struct mnt3_auth_params *auth_params, + const char *host, const char *dir); + +#endif /* _MOUNT3_AUTH */ diff --git a/xlators/nfs/server/src/mount3.c b/xlators/nfs/server/src/mount3.c new file mode 100644 index 00000000000..a34d9104c17 --- /dev/null +++ b/xlators/nfs/server/src/mount3.c @@ -0,0 +1,4267 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include "mount3.h" +#include "xdr-nfs3.h" +#include "msg-nfs3.h" +#include <glusterfs/iobuf.h> +#include "nfs-common.h" +#include "nfs3-fh.h" +#include "nfs-fops.h" +#include "nfs-inodes.h" +#include "nfs-generics.h" +#include <glusterfs/locking.h> +#include <glusterfs/iatt.h> +#include "nfs-mem-types.h" +#include "nfs.h" +#include <glusterfs/common-utils.h> +#include <glusterfs/store.h> +#include "glfs-internal.h" +#include "glfs.h" +#include "mount3-auth.h" +#include <glusterfs/hashfn.h> +#include "nfs-messages.h" + +#include <errno.h> +#include <sys/socket.h> +#include <sys/uio.h> + +/* This macro will assist in freeing up entire link list + * of host_auth_spec structure. + */ +#define FREE_HOSTSPEC(exp) \ + do { \ + struct host_auth_spec *host = exp->hostspec; \ + while (NULL != host) { \ + struct host_auth_spec *temp = host; \ + host = host->next; \ + if (NULL != temp->host_addr) { \ + GF_FREE(temp->host_addr); \ + } \ + GF_FREE(temp); \ + } \ + exp->hostspec = NULL; \ + } while (0) + +/* Paths for export and netgroup files */ +const char *exports_file_path = GLUSTERD_DEFAULT_WORKDIR "/nfs/exports"; +const char *netgroups_file_path = GLUSTERD_DEFAULT_WORKDIR "/nfs/netgroups"; + +typedef ssize_t (*mnt3_serializer)(struct iovec outmsg, void *args); + +extern void * +mount3udp_thread(void *argv); + +static void +mnt3_export_free(struct mnt3_export *exp) +{ + if (!exp) + return; + + if (exp->exptype == MNT3_EXPTYPE_DIR) + FREE_HOSTSPEC(exp); + GF_FREE(exp->expname); + GF_FREE(exp->fullpath); + GF_FREE(exp); +} + +/* Generic reply function for MOUNTv3 specific replies. */ +int +mnt3svc_submit_reply(rpcsvc_request_t *req, void *arg, mnt3_serializer sfunc) +{ + struct iovec outmsg = { + 0, + }; + struct iobuf *iob = NULL; + struct mount3_state *ms = NULL; + int ret = -1; + ssize_t msglen = 0; + struct iobref *iobref = NULL; + + if (!req) + return -1; + + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "mount state not found"); + goto ret; + } + + /* First, get the io buffer into which the reply in arg will + * be serialized. + */ + /* TODO: use 'xdrproc_t' instead of 'sfunc' to get the xdr-size */ + iob = iobuf_get(ms->iobpool); + if (!iob) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobuf"); + goto ret; + } + + iobuf_to_iovec(iob, &outmsg); + /* Use the given serializer to translate the give C structure in arg + * to XDR format which will be written into the buffer in outmsg. + */ + msglen = sfunc(outmsg, arg); + if (msglen < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ENCODE_MSG_FAIL, + "Failed to encode message"); + goto ret; + } + outmsg.iov_len = msglen; + + iobref = iobref_new(); + if (iobref == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobref"); + goto ret; + } + + ret = iobref_add(iobref, iob); + if (ret) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to add iob to iobref"); + goto ret; + } + + /* Then, submit the message for transmission. */ + ret = rpcsvc_submit_message(req, &outmsg, 1, NULL, 0, iobref); + if (ret == -1) { + gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_REP_SUBMIT_FAIL, + "Reply submission failed"); + goto ret; + } + + ret = 0; +ret: + if (NULL != iob) + iobuf_unref(iob); + if (NULL != iobref) + iobref_unref(iobref); + + return ret; +} +/** + * __mountdict_insert -- Insert a mount entry into the mount state + * + * @ms: The mount state holding the entries + * @me: The mount entry to insert + * + * Not for external use. + */ +void +__mountdict_insert(struct mount3_state *ms, struct mountentry *me) +{ + char *exname = NULL; + char *fpath = NULL; + data_t *medata = NULL; + + GF_VALIDATE_OR_GOTO(GF_MNT, ms, out); + GF_VALIDATE_OR_GOTO(GF_MNT, me, out); + + /* We don't want export names with leading slashes */ + exname = me->exname; + while (exname[0] == '/') + exname++; + + /* Get the fullpath for the export */ + fpath = me->fullpath; + if (me->has_full_path) { + while (fpath[0] == '/') + fpath++; + + /* Export names can either be just volumes or paths inside that + * volume. */ + exname = fpath; + } + + snprintf(me->hashkey, sizeof(me->hashkey), "%s:%s", exname, me->hostname); + + medata = bin_to_data(me, sizeof(*me)); + dict_set(ms->mountdict, me->hashkey, medata); + gf_msg_trace(GF_MNT, 0, "Inserted into mountdict: %s", me->hashkey); +out: + return; +} + +/** + * __mountdict_remove -- Remove a mount entry from the mountstate. + * + * @ms: The mount state holding the entries + * @me: The mount entry to remove + * + * Not for external use. + */ +void +__mountdict_remove(struct mount3_state *ms, struct mountentry *me) +{ + dict_del(ms->mountdict, me->hashkey); +} + +/* Generic error reply function, just pass the err status + * and it will do the rest, including transmission. + */ +int +mnt3svc_mnt_error_reply(rpcsvc_request_t *req, int mntstat) +{ + mountres3 res; + + if (!req) + return -1; + + res.fhs_status = mntstat; + mnt3svc_submit_reply(req, (void *)&res, + (mnt3_serializer)xdr_serialize_mountres3); + + return 0; +} + +mountstat3 +mnt3svc_errno_to_mnterr(int32_t errnum) +{ + mountstat3 stat; + + switch (errnum) { + case 0: + stat = MNT3_OK; + break; + case ENOENT: + stat = MNT3ERR_NOENT; + break; + case EPERM: + stat = MNT3ERR_PERM; + break; + case EIO: + stat = MNT3ERR_IO; + break; + case EACCES: + stat = MNT3ERR_ACCES; + break; + case ENOTDIR: + stat = MNT3ERR_NOTDIR; + break; + case EINVAL: + stat = MNT3ERR_INVAL; + break; + case ENOSYS: + stat = MNT3ERR_NOTSUPP; + break; + case ENOMEM: + stat = MNT3ERR_SERVERFAULT; + break; + default: + stat = MNT3ERR_SERVERFAULT; + break; + } + + return stat; +} + +mountres3 +mnt3svc_set_mountres3(mountstat3 stat, struct nfs3_fh *fh, int *authflavor, + u_int aflen) +{ + mountres3 res = { + 0, + }; + uint32_t fhlen = 0; + + res.fhs_status = stat; + + if (fh) + fhlen = nfs3_fh_compute_size(); + + res.mountres3_u.mountinfo.fhandle.fhandle3_len = fhlen; + res.mountres3_u.mountinfo.fhandle.fhandle3_val = (char *)fh; + res.mountres3_u.mountinfo.auth_flavors.auth_flavors_val = authflavor; + res.mountres3_u.mountinfo.auth_flavors.auth_flavors_len = aflen; + + return res; +} + +/* Read the rmtab from the store_handle and append (or not) the entries to the + * mountlist. + * + * Requires the store_handle to be locked. + */ +static int +__mount_read_rmtab(gf_store_handle_t *sh, struct list_head *mountlist, + gf_boolean_t append) +{ + int ret = 0; + unsigned int idx = 0; + struct mountentry *me = NULL, *tmp = NULL; + /* me->hostname is a char[MNTPATHLEN] */ + char key[MNTPATHLEN + 11]; + + GF_ASSERT(sh && mountlist); + + if (!gf_store_locked_local(sh)) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_READ_LOCKED, + "Not reading unlocked %s", sh->path); + return -1; + } + + if (!append) { + list_for_each_entry_safe(me, tmp, mountlist, mlist) + { + list_del(&me->mlist); + GF_FREE(me); + } + me = NULL; + } + + for (;;) { + char *value = NULL; + + if (me && append) { + /* do not add duplicates */ + list_for_each_entry(tmp, mountlist, mlist) + { + if (!strcmp(tmp->hostname, me->hostname) && + !strcmp(tmp->exname, me->exname)) { + GF_FREE(me); + goto dont_add; + } + } + list_add_tail(&me->mlist, mountlist); + } else if (me) { + list_add_tail(&me->mlist, mountlist); + } + + dont_add: + me = GF_CALLOC(1, sizeof(*me), gf_nfs_mt_mountentry); + if (!me) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Out of memory"); + ret = -1; + goto out; + } + + INIT_LIST_HEAD(&me->mlist); + + snprintf(key, 9 + MNTPATHLEN, "hostname-%d", idx); + ret = gf_store_retrieve_value(sh, key, &value); + if (ret) + break; + snprintf(me->hostname, MNTPATHLEN, "%s", value); + GF_FREE(value); + + snprintf(key, 11 + MNTPATHLEN, "mountpoint-%d", idx); + ret = gf_store_retrieve_value(sh, key, &value); + if (ret) + break; + snprintf(me->exname, MNTPATHLEN, "%s", value); + GF_FREE(value); + + idx++; + gf_msg_trace(GF_MNT, 0, "Read entries %s:%s", me->hostname, me->exname); + } + gf_msg_debug(GF_MNT, 0, "Read %d entries from '%s'", idx, sh->path); + GF_FREE(me); +out: + return ret; +} + +/* Overwrite the contents of the rwtab with the in-memory client list. + * Fail gracefully if the stora_handle is not locked. + */ +static void +__mount_rewrite_rmtab(struct mount3_state *ms, gf_store_handle_t *sh) +{ + struct mountentry *me = NULL; + char key[16]; + int fd, ret; + unsigned int idx = 0; + + if (!gf_store_locked_local(sh)) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_MODIFY_LOCKED, + "Not modifying unlocked %s", sh->path); + return; + } + + fd = gf_store_mkstemp(sh); + if (fd == -1) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Failed to open %s", sh->path); + return; + } + + list_for_each_entry(me, &ms->mountlist, mlist) + { + snprintf(key, 16, "hostname-%d", idx); + ret = gf_store_save_value(fd, key, me->hostname); + if (ret) + goto fail; + + snprintf(key, 16, "mountpoint-%d", idx); + ret = gf_store_save_value(fd, key, me->exname); + if (ret) + goto fail; + + idx++; + } + + gf_msg_debug(GF_MNT, 0, "Updated rmtab with %d entries", idx); + + if (gf_store_rename_tmppath(sh)) + gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_RWTAB_OVERWRITE_FAIL, + "Failed to overwrite rwtab %s", sh->path); + + return; + +fail: + gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_UPDATE_FAIL, + "Failed to update %s", sh->path); + gf_store_unlink_tmppath(sh); +} + +static gf_boolean_t +mount_open_rmtab(const char *rmtab, gf_store_handle_t **sh) +{ + int ret = -1; + + /* updating the rmtab is disabled, use in-memory only */ + if (!rmtab || rmtab[0] == '\0') + return _gf_false; + + ret = gf_store_handle_new(rmtab, sh); + if (ret) { + gf_log(GF_MNT, GF_LOG_WARNING, "Failed to open '%s'", rmtab); + return _gf_false; + } + + return _gf_true; +} + +/* Read the rmtab into a clean ms->mountlist. + */ +static void +mount_read_rmtab(struct mount3_state *ms) +{ + gf_store_handle_t *sh = NULL; + struct nfs_state *nfs = NULL; + gf_boolean_t read_rmtab = _gf_false; + + nfs = (struct nfs_state *)ms->nfsx->private; + + read_rmtab = mount_open_rmtab(nfs->rmtab, &sh); + if (!read_rmtab) + return; + + if (gf_store_lock(sh)) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_LOCK_FAIL, + "Failed to lock '%s'", nfs->rmtab); + goto out; + } + + __mount_read_rmtab(sh, &ms->mountlist, _gf_false); + gf_store_unlock(sh); + +out: + gf_store_handle_destroy(sh); +} + +/* Write the ms->mountlist to the rmtab. + * + * The rmtab could be empty, or it can exists and have been updated by a + * different storage server without our knowing. + * + * 0. if opening the nfs->rmtab fails, return gracefully + * 1. takes the store_handle lock on the current rmtab + * - blocks if an other storage server rewrites the rmtab at the same time + * 2. [if new_rmtab] takes the store_handle lock on the new rmtab + * 3. reads/merges the entries from the current rmtab + * 4. [if new_rmtab] reads/merges the entries from the new rmtab + * 5. [if new_rmtab] writes the new rmtab + * 6. [if not new_rmtab] writes the current rmtab + * 7 [if new_rmtab] replaces nfs->rmtab to point to the new location + * 8. [if new_rmtab] releases the store_handle lock of the new rmtab + * 9. releases the store_handle lock of the old rmtab + */ +void +mount_rewrite_rmtab(struct mount3_state *ms, char *new_rmtab) +{ + gf_store_handle_t *sh = NULL, *nsh = NULL; + struct nfs_state *nfs = NULL; + int ret; + char *rmtab = NULL; + gf_boolean_t got_old_rmtab = _gf_false; + + nfs = (struct nfs_state *)ms->nfsx->private; + + got_old_rmtab = mount_open_rmtab(nfs->rmtab, &sh); + if (!got_old_rmtab && !new_rmtab) + return; + + if (got_old_rmtab && gf_store_lock(sh)) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_REWRITE_ERROR, + "Not rewriting '%s'", nfs->rmtab); + goto free_sh; + } + + if (new_rmtab) { + ret = gf_store_handle_new(new_rmtab, &nsh); + if (ret) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_OPEN_FAIL, + "Failed to open '%s'", new_rmtab); + goto unlock_sh; + } + + if (gf_store_lock(nsh)) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_REWRITE_ERROR, + "Not rewriting '%s'", new_rmtab); + goto free_nsh; + } + } + + /* always read the currently used rmtab */ + if (got_old_rmtab) + __mount_read_rmtab(sh, &ms->mountlist, _gf_true); + + if (new_rmtab) { + /* read the new rmtab and write changes to the new location */ + __mount_read_rmtab(nsh, &ms->mountlist, _gf_true); + __mount_rewrite_rmtab(ms, nsh); + + /* replace the nfs->rmtab reference to the new rmtab */ + rmtab = gf_strdup(new_rmtab); + if (rmtab == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_NO_MEMORY, + "Out of memory, keeping %s as rmtab", nfs->rmtab); + } else { + GF_FREE(nfs->rmtab); + nfs->rmtab = rmtab; + } + + gf_store_unlock(nsh); + } else { + /* rewrite the current (unchanged location) rmtab */ + __mount_rewrite_rmtab(ms, sh); + } + +free_nsh: + if (new_rmtab) + gf_store_handle_destroy(nsh); +unlock_sh: + if (got_old_rmtab) + gf_store_unlock(sh); +free_sh: + if (got_old_rmtab) + gf_store_handle_destroy(sh); +} + +/* Add a new NFS-client to the ms->mountlist and update the rmtab if we can. + * + * A NFS-client will only be removed from the ms->mountlist in case the + * NFS-client sends a unmount request. It is possible that a NFS-client + * crashed/rebooted had network loss or something else prevented the NFS-client + * to unmount cleanly. In this case, a duplicate entry would be added to the + * ms->mountlist, which is wrong and we should prevent. + * + * It is fully acceptable that the ms->mountlist is not 100% correct, this is a + * common issue for all(?) NFS-servers. + */ +int +mnt3svc_update_mountlist(struct mount3_state *ms, rpcsvc_request_t *req, + const char *expname, const char *fullpath) +{ + struct mountentry *me = NULL; + struct mountentry *cur = NULL; + int ret = -1; + char *colon = NULL; + struct nfs_state *nfs = NULL; + gf_store_handle_t *sh = NULL; + gf_boolean_t update_rmtab = _gf_false; + + if ((!ms) || (!req) || (!expname)) + return -1; + + me = (struct mountentry *)GF_CALLOC(1, sizeof(*me), gf_nfs_mt_mountentry); + if (!me) + return -1; + + nfs = (struct nfs_state *)ms->nfsx->private; + + update_rmtab = mount_open_rmtab(nfs->rmtab, &sh); + + snprintf(me->exname, MNTPATHLEN, "%s", expname); + /* Sometimes we don't care about the full path + * so a NULL value for fullpath is valid. + */ + if (fullpath) { + if (strlen(fullpath) < MNTPATHLEN) { + strcpy(me->fullpath, fullpath); + me->has_full_path = _gf_true; + } + } + + INIT_LIST_HEAD(&me->mlist); + /* Must get the IP or hostname of the client so we + * can map it into the mount entry. + */ + ret = rpcsvc_transport_peername(req->trans, me->hostname, MNTPATHLEN); + if (ret == -1) + goto free_err; + + colon = strrchr(me->hostname, ':'); + if (colon) { + *colon = '\0'; + } + LOCK(&ms->mountlock); + { + /* in case locking fails, we just don't write the rmtab */ + if (update_rmtab && gf_store_lock(sh)) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_LOCK_FAIL, + "Failed to lock '%s', changes will not be " + "written", + nfs->rmtab); + } else if (update_rmtab) { + __mount_read_rmtab(sh, &ms->mountlist, _gf_false); + } + + /* do not add duplicates */ + list_for_each_entry(cur, &ms->mountlist, mlist) + { + if (!strcmp(cur->hostname, me->hostname) && + !strcmp(cur->exname, me->exname)) { + GF_FREE(me); + goto dont_add; + } + } + list_add_tail(&me->mlist, &ms->mountlist); + __mountdict_insert(ms, me); + + /* only write the rmtab in case it was locked */ + if (update_rmtab && gf_store_locked_local(sh)) + __mount_rewrite_rmtab(ms, sh); + } +dont_add: + if (update_rmtab && gf_store_locked_local(sh)) + gf_store_unlock(sh); + + UNLOCK(&ms->mountlock); + +free_err: + if (update_rmtab) + gf_store_handle_destroy(sh); + + if (ret == -1) + GF_FREE(me); + + return ret; +} + +int +__mnt3_get_volume_id(struct mount3_state *ms, xlator_t *mntxl, uuid_t volumeid) +{ + int ret = -1; + struct mnt3_export *exp = NULL; + + if ((!ms) || (!mntxl)) + return ret; + + LOCK(&ms->mountlock); + list_for_each_entry(exp, &ms->exportlist, explist) + { + if (exp->vol == mntxl) { + gf_uuid_copy(volumeid, exp->volumeid); + ret = 0; + goto out; + } + } + +out: + UNLOCK(&ms->mountlock); + return ret; +} + +int +__mnt3_build_mountid_from_path(const char *path, uuid_t mountid) +{ + uint32_t hashed_path = 0; + int ret = -1; + + if (!path) + goto out; + + while (strlen(path) > 0 && path[0] == '/') + path++; + + /* Clear the mountid */ + gf_uuid_clear(mountid); + + hashed_path = SuperFastHash(path, strlen(path)); + if (hashed_path == 1) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_HASH_PATH_FAIL, + "failed to hash path: %s", path); + goto out; + } + + memcpy(mountid, &hashed_path, sizeof(hashed_path)); + ret = 0; +out: + return ret; +} + +int +__mnt3_get_mount_id(xlator_t *mntxl, uuid_t mountid) +{ + int ret = -1; + uint32_t hashed_path = 0; + + /* first clear the mountid */ + gf_uuid_clear(mountid); + + hashed_path = SuperFastHash(mntxl->name, strlen(mntxl->name)); + if (hashed_path == 1) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_HASH_XLATOR_FAIL, + "failed to hash xlator name: %s", mntxl->name); + goto out; + } + + memcpy(mountid, &hashed_path, sizeof(hashed_path)); + ret = 0; +out: + return ret; +} + +int32_t +mnt3svc_lookup_mount_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) +{ + mountres3 res = { + 0, + }; + rpcsvc_request_t *req = NULL; + struct nfs3_fh fh = { + {0}, + }; + struct mount3_state *ms = NULL; + mountstat3 status = 0; + int autharr[10]; + int autharrlen = 0; + rpcsvc_t *svc = NULL; + xlator_t *mntxl = NULL; + uuid_t volumeid = { + 0, + }; + char *path = NULL; + uuid_t mountid = { + 1, + }; + char fhstr[1536]; + int alloclen = 0; + + req = (rpcsvc_request_t *)frame->local; + + if (!req) + return -1; + + mntxl = (xlator_t *)cookie; + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "mount state not found"); + op_ret = -1; + op_errno = EINVAL; + } + + if (op_ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, op_errno, NFS_MSG_LOOKUP_MNT_ERROR, + "error=%s", strerror(op_errno)); + status = mnt3svc_errno_to_mnterr(op_errno); + } + if (status != MNT3_OK) + goto xmit_res; + + alloclen = strlen(mntxl->name) + 2; + path = GF_MALLOC(alloclen, gf_nfs_mt_char); + if (!path) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed."); + goto xmit_res; + } + + snprintf(path, alloclen, "/%s", mntxl->name); + mnt3svc_update_mountlist(ms, req, path, NULL); + GF_FREE(path); + if (gf_nfs_dvm_off(nfs_state(ms->nfsx))) { + fh = nfs3_fh_build_indexed_root_fh(ms->nfsx->children, mntxl); + goto xmit_res; + } + + __mnt3_get_mount_id(mntxl, mountid); + __mnt3_get_volume_id(ms, mntxl, volumeid); + fh = nfs3_fh_build_uuid_root_fh(volumeid, mountid); + +xmit_res: + nfs3_fh_to_str(&fh, fhstr, sizeof(fhstr)); + gf_msg_debug(GF_MNT, 0, "MNT reply: fh %s, status: %d", fhstr, status); + if (op_ret == 0) { + svc = rpcsvc_request_service(req); + autharrlen = rpcsvc_auth_array(svc, mntxl->name, autharr, 10); + } + + res = mnt3svc_set_mountres3(status, &fh, autharr, autharrlen); + mnt3svc_submit_reply(req, (void *)&res, + (mnt3_serializer)xdr_serialize_mountres3); + + return 0; +} + +int +mnt3_match_dirpath_export(const char *expname, const char *dirpath, + gf_boolean_t export_parsing_match) +{ + int ret = 0; + size_t dlen; + char *fullpath = NULL; + char *second_slash = NULL; + char *dirdup = NULL; + + if ((!expname) || (!dirpath)) + return 0; + + dirdup = strdupa(dirpath); + + /* Some clients send a dirpath for mount that includes the slash at the + * end. String compare for searching the export will fail because our + * exports list does not include that slash. Remove the slash to + * compare. + */ + dlen = strlen(dirdup); + if (dlen && dirdup[dlen - 1] == '/') + dirdup[dlen - 1] = '\0'; + + /* Here we try to match fullpaths with export names */ + fullpath = dirdup; + + if (export_parsing_match) { + if (dirdup[0] == '/') + fullpath = dirdup + 1; + + second_slash = strchr(fullpath, '/'); + if (second_slash) + *second_slash = '\0'; + } + + /* The export name begins with a slash so move it forward by one + * to ignore the slash when we want to compare the fullpath and + * export. + */ + if (fullpath[0] != '/') + expname++; + + if (strcmp(expname, fullpath) == 0) + ret = 1; + + return ret; +} + +int +mnt3svc_mount_inode(rpcsvc_request_t *req, struct mount3_state *ms, + xlator_t *xl, inode_t *exportinode) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + loc_t exportloc = { + 0, + }; + + if ((!req) || (!xl) || (!ms) || (!exportinode)) + return ret; + + ret = nfs_inode_loc_fill(exportinode, &exportloc, NFS_RESOLVE_EXIST); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_INODE_LOC_FILL_ERROR, + "Loc fill failed for export inode" + ": gfid %s, volume: %s", + uuid_utoa(exportinode->gfid), xl->name); + goto err; + } + + /* To service the mount request, all we need to do + * is to send a lookup fop that returns the stat + * for the root of the child volume. This is + * used to build the root fh sent to the client. + */ + nfs_request_user_init(&nfu, req); + ret = nfs_lookup(ms->nfsx, xl, &nfu, &exportloc, mnt3svc_lookup_mount_cbk, + (void *)req); + + nfs_loc_wipe(&exportloc); +err: + return ret; +} + +/* For a volume mount request, we just have to create loc on the root inode, + * and send a lookup. In the lookup callback the mount reply is send along with + * the file handle. + */ +int +mnt3svc_volume_mount(rpcsvc_request_t *req, struct mount3_state *ms, + struct mnt3_export *exp) +{ + inode_t *exportinode = NULL; + int ret = -EFAULT; + static uuid_t rootgfid = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + if ((!req) || (!exp) || (!ms)) + return ret; + + exportinode = inode_find(exp->vol->itable, rootgfid); + if (!exportinode) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOENT, NFS_MSG_GET_ROOT_INODE_FAIL, + "Failed to get root inode"); + ret = -ENOENT; + goto err; + } + + ret = mnt3svc_mount_inode(req, ms, exp->vol, exportinode); + inode_unref(exportinode); + +err: + return ret; +} + +/* The catch with directory exports is that the first component of the export + * name will be the name of the volume. + * Any lookup that needs to be performed to build the directory's file handle + * needs to start from the directory path from the root of the volume. For that + * we need to strip out the volume name first. + */ +char * +mnt3_get_volume_subdir(char *dirpath, char **volname) +{ + /* subdir points to the first / after the volume name while dirpath + * points to the first char of the volume name. + */ + char *subdir = NULL; + int volname_len = 0; + static char *root = "/"; + + /* all callers are expected to pass a valid *dirpath */ + GF_ASSERT(dirpath); + + if (dirpath[0] == '/') + dirpath++; + + subdir = index(dirpath, (int)'/'); + if (!subdir) { + subdir = root; + volname_len = strlen(dirpath); + } else { + volname_len = subdir - dirpath; + } + + if (!volname) + goto out; + + if (!*volname) + goto out; + + strncpy(*volname, dirpath, volname_len); + *(*volname + volname_len) = '\0'; +out: + return subdir; +} + +void +mnt3_resolve_state_wipe(mnt3_resolve_t *mres) +{ + if (!mres) + return; + + nfs_loc_wipe(&mres->resolveloc); + GF_FREE(mres); +} + +/* Sets up the component argument to contain the next component in the path and + * sets up path as an absolute path starting from the next component. + */ +static char * +setup_next_component(char *path, size_t plen, char *component, size_t clen) +{ + char *comp = NULL; + char *nextcomp = NULL; + + if ((!path) || (!component)) + return NULL; + + strncpy(component, path, clen); + comp = index(component, (int)'/'); + if (!comp) + goto err; + + comp++; + nextcomp = index(comp, (int)'/'); + if (nextcomp) { + strncpy(path, nextcomp, plen); + *nextcomp = '\0'; + } else + path[0] = '\0'; + +err: + return comp; +} + +int32_t +mnt3_resolve_subdir_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); + +int32_t +mnt3_readlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, const char *path, + struct iatt *buf, dict_t *xdata); + +/* There are multiple components in the directory export path and each one + * needs to be looked up one after the other. + */ +int +__mnt3_resolve_export_subdir_comp(mnt3_resolve_t *mres) +{ + char dupsubdir[MNTPATHLEN]; + char *nextcomp = NULL; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + uuid_t gfid = { + 0, + }; + + if (!mres) + return ret; + + nextcomp = setup_next_component(mres->remainingdir, + sizeof(mres->remainingdir), dupsubdir, + sizeof(dupsubdir)); + if (!nextcomp) + goto err; + + /* Wipe the contents of the previous component */ + gf_uuid_copy(gfid, mres->resolveloc.inode->gfid); + nfs_loc_wipe(&mres->resolveloc); + ret = nfs_entry_loc_fill(mres->mstate->nfsx, mres->exp->vol->itable, gfid, + nextcomp, &mres->resolveloc, NFS_RESOLVE_CREATE, + NULL); + if ((ret < 0) && (ret != -2)) { + gf_msg(GF_MNT, GF_LOG_ERROR, EFAULT, NFS_MSG_RESOLVE_INODE_FAIL, + "Failed to resolve and " + "create inode: parent gfid %s, entry %s", + uuid_utoa(gfid), nextcomp); + ret = -EFAULT; + goto err; + } + + nfs_request_user_init(&nfu, mres->req); + if (IA_ISLNK(mres->resolveloc.inode->ia_type)) { + ret = nfs_readlink(mres->mstate->nfsx, mres->exp->vol, &nfu, + &mres->resolveloc, mnt3_readlink_cbk, mres); + gf_msg_debug(GF_MNT, 0, + "Symlink found , need to resolve" + " into directory handle"); + goto err; + } + ret = nfs_lookup(mres->mstate->nfsx, mres->exp->vol, &nfu, + &mres->resolveloc, mnt3_resolve_subdir_cbk, mres); + +err: + return ret; +} + +int +__mnt3_resolve_subdir(mnt3_resolve_t *mres); + +/* + * Per the AFR2 comments, this function performs the "fresh" lookup + * by deleting the inode from cache and calling __mnt3_resolve_subdir + * again. + */ +int +__mnt3_fresh_lookup(mnt3_resolve_t *mres) +{ + inode_unlink(mres->resolveloc.inode, mres->resolveloc.parent, + mres->resolveloc.name); + strncpy(mres->remainingdir, mres->resolveloc.path, + strlen(mres->resolveloc.path)); + nfs_loc_wipe(&mres->resolveloc); + return __mnt3_resolve_subdir(mres); +} + +int32_t +mnt3_resolve_subdir_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) +{ + mnt3_resolve_t *mres = NULL; + mountstat3 mntstat = MNT3ERR_SERVERFAULT; + struct nfs3_fh fh = { + {0}, + }; + int autharr[10]; + int autharrlen = 0; + rpcsvc_t *svc = NULL; + mountres3 res = { + 0, + }; + xlator_t *mntxl = NULL; + char *path = NULL; + struct mount3_state *ms = NULL; + int authcode = 0; + char *authorized_host = NULL; + char *authorized_path = NULL; + inode_t *linked_inode = NULL; + + mres = frame->local; + ms = mres->mstate; + mntxl = (xlator_t *)cookie; + if (op_ret == -1 && op_errno == ESTALE) { + /* Nuke inode from cache and try the LOOKUP + * request again. */ + return __mnt3_fresh_lookup(mres); + } else if (op_ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, op_errno, NFS_MSG_RESOLVE_SUBDIR_FAIL, + "path=%s (%s)", mres->resolveloc.path, strerror(op_errno)); + mntstat = mnt3svc_errno_to_mnterr(op_errno); + goto err; + } + + linked_inode = inode_link(mres->resolveloc.inode, mres->resolveloc.parent, + mres->resolveloc.name, buf); + + if (linked_inode) + nfs_fix_generation(this, linked_inode); + + nfs3_fh_build_child_fh(&mres->parentfh, buf, &fh); + if (strlen(mres->remainingdir) <= 0) { + int alloclen; + op_ret = -1; + mntstat = MNT3_OK; + + /* Construct the full path */ + int resolveloc_path_len = strlen(mres->resolveloc.path); + alloclen = strlen(mres->exp->expname) + resolveloc_path_len + 1; + mres->exp->fullpath = GF_MALLOC(alloclen, gf_nfs_mt_char); + if (!mres->exp->fullpath) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed."); + goto err; + } + snprintf(mres->exp->fullpath, alloclen, "%s%s", mres->exp->expname, + mres->resolveloc.path); + + /* Check if this path is authorized to be mounted */ + authcode = mnt3_authenticate_request( + ms, mres->req, NULL, NULL, mres->exp->fullpath, &authorized_path, + &authorized_host, FALSE); + if (authcode != 0) { + mntstat = MNT3ERR_ACCES; + gf_msg_debug(GF_MNT, 0, "Client mount not allowed"); + op_ret = -1; + goto err; + } + + alloclen = strlen(mres->exp->vol->name) + resolveloc_path_len + 2; + path = GF_MALLOC(alloclen, gf_nfs_mt_char); + if (!path) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto err; + } + /* Build mountid from the authorized path and stick it in the + * filehandle that will get passed back to the client + */ + __mnt3_build_mountid_from_path(authorized_path, fh.mountid); + + snprintf(path, alloclen, "/%s%s", mres->exp->vol->name, + mres->resolveloc.path); + + mnt3svc_update_mountlist(mres->mstate, mres->req, path, + mres->exp->fullpath); + GF_FREE(path); + } else { + mres->parentfh = fh; + op_ret = __mnt3_resolve_export_subdir_comp(mres); + if (op_ret < 0) + mntstat = mnt3svc_errno_to_mnterr(-op_ret); + } +err: + if (op_ret == -1) { + gf_msg_debug(GF_MNT, 0, "Mount reply status: %d", mntstat); + svc = rpcsvc_request_service(mres->req); + autharrlen = rpcsvc_auth_array(svc, mntxl->name, autharr, 10); + + res = mnt3svc_set_mountres3(mntstat, &fh, autharr, autharrlen); + mnt3svc_submit_reply(mres->req, (void *)&res, + (mnt3_serializer)xdr_serialize_mountres3); + mnt3_resolve_state_wipe(mres); + } + + GF_FREE(authorized_path); + GF_FREE(authorized_host); + + return 0; +} + +/* This function resolves symbolic link into directory path from + * the mount and restart the parsing process from the beginning + * + * Note : Path specified in the symlink should be relative to the + * symlink, because that is the one which is consistent through + * out the file system. + * If the symlink resolves into another symlink ,then same process + * will be repeated. + * If symbolic links points outside the file system are not considered + * here. + * + * TODO : 1.) This function cannot handle symlinks points to path which + * goes out of the filesystem and comes backs again to same. + * For example, consider vol is exported volume.It contains + * dir, + * symlink1 which points to ../vol/dir, + * symlink2 which points to ../mnt/../vol/dir, + * symlink1 and symlink2 are not handled right now. + * + * 2.) udp mount routine is much simpler from tcp routine and resolves + * symlink directly.May be ,its better we change this routine + * similar to udp + */ +int32_t +mnt3_readlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, const char *path, + struct iatt *buf, dict_t *xdata) +{ + mnt3_resolve_t *mres = NULL; + int ret = -EFAULT; + char *real_loc = NULL; + size_t path_len = 0; + size_t parent_path_len = 0; + char *parent_path = NULL; + char *absolute_path = NULL; + char *relative_path = NULL; + int mntstat = 0; + + GF_ASSERT(frame); + + mres = frame->local; + if (!mres || !path || (path[0] == '/') || (op_ret < 0)) + goto mnterr; + + /* Finding current location of symlink */ + parent_path_len = strlen(mres->resolveloc.path) - + strlen(mres->resolveloc.name); + parent_path = gf_strndup(mres->resolveloc.path, parent_path_len); + if (!parent_path) { + ret = -ENOMEM; + goto mnterr; + } + + relative_path = gf_strdup(path); + if (!relative_path) { + ret = -ENOMEM; + goto mnterr; + } + /* Resolving into absolute path */ + ret = gf_build_absolute_path(parent_path, relative_path, &absolute_path); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SYMLINK_ERROR, + "Cannot resolve symlink, path is out of boundary " + "from current location %s and with relative path " + "%s pointed by symlink", + parent_path, relative_path); + + goto mnterr; + } + + /* Building the actual mount path to be mounted */ + path_len = strlen(mres->exp->vol->name) + strlen(absolute_path) + + strlen(mres->remainingdir) + 1; + real_loc = GF_MALLOC(path_len, gf_nfs_mt_char); + if (!real_loc) { + ret = -ENOMEM; + goto mnterr; + } + snprintf(real_loc, path_len, "%s%s", mres->exp->vol->name, absolute_path); + gf_path_strip_trailing_slashes(real_loc); + + /* There may entries after symlink in the mount path, + * we should include remaining entries too */ + if (strlen(mres->remainingdir) > 0) + strcat(real_loc, mres->remainingdir); + + gf_msg_debug(GF_MNT, 0, + "Resolved path is : %s%s " + "and actual mount path is %s", + absolute_path, mres->remainingdir, real_loc); + + /* After the resolving the symlink , parsing should be done + * for the populated mount path + */ + ret = mnt3_parse_dir_exports(mres->req, mres->mstate, real_loc, _gf_true); + + if (ret) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_RESOLVE_ERROR, + "Resolved into an unknown path %s%s " + "from the current location of symlink %s", + absolute_path, mres->remainingdir, parent_path); + } + + GF_FREE(real_loc); + GF_FREE(absolute_path); + GF_FREE(parent_path); + GF_FREE(relative_path); + + return ret; + +mnterr: + if (mres) { + mntstat = mnt3svc_errno_to_mnterr(-ret); + mnt3svc_mnt_error_reply(mres->req, mntstat); + } else + gf_msg(GF_MNT, GF_LOG_CRITICAL, EINVAL, NFS_MSG_INVALID_ENTRY, + "mres == NULL, this should *never* happen"); + if (absolute_path) + GF_FREE(absolute_path); + if (parent_path) + GF_FREE(parent_path); + if (relative_path) + GF_FREE(relative_path); + return ret; +} + +/* We will always have to perform a hard lookup on all the components of a + * directory export for a mount request because in the mount reply we need the + * file handle of the directory. Our file handle creation code is designed with + * the assumption that to build a child file/dir fh, we'll always have the + * parent dir's fh available so that we may copy the hash array of the previous + * dir levels. + * + * Since we do not store the file handles anywhere, for every mount request we + * must resolve the file handles of every component so that the parent dir file + * of the exported directory can be built. + */ +int +__mnt3_resolve_subdir(mnt3_resolve_t *mres) +{ + char dupsubdir[MNTPATHLEN]; + char *firstcomp = NULL; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + static uuid_t rootgfid = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + if (!mres) + return ret; + + firstcomp = setup_next_component(mres->remainingdir, + sizeof(mres->remainingdir), dupsubdir, + sizeof(dupsubdir)); + if (!firstcomp) + goto err; + + ret = nfs_entry_loc_fill(mres->mstate->nfsx, mres->exp->vol->itable, + rootgfid, firstcomp, &mres->resolveloc, + NFS_RESOLVE_CREATE, NULL); + if ((ret < 0) && (ret != -2)) { + gf_msg(GF_MNT, GF_LOG_ERROR, EFAULT, NFS_MSG_RESOLVE_INODE_FAIL, + "Failed to resolve and " + "create inode for volume root: %s", + mres->exp->vol->name); + ret = -EFAULT; + goto err; + } + + nfs_request_user_init(&nfu, mres->req); + if (IA_ISLNK(mres->resolveloc.inode->ia_type)) { + ret = nfs_readlink(mres->mstate->nfsx, mres->exp->vol, &nfu, + &mres->resolveloc, mnt3_readlink_cbk, mres); + gf_msg_debug(GF_MNT, 0, + "Symlink found , need to resolve " + "into directory handle"); + goto err; + } + ret = nfs_lookup(mres->mstate->nfsx, mres->exp->vol, &nfu, + &mres->resolveloc, mnt3_resolve_subdir_cbk, mres); + +err: + return ret; +} + +static gf_boolean_t +mnt3_match_subnet_v4(struct addrinfo *ai, uint32_t saddr, uint32_t mask) +{ + for (; ai; ai = ai->ai_next) { + struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; + + if (sin->sin_family != AF_INET) + continue; + + if (mask_match(saddr, sin->sin_addr.s_addr, mask)) + return _gf_true; + } + + return _gf_false; +} + +/** + * This function will verify if the client is allowed to mount + * the directory or not. Client's IP address will be compared with + * allowed IP list or range present in mnt3_export structure. + * + * @param client_addr - This structure contains client's IP address. + * @param export - mnt3_export structure. Contains allowed IP list/range. + * + * @return 0 - on Success and -EACCES on failure. + * + * TODO: Support IPv6 subnetwork + */ +int +mnt3_verify_auth(struct sockaddr_in *client_addr, struct mnt3_export *export) +{ + int retvalue = -EACCES; + int ret = 0; + struct host_auth_spec *host = NULL; + struct sockaddr_in *allowed_addr = NULL; + struct addrinfo *allowed_addrinfo = NULL; + + struct addrinfo hint = { + .ai_family = AF_INET, + .ai_protocol = (int)IPPROTO_TCP, + .ai_flags = AI_CANONNAME, + }; + + /* Sanity check */ + if ((NULL == client_addr) || (NULL == export) || + (NULL == export->hostspec)) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid argument"); + return retvalue; + } + + host = export->hostspec; + + /* + * Currently IPv4 subnetwork is supported i.e. AF_INET. + * TODO: IPv6 subnetwork i.e. AF_INET6. + */ + if (client_addr->sin_family != AF_INET) { + gf_msg(GF_MNT, GF_LOG_ERROR, EAFNOSUPPORT, NFS_MSG_UNSUPPORTED_VERSION, + "Only IPv4 is supported for subdir-auth"); + return retvalue; + } + + /* Try to see if the client IP matches the allowed IP list.*/ + while (NULL != host) { + GF_ASSERT(host->host_addr); + + if (NULL != allowed_addrinfo) { + freeaddrinfo(allowed_addrinfo); + allowed_addrinfo = NULL; + } + + /* Get the addrinfo for the allowed host (host_addr). */ + ret = getaddrinfo(host->host_addr, NULL, &hint, &allowed_addrinfo); + if (0 != ret) { + /* + * getaddrinfo() FAILED for the host IP addr. Continue + * to search other allowed hosts in the hostspec list. + */ + gf_msg_debug(GF_MNT, 0, "getaddrinfo: %s\n", gai_strerror(ret)); + host = host->next; + continue; + } + + allowed_addr = (struct sockaddr_in *)(allowed_addrinfo->ai_addr); + if (NULL == allowed_addr) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid structure"); + break; + } + + /* Check if the network addr of both IPv4 socket match */ + if (mnt3_match_subnet_v4(allowed_addrinfo, client_addr->sin_addr.s_addr, + host->netmask)) { + retvalue = 0; + break; + } + + /* No match yet, continue the search */ + host = host->next; + } + + /* FREE the dynamic memory allocated by getaddrinfo() */ + if (NULL != allowed_addrinfo) { + freeaddrinfo(allowed_addrinfo); + } + + return retvalue; +} + +int +mnt3_resolve_subdir(rpcsvc_request_t *req, struct mount3_state *ms, + struct mnt3_export *exp, char *subdir, + gf_boolean_t send_reply) +{ + mnt3_resolve_t *mres = NULL; + int ret = -EFAULT; + struct nfs3_fh pfh = GF_NFS3FH_STATIC_INITIALIZER; + struct sockaddr_in *sin = NULL; + + if ((!req) || (!ms) || (!exp) || (!subdir)) + return ret; + + sin = (struct sockaddr_in *)(&(req->trans->peerinfo.sockaddr)); + + /* Need to check AUTH */ + if (NULL != exp->hostspec) { + ret = mnt3_verify_auth(sin, exp); + if (0 != ret) { + gf_msg(GF_MNT, GF_LOG_ERROR, EACCES, NFS_MSG_AUTH_VERIFY_FAILED, + "AUTH verification failed"); + return ret; + } + } + + /* no reply is needed (WebNFS permissions checking), just return */ + if (!send_reply) + return 0; /* no error, mnt3_verify_auth() allowed it */ + + mres = GF_CALLOC(1, sizeof(mnt3_resolve_t), gf_nfs_mt_mnt3_resolve); + if (!mres) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto err; + } + + mres->exp = exp; + mres->mstate = ms; + mres->req = req; + + snprintf(mres->remainingdir, MNTPATHLEN, "%s", subdir); + gf_path_strip_trailing_slashes(mres->remainingdir); + + if (gf_nfs_dvm_off(nfs_state(ms->nfsx))) + pfh = nfs3_fh_build_indexed_root_fh(mres->mstate->nfsx->children, + mres->exp->vol); + else + pfh = nfs3_fh_build_uuid_root_fh(exp->volumeid, exp->mountid); + + mres->parentfh = pfh; + ret = __mnt3_resolve_subdir(mres); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SUBDIR_FAIL, + "Failed to resolve export dir: %s", mres->exp->expname); + GF_FREE(mres); + } + +err: + return ret; +} + +int +mnt3_resolve_export_subdir(rpcsvc_request_t *req, struct mount3_state *ms, + struct mnt3_export *exp) +{ + char *volume_subdir = NULL; + int ret = -EFAULT; + + if ((!req) || (!ms) || (!exp)) + return ret; + + volume_subdir = mnt3_get_volume_subdir(exp->expname, NULL); + + ret = mnt3_resolve_subdir(req, ms, exp, volume_subdir, _gf_true); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SUBDIR_FAIL, + "Failed to resolve export dir: %s", exp->expname); + goto err; + } + +err: + return ret; +} + +int +mnt3svc_mount(rpcsvc_request_t *req, struct mount3_state *ms, + struct mnt3_export *exp) +{ + int ret = -EFAULT; + + if ((!req) || (!ms) || (!exp)) + return ret; + + if (exp->exptype == MNT3_EXPTYPE_VOLUME) + ret = mnt3svc_volume_mount(req, ms, exp); + else if (exp->exptype == MNT3_EXPTYPE_DIR) + ret = mnt3_resolve_export_subdir(req, ms, exp); + + return ret; +} + +/* mnt3_mntpath_to_xlator sets this to 1 if the mount is for a full + * volume or 2 for a subdir in the volume. + * + * The parameter 'export_parsing_match' indicates whether this function + * is being called by an exports parser or whether it is being called + * during mount. The behavior is different since we don't have to resolve + * the path when doing the parse. + */ +struct mnt3_export * +mnt3_mntpath_to_export(struct mount3_state *ms, const char *dirpath, + gf_boolean_t export_parsing_match) +{ + struct mnt3_export *exp = NULL; + struct mnt3_export *found = NULL; + + if ((!ms) || (!dirpath)) + return NULL; + + LOCK(&ms->mountlock); + list_for_each_entry(exp, &ms->exportlist, explist) + { + /* Search for the an exact match with the volume */ + if (mnt3_match_dirpath_export(exp->expname, dirpath, + export_parsing_match)) { + found = exp; + gf_msg_debug(GF_MNT, 0, + "Found export volume: " + "%s", + exp->vol->name); + goto foundexp; + } + } + + gf_msg_debug(GF_MNT, 0, "Export not found"); +foundexp: + UNLOCK(&ms->mountlock); + return found; +} + +static int +mnt3_check_client_net_check(rpcsvc_t *svc, char *expvol, char *ipaddr, + uint16_t port) +{ + int ret = RPCSVC_AUTH_REJECT; + + if ((!svc) || (!expvol) || (!ipaddr)) + goto err; + + ret = rpcsvc_auth_check(svc, expvol, ipaddr); + if (ret == RPCSVC_AUTH_REJECT) { + gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_PEER_NOT_ALLOWED, + "Peer %s not allowed", ipaddr); + goto err; + } + + ret = rpcsvc_transport_privport_check(svc, expvol, port); + if (ret == RPCSVC_AUTH_REJECT) { + gf_msg(GF_MNT, GF_LOG_INFO, errno, NFS_MSG_PEER_NOT_ALLOWED, + "Peer %s rejected. Unprivileged " + "port %d not allowed", + ipaddr, port); + goto err; + } + + ret = RPCSVC_AUTH_ACCEPT; +err: + return ret; +} + +int +mnt3_check_client_net_tcp(rpcsvc_request_t *req, char *volname) +{ + rpcsvc_t *svc = NULL; + rpc_transport_t *trans = NULL; + union gf_sock_union sock_union; + socklen_t socksize = sizeof(struct sockaddr_in); + char peer[RPCSVC_PEER_STRLEN] = { + 0, + }; + char *ipaddr = NULL; + uint16_t port = 0; + int ret = RPCSVC_AUTH_REJECT; + + if ((!req) || (!volname)) + goto err; + + svc = rpcsvc_request_service(req); + trans = rpcsvc_request_transport(req); + if ((!svc) || (!trans)) + goto err; + + ret = rpcsvc_transport_peeraddr(trans, peer, RPCSVC_PEER_STRLEN, + &sock_union.storage, socksize); + if (ret != 0) { + gf_msg(GF_MNT, GF_LOG_WARNING, ENOENT, NFS_MSG_GET_PEER_ADDR_FAIL, + "Failed to get peer " + "addr: %s", + gai_strerror(ret)); + ret = RPCSVC_AUTH_REJECT; + goto err; + } + + /* peer[] gets IP:PORT formar, slash the port out */ + if (!get_host_name((char *)peer, &ipaddr)) + ipaddr = peer; + + port = ntohs(sock_union.sin.sin_port); + + ret = mnt3_check_client_net_check(svc, volname, ipaddr, port); +err: + return ret; +} + +static int +mnt3_check_client_net_udp(struct svc_req *req, char *volname, xlator_t *nfsx) +{ + rpcsvc_t *svc = NULL; + struct sockaddr_in *sin = NULL; + char ipaddr[INET_ADDRSTRLEN + 1] = { + 0, + }; + uint16_t port = 0; + int ret = RPCSVC_AUTH_REJECT; + struct nfs_state *nfs = NULL; + + if ((!req) || (!volname) || (!nfsx)) + goto err; + +#if !defined(_TIRPC_SVC_H) + sin = svc_getcaller(req->rq_xprt); +#else + sin = (struct sockaddr_in *)svc_getcaller(req->rq_xprt); + /* TIRPC's svc_getcaller() returns a pointer to a sockaddr_in6, even + * though it might actually be an IPv4 address. It ought return a + * struct sockaddr and make the caller upcast it to the proper + * address family. Sigh. + */ +#endif + if (!sin) + goto err; + /* And let's make sure that it's actually an IPv4 address. */ + + GF_ASSERT(sin->sin_family == AF_INET); + + (void)inet_ntop(AF_INET, &sin->sin_addr, ipaddr, INET_ADDRSTRLEN); + + port = ntohs(sin->sin_port); + + nfs = (struct nfs_state *)nfsx->private; + if (nfs != NULL) + svc = nfs->rpcsvc; + + ret = mnt3_check_client_net_check(svc, volname, ipaddr, port); +err: + return ret; +} + +int +mnt3_parse_dir_exports(rpcsvc_request_t *req, struct mount3_state *ms, + char *path, gf_boolean_t send_reply) +{ + char volname[1024] = { + 0, + }; + struct mnt3_export *exp = NULL; + char *volname_ptr = NULL; + char *subdir = NULL; + int ret = -ENOENT; + struct nfs_state *nfs = NULL; + + if ((!ms) || (!path)) + return -1; + + volname_ptr = volname; + subdir = mnt3_get_volume_subdir(path, &volname_ptr); + + /* first try to match the full export/subdir */ + exp = mnt3_mntpath_to_export(ms, path, _gf_false); + if (!exp) { + gf_msg_trace(GF_MNT, 0, + "Could not find exact matching export " + "for path=%s", + path); + /* if no exact match is found, look for a fallback */ + exp = mnt3_mntpath_to_export(ms, volname, _gf_true); + if (!exp) { + gf_msg_trace(GF_MNT, 0, + "Could not find export for " + "volume %s", + volname); + goto err; + } + } + gf_msg_trace(GF_MNT, 0, + "volume %s and export %s will be used for " + "path %s", + exp->vol->name, exp->expname, path); + + nfs = (struct nfs_state *)ms->nfsx->private; + if (!nfs) + goto err; + + if (!nfs_subvolume_started(nfs, exp->vol)) { + gf_msg_debug(GF_MNT, 0, "Volume %s not started", exp->vol->name); + goto err; + } + + ret = mnt3_check_client_net_tcp(req, exp->vol->name); + if (ret == RPCSVC_AUTH_REJECT) { + gf_msg_debug(GF_MNT, 0, "Client mount not allowed"); + ret = -EACCES; + goto err; + } + + ret = mnt3_resolve_subdir(req, ms, exp, subdir, send_reply); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SUBDIR_FAIL, + "Failed to resolve export dir: %s", subdir); + goto err; + } + +err: + return ret; +} + +int +mnt3_find_export(rpcsvc_request_t *req, char *path, struct mnt3_export **e) +{ + int ret = -EFAULT; + struct mount3_state *ms = NULL; + struct mnt3_export *exp = NULL; + + if ((!req) || (!path) || (!e)) + return -1; + + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "Mount state not present"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + goto err; + } + + gf_msg_debug(GF_MNT, 0, "dirpath: %s", path); + exp = mnt3_mntpath_to_export(ms, path, _gf_false); + if (exp) { + ret = 0; + *e = exp; + goto err; + } + + if (!gf_mnt3_export_dirs(ms)) { + ret = -1; + goto err; + } + + ret = mnt3_parse_dir_exports(req, ms, path, _gf_true); + +err: + return ret; +} + +/** + * _mnt3_get_peer_addr -- Take an rpc request object and return an allocated + * peer address. A peer address is host:port. + * + * @req: An rpc svc request object to extract the peer address from + * + * @return: success: Pointer to an allocated string containing the peer address + * failure: NULL + */ +char * +_mnt3_get_peer_addr(const rpcsvc_request_t *req) +{ + rpc_transport_t *trans = NULL; + struct sockaddr_storage sastorage = { + 0, + }; + char peer[RPCSVC_PEER_STRLEN] = { + 0, + }; + char *peerdup = NULL; + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_NFS, req, out); + + trans = rpcsvc_request_transport(req); + ret = rpcsvc_transport_peeraddr(trans, peer, RPCSVC_PEER_STRLEN, &sastorage, + sizeof(sastorage)); + if (ret != 0) + goto out; + + peerdup = gf_strdup(peer); +out: + return peerdup; +} + +/** + * _mnt3_get_host_from_peer -- Take a peer address and get an allocated + * hostname. The hostname is the string on the + * left side of the colon. + * + * @peer_addr: The peer address to get a hostname from + * + * @return: success: Allocated string containing the hostname + * failure: NULL + * + */ +char * +_mnt3_get_host_from_peer(const char *peer_addr) +{ + char *part = NULL; + size_t host_len = 0; + char *colon = NULL; + + colon = strrchr(peer_addr, ':'); + if (!colon) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_BAD_PEER, "Bad peer %s", + peer_addr); + goto out; + } + + host_len = colon - peer_addr; + if (host_len < RPCSVC_PEER_STRLEN) + part = gf_strndup(peer_addr, host_len); + else + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_PEER_TOO_LONG, + "Peer too long %s", peer_addr); +out: + return part; +} + +/** + * mnt3_check_cached_fh -- Check if FH is cached. + * + * Calls auxiliary functions based on whether we are checking + * a write operation. + * + */ +int +mnt3_check_cached_fh(struct mount3_state *ms, struct nfs3_fh *fh, + const char *host_addr, gf_boolean_t is_write_op) +{ + if (!is_write_op) + return is_nfs_fh_cached(ms->authcache, fh, host_addr); + + return is_nfs_fh_cached_and_writeable(ms->authcache, fh, host_addr); +} + +/** + * _mnt3_authenticate_req -- Given an RPC request and a path OR a filehandle + * check if the host is authorized to make the + * request. Uses exports/netgroups auth model to + * do this check. + * + * @ms : The mount state + * @req : The RPC request + * @fh : The NFS FH to authenticate (set when authenticating an FOP) + * @path: The path to authenticate (set when authenticating a mount req) + * @authorized_export: Allocate and fill this value when an export is authorized + * @authorized_host: Allocate and fill this value when a host is authorized + * @is_write_op: Is this a write op that we are authenticating? + * + * @return: 0 if authorized + * -EACCES for completely unauthorized fop + * -EROFS for unauthorized write operations (rm, mkdir, write) + */ +int +_mnt3_authenticate_req(struct mount3_state *ms, rpcsvc_request_t *req, + struct nfs3_fh *fh, const char *path, + char **authorized_export, char **authorized_host, + gf_boolean_t is_write_op) +{ + char *peer_addr = NULL; + char *host_addr_ip = NULL; + char *host_addr_fqdn = NULL; + int auth_status_code = -EACCES; + char *pathdup = NULL; + size_t dlen = 0; + char *auth_host = NULL; + gf_boolean_t fh_cached = _gf_false; + struct export_item *expitem = NULL; + + GF_VALIDATE_OR_GOTO(GF_MNT, ms, out); + GF_VALIDATE_OR_GOTO(GF_MNT, req, out); + + peer_addr = _mnt3_get_peer_addr(req); + + if (!peer_addr) + goto free_and_out; + + host_addr_ip = _mnt3_get_host_from_peer(peer_addr); + + if (!host_addr_ip) + goto free_and_out; + + if (path) { + /* Need to strip out trailing '/' */ + pathdup = strdupa(path); + dlen = strlen(pathdup); + if (dlen > 0 && pathdup[dlen - 1] == '/') + pathdup[dlen - 1] = '\0'; + } + + /* Check if the filehandle is cached */ + fh_cached = mnt3_check_cached_fh(ms, fh, host_addr_ip, is_write_op); + if (fh_cached) { + gf_msg_trace(GF_MNT, 0, "Found cached FH for %s", host_addr_ip); + auth_status_code = 0; + goto free_and_out; + } + + /* Check if the IP is authorized */ + auth_status_code = mnt3_auth_host(ms->auth_params, host_addr_ip, fh, + pathdup, is_write_op, &expitem); + + gf_msg_debug(GF_MNT, 0, "access from IP %s is %s", host_addr_ip, + auth_status_code ? "denied" : "allowed"); + + if (auth_status_code != 0) { + /* If not, check if the FQDN is authorized */ + host_addr_fqdn = gf_rev_dns_lookup(host_addr_ip); + auth_status_code = mnt3_auth_host(ms->auth_params, host_addr_fqdn, fh, + pathdup, is_write_op, &expitem); + + gf_msg_debug(GF_MNT, 0, "access from FQDN %s is %s", host_addr_fqdn, + auth_status_code ? "denied" : "allowed"); + + if (auth_status_code == 0) + auth_host = host_addr_fqdn; + } else + auth_host = host_addr_ip; + + /* Skip the lines that set authorized export & + * host if they are null. + */ + if (!authorized_export || !authorized_host) { + /* Cache the file handle if it was authorized */ + if (fh && auth_status_code == 0) + cache_nfs_fh(ms->authcache, fh, host_addr_ip, expitem); + + goto free_and_out; + } + + if (!fh && auth_status_code == 0) { + *authorized_export = gf_strdup(pathdup); + if (!*authorized_export) + gf_msg(GF_MNT, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Allocation error when copying " + "authorized path"); + + *authorized_host = gf_strdup(auth_host); + if (!*authorized_host) + gf_msg(GF_MNT, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Allocation error when copying " + "authorized host"); + } + +free_and_out: + /* Free allocated strings after doing the auth */ + GF_FREE(peer_addr); + GF_FREE(host_addr_fqdn); + GF_FREE(host_addr_ip); +out: + return auth_status_code; +} + +/** + * mnt3_authenticate_request -- Given an RPC request and a path, check if the + * host is authorized to make the request. This + * function calls _mnt3_authenticate_req_path () + * in a loop for the parent of each path while + * the authentication check for that path is + * failing. + * + * E.g. If the requested path is /patchy/L1, and /patchy is authorized, but + * /patchy/L1 is not, it follows this code path : + * + * _mnt3_authenticate_req ("/patchy/L1") -> F + * _mnt3_authenticate_req ("/patchy"); -> T + * return T; + * + * @ms : The mount state + * @req : The RPC request + * @path: The requested path + * @authorized_path: This gets allocated and populated with the authorized path + * @authorized_host: This gets allocated and populated with the authorized host + * @return: 0 if authorized + * -EACCES for completely unauthorized fop + * -EROFS for unauthorized write operations (rm, mkdir, write) + */ +int +mnt3_authenticate_request(struct mount3_state *ms, rpcsvc_request_t *req, + struct nfs3_fh *fh, const char *volname, + const char *path, char **authorized_path, + char **authorized_host, gf_boolean_t is_write_op) +{ + int auth_status_code = -EACCES; + char *parent_path = NULL; + const char *parent_old = NULL; + + GF_VALIDATE_OR_GOTO(GF_MNT, ms, out); + GF_VALIDATE_OR_GOTO(GF_MNT, req, out); + + /* If this option is not set, just allow it through */ + if (!ms->nfs->exports_auth) { + /* This function is called in a variety of use-cases (mount + * + each fop) so path/authorized_path are not always present. + * For the cases which it _is_ present we need to populate the + * authorized_path. */ + if (path && authorized_path) + *authorized_path = gf_strdup(path); + + auth_status_code = 0; + goto out; + } + + /* First check if the path is allowed */ + auth_status_code = _mnt3_authenticate_req( + ms, req, fh, path, authorized_path, authorized_host, is_write_op); + + /* If the filehandle is set, just exit since we have to make only + * one call to the function above + */ + if (fh) + goto out; + + parent_old = path; + while (auth_status_code != 0) { + /* Get the path's parent */ + parent_path = gf_resolve_path_parent(parent_old); + if (!parent_path) /* Nothing left in the path to resolve */ + goto out; + + /* Authenticate it */ + auth_status_code = _mnt3_authenticate_req(ms, req, fh, parent_path, + authorized_path, + authorized_host, is_write_op); + + parent_old = strdupa(parent_path); /* Copy the parent onto the + * stack. + */ + + GF_FREE(parent_path); /* Free the allocated parent string */ + } + +out: + return auth_status_code; +} + +int +mnt3svc_mnt(rpcsvc_request_t *req) +{ + struct iovec pvec = { + 0, + }; + char path[MNTPATHLEN]; + int ret = -1; + struct mount3_state *ms = NULL; + mountstat3 mntstat = MNT3ERR_SERVERFAULT; + struct mnt3_export *exp = NULL; + struct nfs_state *nfs = NULL; + int authcode = 0; + + if (!req) + return -1; + + pvec.iov_base = path; + pvec.iov_len = MNTPATHLEN; + ret = xdr_to_mountpath(pvec, req->msg[0]); + if (ret == -1) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Failed to decode args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "Mount state not present"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = -1; + goto rpcerr; + } + + nfs = (struct nfs_state *)ms->nfsx->private; + gf_msg_debug(GF_MNT, 0, "dirpath: %s", path); + ret = mnt3_find_export(req, path, &exp); + if (ret < 0) { + mntstat = mnt3svc_errno_to_mnterr(-ret); + goto mnterr; + } else if (!exp) { + /* + * SPECIAL CASE: exp is NULL if "path" is subdir in + * call to mnt3_find_export(). + * + * This is subdir mount, we are already DONE! + * nfs_subvolume_started() and mnt3_check_client_net_tcp() + * validation are done in mnt3_parse_dir_exports() + * which is invoked through mnt3_find_export(). + * + * TODO: All mount should happen thorugh mnt3svc_mount() + * It needs more clean up. + */ + return (0); + } + + if (!nfs_subvolume_started(nfs, exp->vol)) { + gf_msg_debug(GF_MNT, 0, "Volume %s not started", exp->vol->name); + ret = -1; + mntstat = MNT3ERR_NOENT; + goto mnterr; + } + + ret = mnt3_check_client_net_tcp(req, exp->vol->name); + if (ret == RPCSVC_AUTH_REJECT) { + mntstat = MNT3ERR_ACCES; + gf_msg_debug(GF_MNT, 0, "Client mount not allowed"); + ret = -1; + goto mnterr; + } + + /* The second authentication check is the exports/netgroups + * check. + */ + authcode = mnt3_authenticate_request(ms, req, NULL, NULL, path, NULL, NULL, + _gf_false); + if (authcode != 0) { + mntstat = MNT3ERR_ACCES; + gf_msg_debug(GF_MNT, 0, "Client mount not allowed"); + ret = -1; + goto mnterr; + } + + ret = mnt3svc_mount(req, ms, exp); + + if (ret < 0) + mntstat = mnt3svc_errno_to_mnterr(-ret); +mnterr: + if (ret < 0) { + mnt3svc_mnt_error_reply(req, mntstat); + ret = 0; + } + +rpcerr: + return ret; +} + +int +mnt3svc_null(rpcsvc_request_t *req) +{ + struct iovec dummyvec = { + 0, + }; + + if (!req) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Got NULL request!"); + return 0; + } + rpcsvc_submit_generic(req, &dummyvec, 1, NULL, 0, NULL); + return 0; +} + +mountlist +__build_mountlist(struct mount3_state *ms, int *count) +{ + struct mountbody *mlist = NULL; + struct mountbody *prev = NULL; + struct mountbody *first = NULL; + size_t namelen = 0; + int ret = -1; + struct mountentry *me = NULL; + + if ((!ms) || (!count)) + return NULL; + + /* read rmtab, other peers might have updated it */ + mount_read_rmtab(ms); + + *count = 0; + gf_msg_debug(GF_MNT, 0, "Building mount list:"); + list_for_each_entry(me, &ms->mountlist, mlist) + { + namelen = strlen(me->exname); + mlist = GF_CALLOC(1, sizeof(*mlist), gf_nfs_mt_mountbody); + if (!mlist) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto free_list; + } + if (!first) + first = mlist; + + mlist->ml_directory = GF_MALLOC(namelen + 2, gf_nfs_mt_char); + if (!mlist->ml_directory) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto free_list; + } + + strcpy(mlist->ml_directory, me->exname); + + namelen = strlen(me->hostname); + mlist->ml_hostname = GF_MALLOC(namelen + 2, gf_nfs_mt_char); + if (!mlist->ml_hostname) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto free_list; + } + + strcpy(mlist->ml_hostname, me->hostname); + + gf_msg_debug(GF_MNT, 0, "mount entry: dir: %s, host: %s", + mlist->ml_directory, mlist->ml_hostname); + if (prev) { + prev->ml_next = mlist; + prev = mlist; + } else + prev = mlist; + + (*count)++; + } + + ret = 0; + +free_list: + if (ret == -1) { + xdr_free_mountlist(first); + first = NULL; + } + + return first; +} + +mountlist +mnt3svc_build_mountlist(struct mount3_state *ms, int *count) +{ + struct mountbody *first = NULL; + + LOCK(&ms->mountlock); + { + first = __build_mountlist(ms, count); + } + UNLOCK(&ms->mountlock); + + return first; +} + +int +mnt3svc_dump(rpcsvc_request_t *req) +{ + int ret = -1; + struct mount3_state *ms = NULL; + mountlist mlist; + mountstat3 mstat = 0; + mnt3_serializer sfunc = NULL; + void *arg = NULL; + + if (!req) + return -1; + + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + rpcsvc_request_seterr(req, SYSTEM_ERR); + goto rpcerr; + } + + sfunc = (mnt3_serializer)xdr_serialize_mountlist; + mlist = mnt3svc_build_mountlist(ms, &ret); + arg = &mlist; + + if (!mlist) { + if (ret != 0) { + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = -1; + goto rpcerr; + } else { + arg = &mstat; + sfunc = (mnt3_serializer)xdr_serialize_mountstat3; + } + } + + mnt3svc_submit_reply(req, arg, sfunc); + + xdr_free_mountlist(mlist); + ret = 0; + +rpcerr: + return ret; +} + +int +mnt3svc_umount(struct mount3_state *ms, char *dirpath, char *hostname) +{ + struct mountentry *me = NULL; + int ret = -1; + gf_store_handle_t *sh = NULL; + struct nfs_state *nfs = NULL; + gf_boolean_t update_rmtab = _gf_false; + + if ((!ms) || (!dirpath) || (!hostname)) + return -1; + + nfs = (struct nfs_state *)ms->nfsx->private; + + update_rmtab = mount_open_rmtab(nfs->rmtab, &sh); + if (update_rmtab) { + ret = gf_store_lock(sh); + if (ret) + goto out_free; + } + + LOCK(&ms->mountlock); + { + if (update_rmtab) + __mount_read_rmtab(sh, &ms->mountlist, _gf_false); + + if (list_empty(&ms->mountlist)) { + ret = 0; + goto out_unlock; + } + + ret = -1; + list_for_each_entry(me, &ms->mountlist, mlist) + { + if ((strcmp(me->exname, dirpath) == 0) && + (strcmp(me->hostname, hostname) == 0)) { + ret = 0; + break; + } + } + + /* Need this check here because at the end of the search me + * might still be pointing to the last entry, which may not be + * the one we're looking for. + */ + if (ret == -1) { /* Not found in list. */ + gf_msg_trace(GF_MNT, 0, "Export not found"); + goto out_unlock; + } + + if (!me) + goto out_unlock; + + gf_msg_debug(GF_MNT, 0, "Unmounting: dir %s, host: %s", me->exname, + me->hostname); + + list_del(&me->mlist); + GF_FREE(me); + + if (update_rmtab) + __mount_rewrite_rmtab(ms, sh); + } +out_unlock: + UNLOCK(&ms->mountlock); + + if (update_rmtab) + gf_store_unlock(sh); + +out_free: + if (update_rmtab) + gf_store_handle_destroy(sh); + + return ret; +} + +int +mnt3svc_umnt(rpcsvc_request_t *req) +{ + char hostname[MNTPATHLEN]; + char dirpath[MNTPATHLEN]; + struct iovec pvec = { + 0, + }; + int ret = -1; + struct mount3_state *ms = NULL; + mountstat3 mstat = MNT3_OK; + char *colon = NULL; + + if (!req) + return -1; + + /* Remove the mount point from the exports list. */ + pvec.iov_base = dirpath; + pvec.iov_len = MNTPATHLEN; + ret = xdr_to_mountpath(pvec, req->msg[0]); + if (ret == -1) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Failed decode args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "Mount state not present"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = -1; + goto rpcerr; + } + + ret = rpcsvc_transport_peername(req->trans, hostname, MNTPATHLEN); + if (ret != 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOENT, NFS_MSG_GET_REMOTE_NAME_FAIL, + "Failed to get remote name: %s", gai_strerror(ret)); + goto rpcerr; + } + + colon = strrchr(hostname, ':'); + if (colon) { + *colon = '\0'; + } + gf_path_strip_trailing_slashes(dirpath); + gf_msg_debug(GF_MNT, 0, "dirpath: %s, hostname: %s", dirpath, hostname); + ret = mnt3svc_umount(ms, dirpath, hostname); + + if (ret == -1) { + ret = 0; + mstat = MNT3ERR_NOENT; + } + /* FIXME: also take care of the corner case where the + * client was resolvable at mount but not at the umount - vice-versa. + */ + mnt3svc_submit_reply(req, &mstat, + (mnt3_serializer)xdr_serialize_mountstat3); + +rpcerr: + return ret; +} + +int +__mnt3svc_umountall(struct mount3_state *ms) +{ + struct mountentry *me = NULL; + struct mountentry *tmp = NULL; + + if (!ms) + return -1; + + if (list_empty(&ms->mountlist)) + return 0; + + list_for_each_entry_safe(me, tmp, &ms->mountlist, mlist) + { + list_del(&me->mlist); /* Remove from the mount list */ + __mountdict_remove(ms, me); /* Remove from the mount dict */ + GF_FREE(me); + } + + return 0; +} + +int +mnt3svc_umountall(struct mount3_state *ms) +{ + int ret = -1; + if (!ms) + return -1; + + LOCK(&ms->mountlock); + { + ret = __mnt3svc_umountall(ms); + } + UNLOCK(&ms->mountlock); + + return ret; +} + +int +mnt3svc_umntall(rpcsvc_request_t *req) +{ + int ret = RPCSVC_ACTOR_ERROR; + struct mount3_state *ms = NULL; + mountstat3 mstat = MNT3_OK; + + if (!req) + return ret; + + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "Mount state not present"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + goto rpcerr; + } + + mnt3svc_umountall(ms); + mnt3svc_submit_reply(req, &mstat, + (mnt3_serializer)xdr_serialize_mountstat3); + + ret = RPCSVC_ACTOR_SUCCESS; +rpcerr: + return ret; +} + +exports +mnt3_xlchildren_to_exports(rpcsvc_t *svc, struct mount3_state *ms) +{ + struct exportnode *elist = NULL; + struct exportnode *prev = NULL; + struct exportnode *first = NULL; + size_t namelen = 0; + int ret = -1; + char *addrstr = NULL; + struct mnt3_export *ent = NULL; + struct nfs_state *nfs = NULL; + + if ((!ms) || (!svc)) + return NULL; + + nfs = (struct nfs_state *)ms->nfsx->private; + if (!nfs) + return NULL; + + LOCK(&ms->mountlock); + list_for_each_entry(ent, &ms->exportlist, explist) + { + /* If volume is not started yet, do not list it for tools like + * showmount. + */ + if (!nfs_subvolume_started(nfs, ent->vol)) + continue; + + elist = GF_CALLOC(1, sizeof(*elist), gf_nfs_mt_exportnode); + if (!elist) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto free_list; + } + if (!first) + first = elist; + namelen = strlen(ent->expname); + elist->ex_dir = GF_MALLOC(namelen + 2, gf_nfs_mt_char); + if (!elist->ex_dir) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + if (first == elist) + first = NULL; + xdr_free_exports_list(elist); + elist = NULL; + goto free_list; + } + strcpy(elist->ex_dir, ent->expname); + + addrstr = rpcsvc_volume_allowed(svc->options, ent->vol->name); + if (addrstr) { + /* create a groupnode per allowed client */ + char *pos = NULL; + char *addr = NULL; + char *addrs = NULL; + struct groupnode *group = NULL; + struct groupnode *prev_group = NULL; + + /* strtok_r() modifies the string, dup it */ + addrs = gf_strdup(addrstr); + if (!addrs) + goto free_list; + + while (1) { + /* only pass addrs on the 1st call */ + addr = strtok_r(group ? NULL : addrs, ",", &pos); + if (addr == NULL) + /* no mode clients */ + break; + + group = GF_CALLOC(1, sizeof(struct groupnode), + gf_nfs_mt_groupnode); + if (!group) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory " + "allocation failed"); + GF_FREE(addrs); + goto free_list; + } + + group->gr_name = gf_strdup(addr); + if (!group->gr_name) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory " + "allocation failed"); + GF_FREE(group); + GF_FREE(addrs); + goto free_list; + } + + /* chain the groups together */ + if (!elist->ex_groups) + elist->ex_groups = group; + else if (prev_group && !prev_group->gr_next) + prev_group->gr_next = group; + prev_group = group; + } + + GF_FREE(addrs); + } else { + elist->ex_groups = GF_CALLOC(1, sizeof(struct groupnode), + gf_nfs_mt_groupnode); + if (!elist->ex_groups) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation " + "failed"); + goto free_list; + } + + addrstr = gf_strdup("No Access"); + if (!addrstr) + goto free_list; + + elist->ex_groups->gr_name = addrstr; + } + + if (prev) { + prev->ex_next = elist; + prev = elist; + } else + prev = elist; + } + + ret = 0; + +free_list: + UNLOCK(&ms->mountlock); + if (ret == -1) { + xdr_free_exports_list(first); + first = NULL; + } + + return first; +} + +int +mnt3svc_export(rpcsvc_request_t *req) +{ + struct mount3_state *ms = NULL; + exports elist = NULL; + int ret = -1; + + if (!req) + return -1; + + ms = (struct mount3_state *)rpcsvc_request_program_private(req); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "mount state not found"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + goto err; + } + + /* Using the children translator names, build the export list */ + elist = mnt3_xlchildren_to_exports(rpcsvc_request_service(req), ms); + /* Do not return error when exports list is empty. An exports list can + * be empty when no subvolumes have come up. No point returning error + * and confusing the user. + if (!elist) { + gf_log (GF_MNT, GF_LOG_ERROR, "Failed to build exports list"); + nfs_rpcsvc_request_seterr (req, SYSTEM_ERR); + goto err; + } + */ + + /* Note how the serializer is passed to the generic reply function. */ + mnt3svc_submit_reply(req, &elist, (mnt3_serializer)xdr_serialize_exports); + + xdr_free_exports_list(elist); + ret = 0; +err: + return ret; +} + +/* + * __mnt3udp_get_mstate() Fetches mount3_state from xlator + * Linkage: Static + * Usage: Used only for UDP MOUNT codepath + */ +static struct mount3_state * +__mnt3udp_get_mstate(xlator_t *nfsx) +{ + struct nfs_state *nfs = NULL; + struct mount3_state *ms = NULL; + + if (nfsx == NULL) + return NULL; + + nfs = (struct nfs_state *)nfsx->private; + if (nfs == NULL) + return NULL; + + ms = (struct mount3_state *)nfs->mstate; + return ms; +} + +extern int +glfs_resolve_at(struct glfs *, xlator_t *, inode_t *, const char *, loc_t *, + struct iatt *, int, int); + +extern struct glfs * +glfs_new_from_ctx(glusterfs_ctx_t *); + +extern void +glfs_free_from_ctx(struct glfs *); + +static inode_t * +__mnt3udp_get_export_subdir_inode(struct svc_req *req, char *subdir, + char *expname, /* OUT */ + struct mnt3_export *exp) +{ + inode_t *inode = NULL; + loc_t loc = { + 0, + }; + struct iatt buf = { + 0, + }; + int ret = -1; + glfs_t *fs = NULL; + + if ((!req) || (!subdir) || (!expname) || (!exp)) + return NULL; + + /* AUTH check for subdir i.e. nfs.export-dir */ + if (exp->hostspec) { + struct sockaddr_in *sin = NULL; + +#if !defined(_TIRPC_SVC_H) + sin = svc_getcaller(req->rq_xprt); +#else + sin = (struct sockaddr_in *)svc_getcaller(req->rq_xprt); + /* TIRPC's svc_getcaller() returns a pointer to a + * sockaddr_in6, even though it might actually be an + * IPv4 address. It ought return a struct sockaddr and + * make the caller upcast it to the proper address family. + */ +#endif + /* And let's make sure that it's actually an IPv4 address. */ + GF_ASSERT(sin->sin_family == AF_INET); + + ret = mnt3_verify_auth(sin, exp); + if (ret) { + gf_msg(GF_MNT, GF_LOG_ERROR, EACCES, NFS_MSG_AUTH_VERIFY_FAILED, + "AUTH(nfs.export-dir) verification failed"); + errno = EACCES; + return NULL; + } + } + + /* + * IMP: glfs_t fs object is not used by glfs_resolve_at (). The main + * purpose is to not change the ABI of glfs_resolve_at () and not to + * pass a NULL object. + * + * TODO: Instead of linking against libgfapi.so, just for one API + * i.e. glfs_resolve_at(), It would be cleaner if PATH name to + * inode resolution code can be moved to libglusterfs.so or so. + * refer bugzilla for more details : + * https://bugzilla.redhat.com/show_bug.cgi?id=1161573 + */ + fs = glfs_new_from_ctx(exp->vol->ctx); + if (!fs) + return NULL; + + ret = glfs_resolve_at(fs, exp->vol, NULL, subdir, &loc, &buf, + 1 /* Follow link */, 0 /* Hard lookup */); + + glfs_free_from_ctx(fs); + + if (ret != 0) { + loc_wipe(&loc); + return NULL; + } + + inode = inode_ref(loc.inode); + snprintf(expname, PATH_MAX, "/%s%s", exp->vol->name, loc.path); + + loc_wipe(&loc); + + return inode; +} + +static inode_t * +__mnt3udp_get_export_volume_inode(struct svc_req *req, char *volpath, + char *expname, /* OUT */ + struct mnt3_export *exp) +{ + char *rpath = NULL; + inode_t *inode = NULL; + + if ((!req) || (!volpath) || (!expname) || (!exp)) + return NULL; + + rpath = strchr(volpath, '/'); + if (rpath == NULL) + rpath = "/"; + + inode = inode_from_path(exp->vol->itable, rpath); + snprintf(expname, PATH_MAX, "/%s", exp->vol->name); + + return inode; +} + +/* + * nfs3_rootfh() is used for NFS MOUNT over UDP i.e. mountudpproc3_mnt_3_svc(). + * Especially in mount3udp_thread() THREAD. Gluster NFS starts this thread + * when nfs.mount-udp is ENABLED (set to TRUE/ON). + */ +struct nfs3_fh * +nfs3_rootfh(struct svc_req *req, xlator_t *nfsx, char *path, + char *expname /* OUT */) +{ + struct nfs3_fh *fh = NULL; + inode_t *inode = NULL; + struct mnt3_export *exp = NULL; + struct mount3_state *ms = NULL; + struct nfs_state *nfs = NULL; + int mnt3type = MNT3_EXPTYPE_DIR; + int ret = RPCSVC_AUTH_REJECT; + + if ((!req) || (!nfsx) || (!path) || (!expname)) { + errno = EFAULT; + return NULL; + } + + /* + * 1. First check if the MOUNT is for whole volume. + * i.e. __mnt3udp_get_export_volume_inode () + * 2. If NOT, then TRY for SUBDIR MOUNT. + * i.e. __mnt3udp_get_export_subdir_inode () + * 3. If a subdir is exported using nfs.export-dir, + * then the mount type would be MNT3_EXPTYPE_DIR, + * so make sure to find the proper path to be + * resolved using mnt3_get_volume_subdir() + * 3. Make sure subdir export is allowed. + */ + ms = __mnt3udp_get_mstate(nfsx); + if (!ms) { + errno = EFAULT; + return NULL; + } + + exp = mnt3_mntpath_to_export(ms, path, _gf_false); + if (exp != NULL) + mnt3type = exp->exptype; + + if (mnt3type == MNT3_EXPTYPE_DIR) { + char volname[MNTPATHLEN] = { + 0, + }; + char *volptr = volname; + + /* Subdir export (nfs3.export-dirs) check */ + if (!gf_mnt3_export_dirs(ms)) { + errno = EACCES; + return NULL; + } + + path = mnt3_get_volume_subdir(path, &volptr); + if (exp == NULL) + exp = mnt3_mntpath_to_export(ms, volname, _gf_false); + } + + if (exp == NULL) { + errno = ENOENT; + return NULL; + } + + nfs = (struct nfs_state *)nfsx->private; + if (!nfs_subvolume_started(nfs, exp->vol)) { + errno = ENOENT; + return NULL; + } + + /* AUTH check: respect nfs.rpc-auth-allow/reject */ + ret = mnt3_check_client_net_udp(req, exp->vol->name, nfsx); + if (ret == RPCSVC_AUTH_REJECT) { + errno = EACCES; + return NULL; + } + + switch (mnt3type) { + case MNT3_EXPTYPE_VOLUME: + inode = __mnt3udp_get_export_volume_inode(req, path, expname, exp); + break; + + case MNT3_EXPTYPE_DIR: + inode = __mnt3udp_get_export_subdir_inode(req, path, expname, exp); + break; + + default: + /* Never reachable */ + gf_msg(GF_MNT, GF_LOG_ERROR, EFAULT, NFS_MSG_UNKNOWN_MNT_TYPE, + "Unknown MOUNT3 type"); + errno = EFAULT; + goto err; + } + + if (inode == NULL) { + /* Don't over-write errno */ + if (!errno) + errno = ENOENT; + goto err; + } + + /* Build the inode from FH */ + fh = GF_CALLOC(1, sizeof(*fh), gf_nfs_mt_nfs3_fh); + if (fh == NULL) { + errno = ENOMEM; + goto err; + } + + (void)nfs3_build_fh(inode, exp->volumeid, fh); + +err: + if (inode) + inode_unref(inode); + + return fh; +} + +int +mount3udp_add_mountlist(xlator_t *nfsx, char *host, char *export) +{ + struct mountentry *me = NULL; + struct mount3_state *ms = NULL; + + if ((!host) || (!export) || (!nfsx)) + return -1; + + ms = __mnt3udp_get_mstate(nfsx); + if (!ms) + return -1; + + me = GF_CALLOC(1, sizeof(*me), gf_nfs_mt_mountentry); + if (!me) + return -1; + + snprintf(me->exname, MNTPATHLEN, "%s", export); + snprintf(me->hostname, MNTPATHLEN, "%s", host); + INIT_LIST_HEAD(&me->mlist); + LOCK(&ms->mountlock); + { + list_add_tail(&me->mlist, &ms->mountlist); + mount_rewrite_rmtab(ms, NULL); + } + UNLOCK(&ms->mountlock); + return 0; +} + +int +mount3udp_delete_mountlist(xlator_t *nfsx, char *hostname, char *export) +{ + struct mount3_state *ms = NULL; + + if ((!hostname) || (!export) || (!nfsx)) + return -1; + + ms = __mnt3udp_get_mstate(nfsx); + if (!ms) + return -1; + + mnt3svc_umount(ms, export, hostname); + return 0; +} + +/** + * This function will parse the hostip (IP address, IP range, or hostname) + * and fill the host_auth_spec structure. + * + * @param hostspec - struct host_auth_spec + * @param hostip - IP address, IP range (CIDR format) or hostname + * + * @return 0 - on success and -1 on failure + * + * NB: This does not support IPv6 currently. + */ +int +mnt3_export_fill_hostspec(struct host_auth_spec *hostspec, const char *hostip) +{ + char *ipdupstr = NULL; + char *savptr = NULL; + char *endptr = NULL; + char *ip = NULL; + char *token = NULL; + int ret = -1; + long prefixlen = IPv4_ADDR_SIZE; /* default */ + uint32_t shiftbits = 0; + size_t length = 0; + + /* Create copy of the string so that the source won't change + */ + ipdupstr = gf_strdup(hostip); + if (NULL == ipdupstr) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto err; + } + + ip = strtok_r(ipdupstr, "/", &savptr); + /* Validate the Hostname or IPv4 address + * TODO: IPv6 support for subdir auth. + */ + length = strlen(ip); + if ((!valid_ipv4_address(ip, (int)length, _gf_false)) && + (!valid_host_name(ip, (int)length))) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid hostname or IPv4 address: %s", ip); + goto err; + } + + hostspec->host_addr = gf_strdup(ip); + if (NULL == hostspec->host_addr) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto err; + } + + /** + * User provided CIDR address (xx.xx.xx.xx/n format) is split + * into HOST (IP addr or hostname) and network prefix(n) from + * which netmask would be calculated. This CIDR address may + * denote a single, distinct interface address or the beginning + * address of an entire network. + * + * e.g. the IPv4 block 192.168.100.0/24 represents the 256 + * IPv4 addresses from 192.168.100.0 to 192.168.100.255. + * Therefore to check if an IP matches 192.168.100.0/24 + * we should mask the IP with FFFFFF00 and compare it with + * host address part of CIDR. + * + * Refer: mask_match() in common-utils.c. + */ + token = strtok_r(NULL, "/", &savptr); + if (token != NULL) { + prefixlen = strtol(token, &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || (prefixlen < 0) || + (prefixlen > IPv4_ADDR_SIZE)) { + gf_msg(THIS->name, GF_LOG_WARNING, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid IPv4 subnetwork mask"); + goto err; + } + } + + /* + * 1. Calculate the network mask address. + * 2. Convert it into Big-Endian format. + * 3. Store it in hostspec netmask. + */ + shiftbits = IPv4_ADDR_SIZE - prefixlen; + hostspec->netmask = htonl((uint32_t)~0 << shiftbits); + + ret = 0; /* SUCCESS */ +err: + if (NULL != ipdupstr) { + GF_FREE(ipdupstr); + } + return ret; +} + +/** + * This function will parse the AUTH parameter passed along with + * "export-dir" option. If AUTH parameter is present then it will be + * stripped from exportpath and stored in mnt3_export (exp) structure. + * + * @param exp - mnt3_export structure. Holds information needed for mount. + * @param exportpath - Value of "export-dir" key. Holds both export path + * and AUTH parameter for the path. + * exportpath format: <abspath>[(hostdesc[|hostspec|...])] + * + * @return This function will return 0 on success and -1 on failure. + */ +int +mnt3_export_parse_auth_param(struct mnt3_export *exp, char *exportpath) +{ + char *token = NULL; + char *savPtr = NULL; + char *hostip = NULL; + struct host_auth_spec *host = NULL; + int ret = 0; + + if (exportpath == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_PARSE_HOSTSPEC_FAIL, + "Export path is NULL"); + return -1; + } + + /* Using exportpath directly in strtok_r because we want + * to strip off AUTH parameter from exportpath. */ + token = strtok_r(exportpath, "(", &savPtr); + + /* Get the next token, which will be the AUTH parameter. */ + token = strtok_r(NULL, ")", &savPtr); + + if (NULL == token) { + /* If AUTH is not present then we should return success. */ + return 0; + } + + /* Free any previously allocated hostspec structure. */ + if (NULL != exp->hostspec) { + GF_FREE(exp->hostspec); + exp->hostspec = NULL; + } + + exp->hostspec = GF_CALLOC(1, sizeof(*(exp->hostspec)), gf_nfs_mt_auth_spec); + if (NULL == exp->hostspec) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + return -1; + } + + /* AUTH parameter can have multiple entries. For each entry + * a host_auth_spec structure is created. */ + host = exp->hostspec; + + hostip = strtok_r(token, "|", &savPtr); + + /* Parse all AUTH parameters separated by '|' */ + while (NULL != hostip) { + ret = mnt3_export_fill_hostspec(host, hostip); + if (0 != ret) { + gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_PARSE_HOSTSPEC_FAIL, + "Failed to parse hostspec: %s", hostip); + goto err; + } + + hostip = strtok_r(NULL, "|", &savPtr); + if (NULL == hostip) { + break; + } + + host->next = GF_CALLOC(1, sizeof(*(host)), gf_nfs_mt_auth_spec); + if (NULL == host->next) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto err; + } + host = host->next; + } + + /* In case of success return from here */ + return 0; +err: + /* In case of failure free up hostspec structure. */ + FREE_HOSTSPEC(exp); + + return -1; +} + +/** + * exportpath will also have AUTH options (ip address, subnet address or + * hostname) mentioned. + * exportpath format: <abspath>[(hostdesc[|hostspec|...])] + */ +struct mnt3_export * +mnt3_init_export_ent(struct mount3_state *ms, xlator_t *xl, char *exportpath, + uuid_t volumeid) +{ + struct mnt3_export *exp = NULL; + int alloclen = 0; + int ret = -1; + + if ((!ms) || (!xl)) + return NULL; + + exp = GF_CALLOC(1, sizeof(*exp), gf_nfs_mt_mnt3_export); + if (!exp) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + return NULL; + } + + if (NULL != exportpath) { + /* If exportpath is not NULL then we should check if AUTH + * parameter is present or not. If AUTH parameter is present + * then it will be stripped and stored in mnt3_export (exp) + * structure. + */ + if (0 != mnt3_export_parse_auth_param(exp, exportpath)) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_PARSE_AUTH_PARAM_FAIL, + "Failed to parse auth param"); + goto err; + } + } + + INIT_LIST_HEAD(&exp->explist); + if (exportpath) + alloclen = strlen(xl->name) + 2 + strlen(exportpath); + else + alloclen = strlen(xl->name) + 2; + + exp->expname = GF_MALLOC(alloclen, gf_nfs_mt_char); + if (!exp->expname) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + goto err; + } + + if (exportpath) { + gf_msg_trace(GF_MNT, 0, "Initing dir export: %s:%s", xl->name, + exportpath); + exp->exptype = MNT3_EXPTYPE_DIR; + ret = snprintf(exp->expname, alloclen, "/%s%s", xl->name, exportpath); + } else { + gf_msg_trace(GF_MNT, 0, "Initing volume export: %s", xl->name); + exp->exptype = MNT3_EXPTYPE_VOLUME; + ret = snprintf(exp->expname, alloclen, "/%s", xl->name); + } + if (ret < 0) { + gf_msg(xl->name, GF_LOG_ERROR, ret, NFS_MSG_SET_EXP_FAIL, + "Failed to set the export name"); + goto err; + } + /* Just copy without discrimination, we'll determine whether to + * actually use it when a mount request comes in and a file handle + * needs to be built. + */ + gf_uuid_copy(exp->volumeid, volumeid); + exp->vol = xl; + + /* On success we should return from here*/ + return exp; +err: + /* On failure free exp and it's members.*/ + if (NULL != exp) { + mnt3_export_free(exp); + exp = NULL; + } + + return exp; +} + +int +__mnt3_init_volume_direxports(struct mount3_state *ms, xlator_t *xlator, + char *optstr, uuid_t volumeid) +{ + struct mnt3_export *newexp = NULL; + int ret = -1; + char *savptr = NULL; + char *dupopt = NULL; + char *token = NULL; + + if ((!ms) || (!xlator) || (!optstr)) + return -1; + + dupopt = strdupa(optstr); + + token = strtok_r(dupopt, ",", &savptr); + while (token) { + newexp = mnt3_init_export_ent(ms, xlator, token, volumeid); + if (!newexp) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_INIT_DIR_EXP_FAIL, + "Failed to init dir " + "export: %s", + token); + ret = -1; + goto err; + } + + list_add_tail(&newexp->explist, &ms->exportlist); + token = strtok_r(NULL, ",", &savptr); + } + + ret = 0; +err: + return ret; +} + +int +__mnt3_init_volume(struct mount3_state *ms, dict_t *opts, xlator_t *xlator) +{ + struct mnt3_export *newexp = NULL; + int ret = -1; + char searchstr[1024]; + char *optstr = NULL; + uuid_t volumeid = { + 0, + }; + + if ((!ms) || (!xlator) || (!opts)) + return -1; + + gf_uuid_clear(volumeid); + if (gf_nfs_dvm_off(nfs_state(ms->nfsx))) + goto no_dvm; + + ret = snprintf(searchstr, 1024, "nfs3.%s.volume-id", xlator->name); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SNPRINTF_FAIL, + "snprintf failed"); + ret = -1; + goto err; + } + + if (dict_get(opts, searchstr)) { + ret = dict_get_str(opts, searchstr, &optstr); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED, + "Failed to read " + "option: %s", + searchstr); + ret = -1; + goto err; + } + } else { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_VOLID_MISSING, + "DVM is on but volume-id not " + "given for volume: %s", + xlator->name); + ret = -1; + goto err; + } + + if (optstr) { + ret = gf_uuid_parse(optstr, volumeid); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_PARSE_VOL_UUID_FAIL, + "Failed to parse " + "volume UUID"); + ret = -1; + goto err; + } + } + +no_dvm: + ret = snprintf(searchstr, 1024, "nfs3.%s.export-dir", xlator->name); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SNPRINTF_FAIL, + "snprintf failed"); + ret = -1; + goto err; + } + + if (dict_get(opts, searchstr)) { + ret = dict_get_str(opts, searchstr, &optstr); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED, + "Failed to read " + "option: %s", + searchstr); + ret = -1; + goto err; + } + + ret = __mnt3_init_volume_direxports(ms, xlator, optstr, volumeid); + if (ret == -1) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_DIR_EXP_SETUP_FAIL, + "Dir export " + "setup failed for volume: %s", + xlator->name); + goto err; + } + } + + if (ms->export_volumes) { + newexp = mnt3_init_export_ent(ms, xlator, NULL, volumeid); + if (!newexp) { + ret = -1; + goto err; + } + + list_add_tail(&newexp->explist, &ms->exportlist); + } + ret = 0; + +err: + return ret; +} + +int +__mnt3_init_volume_export(struct mount3_state *ms, dict_t *opts) +{ + int ret = -1; + char *optstr = NULL; + /* On by default. */ + gf_boolean_t boolt = _gf_true; + + if ((!ms) || (!opts)) + return -1; + + if (!dict_get(opts, "nfs3.export-volumes")) { + ret = 0; + goto err; + } + + ret = dict_get_str(opts, "nfs3.export-volumes", &optstr); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED, + "Failed to read option: nfs3.export-volumes"); + ret = -1; + goto err; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_STR2BOOL_FAIL, + "Failed to convert string to boolean"); + } + +err: + if (boolt == _gf_false) { + gf_msg_trace(GF_MNT, 0, "Volume exports disabled"); + ms->export_volumes = 0; + } else { + gf_msg_trace(GF_MNT, 0, "Volume exports enabled"); + ms->export_volumes = 1; + } + + return ret; +} + +int +__mnt3_init_dir_export(struct mount3_state *ms, dict_t *opts) +{ + int ret = -1; + char *optstr = NULL; + /* On by default. */ + gf_boolean_t boolt = _gf_true; + + if ((!ms) || (!opts)) + return -1; + + if (!dict_get(opts, "nfs3.export-dirs")) { + ret = 0; + goto err; + } + + ret = dict_get_str(opts, "nfs3.export-dirs", &optstr); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED, + "Failed to read option: nfs3.export-dirs"); + ret = -1; + goto err; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_STR2BOOL_FAIL, + "Failed to convert string to boolean"); + } + +err: + if (boolt == _gf_false) { + gf_msg_trace(GF_MNT, 0, "Dir exports disabled"); + ms->export_dirs = 0; + } else { + gf_msg_trace(GF_MNT, 0, "Dir exports enabled"); + ms->export_dirs = 1; + } + + return ret; +} + +int +mnt3_init_options(struct mount3_state *ms, dict_t *options) +{ + xlator_list_t *volentry = NULL; + int ret = -1; + + if ((!ms) || (!options)) + return -1; + + __mnt3_init_volume_export(ms, options); + __mnt3_init_dir_export(ms, options); + volentry = ms->nfsx->children; + while (volentry) { + gf_msg_trace(GF_MNT, 0, "Initing options for: %s", + volentry->xlator->name); + ret = __mnt3_init_volume(ms, options, volentry->xlator); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_VOL_INIT_FAIL, + "Volume init failed"); + goto err; + } + + volentry = volentry->next; + } + + ret = 0; +err: + return ret; +} + +struct mount3_state * +mnt3_init_state(xlator_t *nfsx) +{ + struct mount3_state *ms = NULL; + int ret = -1; + + if (!nfsx) + return NULL; + + ms = GF_CALLOC(1, sizeof(*ms), gf_nfs_mt_mount3_state); + if (!ms) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + return NULL; + } + + ms->iobpool = nfsx->ctx->iobuf_pool; + ms->nfsx = nfsx; + INIT_LIST_HEAD(&ms->exportlist); + ret = mnt3_init_options(ms, nfsx->options); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_OPT_INIT_FAIL, + "Options init failed"); + return NULL; + } + + INIT_LIST_HEAD(&ms->mountlist); + LOCK_INIT(&ms->mountlock); + + return ms; +} + +int +mount_init_state(xlator_t *nfsx) +{ + int ret = -1; + struct nfs_state *nfs = NULL; + + if (!nfsx) + goto out; + + nfs = (struct nfs_state *)nfs_state(nfsx); + /*Maintaining global state for MOUNT1 and MOUNT3*/ + nfs->mstate = mnt3_init_state(nfsx); + if (!nfs->mstate) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate mount state"); + goto out; + } + ret = 0; +out: + return ret; +} + +static rpcsvc_actor_t mnt3svc_actors[MOUNT3_PROC_COUNT] = { + { + "NULL", + mnt3svc_null, + NULL, + MOUNT3_NULL, + DRC_NA, + }, + {"MNT", mnt3svc_mnt, NULL, MOUNT3_MNT, DRC_NA, 0}, + {"DUMP", mnt3svc_dump, NULL, MOUNT3_DUMP, DRC_NA, 0}, + {"UMNT", mnt3svc_umnt, NULL, MOUNT3_UMNT, DRC_NA, 0}, + {"UMNTALL", mnt3svc_umntall, NULL, MOUNT3_UMNTALL, DRC_NA, 0}, + {"EXPORT", mnt3svc_export, NULL, MOUNT3_EXPORT, DRC_NA, 0}}; + +/* Static init parts are assigned here, dynamic ones are done in + * mnt3svc_init and mnt3_init_state. + * Making MOUNT3 a synctask so that the blocking DNS calls during rpc auth + * gets offloaded to syncenv, keeping the main/poll thread unblocked + */ +static rpcsvc_program_t mnt3prog = { + .progname = "MOUNT3", + .prognum = MOUNT_PROGRAM, + .progver = MOUNT_V3, + .progport = GF_MOUNTV3_PORT, + .actors = mnt3svc_actors, + .numactors = MOUNT3_PROC_COUNT, + .min_auth = AUTH_NULL, + .synctask = _gf_true, +}; + +/** + * __mnt3_mounted_exports_walk -- Walk through the mounted export directories + * and unmount the directories that are no + * longer authorized to be mounted. + * @dict: The dict to walk + * @key : The key we are on + * @val : The value associated with that key + * @tmp : Additional params (pointer to an auth params struct passed here) + * + */ +int +__mnt3_mounted_exports_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + char *path = NULL; + char *host_addr_ip = NULL; + char *host_addr_fqdn = NULL; + char *keydup = NULL; + char *colon = NULL; + struct mnt3_auth_params *auth_params = NULL; + int ret = 0; + int auth_status_code = 0; + + gf_msg_trace(GF_MNT, 0, "Checking if key %s is authorized.", key); + + auth_params = (struct mnt3_auth_params *)tmp; + + /* Since we haven't obtained a lock around the mount dict + * here, we want to duplicate the key and then process it. + * Otherwise we would potentially have a race condition + * by modifying the key in the dict when other threads + * are accessing it. + */ + keydup = strdupa(key); + + colon = strchr(keydup, ':'); + if (!colon) + return 0; + + *colon = '\0'; + + path = alloca(strlen(keydup) + 2); + snprintf(path, strlen(keydup) + 2, "/%s", keydup); + + /* Host is one character after ':' */ + host_addr_ip = colon + 1; + + /* Check if the IP is authorized */ + auth_status_code = mnt3_auth_host(auth_params, host_addr_ip, NULL, path, + FALSE, NULL); + if (auth_status_code == 0) { + goto out; + } + + ret = gf_get_hostname_from_ip(host_addr_ip, &host_addr_fqdn); + if (ret != 0) { + gf_msg(GF_MNT, GF_LOG_DEBUG, 0, NFS_MSG_AUTH_ERROR, + "Authorization failed for IP [%s], but name " + "resolution also failed!", + host_addr_ip); + goto unmount; + } + + /* If not, check if the FQDN is authorized */ + gf_msg(GF_MNT, GF_LOG_DEBUG, 0, NFS_MSG_AUTH_ERROR, + "Authorization failed for IP [%s], attempting to" + " auth hostname [%s]...", + host_addr_ip, host_addr_fqdn); + + auth_status_code = mnt3_auth_host(auth_params, host_addr_fqdn, NULL, path, + FALSE, NULL); + if (auth_status_code == 0) { + gf_msg(GF_MNT, GF_LOG_DEBUG, 0, NFS_MSG_AUTH_ERROR, + "Authorization succeeded for " + "Client [IP=%s, Hostname=%s].", + host_addr_ip, host_addr_fqdn); + goto out; + } + +unmount: + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_AUTH_ERROR, + "Client [IP=%s, Hostname=%s] not authorized for this mount. " + "Unmounting!", + host_addr_ip, host_addr_fqdn); + mnt3svc_umount(auth_params->ms, path, host_addr_ip); +out: + GF_FREE(host_addr_fqdn); + return 0; +} + +/** + * _mnt3_invalidate_old_mounts -- Calls __mnt3_mounted_exports_walk which checks + * checks if hosts are authorized to be mounted + * and umounts them. + * + * @ms: The mountstate for this service that holds all the information we need + * + */ +void +_mnt3_invalidate_old_mounts(struct mount3_state *ms) +{ + gf_msg_debug(GF_MNT, 0, "Invalidating old mounts ..."); + dict_foreach(ms->mountdict, __mnt3_mounted_exports_walk, ms->auth_params); +} + +/** + * _mnt3_has_file_changed -- Checks if a file has changed on disk + * + * @path: The path of the file on disk + * @oldmtime: The previous mtime of the file + * + * @return: file changed: TRUE + * otherwise : FALSE + * + * Uses get_file_mtime () in common-utils.c + */ +gf_boolean_t +_mnt3_has_file_changed(const char *path, time_t *oldmtime) +{ + gf_boolean_t changed = _gf_false; + time_t mtime = {0}; + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_MNT, path, out); + GF_VALIDATE_OR_GOTO(GF_MNT, oldmtime, out); + + ret = get_file_mtime(path, &mtime); + if (ret < 0) + goto out; + + if (mtime != *oldmtime) { + changed = _gf_true; + *oldmtime = mtime; + } +out: + return changed; +} + +/** + * _mnt_auth_param_refresh_thread - Started using pthread_create () in + * mnt3svc_init (). Reloads exports/netgroups + * files from disk and sets the auth params + * structure in the mount state to reflect + * any changes from disk. + * @argv: Unused argument + * @return: Always returns NULL + */ +void * +_mnt3_auth_param_refresh_thread(void *argv) +{ + struct mount3_state *mstate = (struct mount3_state *)argv; + char *exp_file_path = NULL; + char *ng_file_path = NULL; + size_t nbytes = 0; + time_t exp_time = 0; + time_t ng_time = 0; + gf_boolean_t any_file_changed = _gf_false; + int ret = 0; + + nbytes = strlen(exports_file_path) + 1; + exp_file_path = alloca(nbytes); + snprintf(exp_file_path, nbytes, "%s", exports_file_path); + + nbytes = strlen(netgroups_file_path) + 1; + ng_file_path = alloca(nbytes); + snprintf(ng_file_path, nbytes, "%s", netgroups_file_path); + + /* Set the initial timestamps to avoid reloading right after + * mnt3svc_init () spawns this thread */ + get_file_mtime(exp_file_path, &exp_time); + get_file_mtime(ng_file_path, &ng_time); + + while (_gf_true) { + if (mstate->stop_refresh) + break; + any_file_changed = _gf_false; + + /* Sleep before checking the file again */ + sleep(mstate->nfs->auth_refresh_time_secs); + + if (_mnt3_has_file_changed(exp_file_path, &exp_time)) { + gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_UPDATING_EXP, + "File %s changed, updating exports,", exp_file_path); + + ret = mnt3_auth_set_exports_auth(mstate->auth_params, + exp_file_path); + if (ret) + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SET_EXP_AUTH_PARAM_FAIL, + "Failed to set export auth params."); + else + any_file_changed = _gf_true; + } + + if (_mnt3_has_file_changed(ng_file_path, &ng_time)) { + gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_UPDATING_NET_GRP, + "File %s changed," + "updating netgroups", + ng_file_path); + + ret = mnt3_auth_set_netgroups_auth(mstate->auth_params, + ng_file_path); + if (ret) + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SET_NET_GRP_FAIL, + "Failed to set netgroup auth params."); + else + any_file_changed = _gf_true; + } + + /* If no files changed, go back to sleep */ + if (!any_file_changed) + continue; + + gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_PURGING_AUTH_CACHE, + "Purging auth cache."); + auth_cache_purge(mstate->authcache); + + /* Walk through mounts that are no longer authorized + * and unmount them on the server side. This will + * cause subsequent file ops to fail with access denied. + */ + _mnt3_invalidate_old_mounts(mstate); + } + + return NULL; +} + +/** + * _mnt3_init_auth_params -- Initialize authentication parameters by allocating + * the struct and setting the exports & netgroups + * files as parameters. + * + * @mstate : The mount state we are going to set the auth parameters in it. + * + * @return : success: 0 for success + * failure: -EINVAL for bad args, -ENOMEM for allocation errors, < 0 + * for other errors (parsing the files, etc.) These are + * bubbled up from the functions we call to set the params. + */ +int +_mnt3_init_auth_params(struct mount3_state *mstate) +{ + int ret = -EINVAL; + char *exp_file_path = NULL; + char *ng_file_path = NULL; + size_t nbytes = 0; + + GF_VALIDATE_OR_GOTO(GF_MNT, mstate, out); + + mstate->auth_params = mnt3_auth_params_init(mstate); + if (!mstate->auth_params) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to init mount auth params."); + ret = -ENOMEM; + goto out; + } + + nbytes = strlen(exports_file_path) + 1; + exp_file_path = alloca(nbytes); + snprintf(exp_file_path, nbytes, "%s", exports_file_path); + + ret = mnt3_auth_set_exports_auth(mstate->auth_params, exp_file_path); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SET_EXP_AUTH_PARAM_FAIL, + "Failed to set export auth params."); + goto out; + } + + nbytes = strlen(netgroups_file_path) + 1; + ng_file_path = alloca(nbytes); + snprintf(ng_file_path, nbytes, "%s", netgroups_file_path); + + ret = mnt3_auth_set_netgroups_auth(mstate->auth_params, ng_file_path); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SET_EXP_AUTH_PARAM_FAIL, + "Failed to set netgroup auth params."); + goto out; + } + + ret = 0; +out: + return ret; +} + +/** + * mnt3svc_deinit -- Function called by the nfs translator to cleanup all state + * + * @nfsx : The NFS translator used to perform the cleanup + * This structure holds all the pointers to memory that we need to free + * as well as the threads that have been started. + */ +void +mnt3svc_deinit(xlator_t *nfsx) +{ + struct mount3_state *mstate = NULL; + struct nfs_state *nfs = NULL; + + if (!nfsx || !nfsx->private) + return; + + nfs = (struct nfs_state *)nfsx->private; + mstate = (struct mount3_state *)nfs->mstate; + + if (nfs->refresh_auth) { + /* Mark as true and wait for thread to exit */ + mstate->stop_refresh = _gf_true; + pthread_join(mstate->auth_refresh_thread, NULL); + } + + if (nfs->exports_auth) + mnt3_auth_params_deinit(mstate->auth_params); + + /* Unmount everything and clear mountdict */ + LOCK(&mstate->mountlock); + { + __mnt3svc_umountall(mstate); + dict_unref(mstate->mountdict); + } + UNLOCK(&mstate->mountlock); +} + +rpcsvc_program_t * +mnt3svc_init(xlator_t *nfsx) +{ + struct mount3_state *mstate = NULL; + struct nfs_state *nfs = NULL; + dict_t *options = NULL; + char *portstr = NULL; + int ret = -1; + pthread_t udp_thread; + + if (!nfsx || !nfsx->private) + return NULL; + + nfs = (struct nfs_state *)nfsx->private; + + gf_msg_debug(GF_MNT, 0, "Initing Mount v3 state"); + mstate = (struct mount3_state *)nfs->mstate; + if (!mstate) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_MNT_STATE_INIT_FAIL, + "Mount v3 state init failed"); + goto err; + } + + mstate->nfs = nfs; + + mstate->mountdict = dict_new(); + if (!mstate->mountdict) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to setup mount dict. Allocation error."); + goto err; + } + + if (nfs->exports_auth) { + ret = _mnt3_init_auth_params(mstate); + if (ret < 0) + goto err; + + mstate->authcache = auth_cache_init(nfs->auth_cache_ttl_sec); + if (!mstate->authcache) { + ret = -ENOMEM; + goto err; + } + + mstate->stop_refresh = _gf_false; /* Allow thread to run */ + ret = gf_thread_create(&mstate->auth_refresh_thread, NULL, + _mnt3_auth_param_refresh_thread, mstate, + "nfsauth"); + if (ret) { + gf_msg_debug(GF_MNT, GF_LOG_DEBUG, "Thread creation failed"); + } + + } else + gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_EXP_AUTH_DISABLED, + "Exports auth has been disabled!"); + + mnt3prog.private = mstate; + options = dict_new(); + if (options == NULL) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL, + "dict allocation failed"); + goto err; + } + + ret = gf_asprintf(&portstr, "%d", GF_MOUNTV3_PORT); + if (ret == -1) + goto err; + + ret = dict_set_dynstr(options, "transport.socket.listen-port", portstr); + if (ret == -1) + goto err; + + ret = dict_set_str(options, "transport-type", "socket"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + if (nfs->allow_insecure) { + ret = dict_set_str(options, "rpc-auth-allow-insecure", "on"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + ret = dict_set_str(options, "rpc-auth.ports.insecure", "on"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + } + + ret = rpcsvc_create_listeners(nfs->rpcsvc, options, nfsx->name); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_LISTENERS_CREATE_FAIL, + "Unable to create listeners"); + goto err; + } + + if (nfs->mount_udp) { + ret = gf_thread_create(&udp_thread, NULL, mount3udp_thread, nfsx, + "nfsudp"); + if (ret) { + gf_msg_debug(GF_MNT, GF_LOG_DEBUG, "Thread creation failed"); + } + } + if (options) + dict_unref(options); + + return &mnt3prog; +err: + if (options) + dict_unref(options); + return NULL; +} + +static rpcsvc_actor_t mnt1svc_actors[MOUNT1_PROC_COUNT] = { + {"NULL", mnt3svc_null, NULL, MOUNT1_NULL, DRC_NA, 0}, + {"MNT", NULL, NULL, MOUNT1_MNT, DRC_NA, 0}, + {"DUMP", mnt3svc_dump, NULL, MOUNT1_DUMP, DRC_NA, 0}, + {"UMNT", mnt3svc_umnt, NULL, MOUNT1_UMNT, DRC_NA, 0}, + {"UMNTALL", NULL, NULL, MOUNT1_UMNTALL, DRC_NA, 0}, + {"EXPORT", mnt3svc_export, NULL, MOUNT1_EXPORT, DRC_NA, 0}}; + +static rpcsvc_program_t mnt1prog = { + .progname = "MOUNT1", + .prognum = MOUNT_PROGRAM, + .progver = MOUNT_V1, + .progport = GF_MOUNTV1_PORT, + .actors = mnt1svc_actors, + .numactors = MOUNT1_PROC_COUNT, + .min_auth = AUTH_NULL, + .synctask = _gf_true, +}; + +rpcsvc_program_t * +mnt1svc_init(xlator_t *nfsx) +{ + struct mount3_state *mstate = NULL; + struct nfs_state *nfs = NULL; + dict_t *options = NULL; + char *portstr = NULL; + int ret = -1; + + if (!nfsx || !nfsx->private) + return NULL; + + nfs = (struct nfs_state *)nfsx->private; + + gf_msg_debug(GF_MNT, GF_LOG_DEBUG, "Initing Mount v1 state"); + mstate = (struct mount3_state *)nfs->mstate; + if (!mstate) { + gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_INIT_FAIL, + "Mount v3 state init failed"); + goto err; + } + + mnt1prog.private = mstate; + + options = dict_new(); + if (options == NULL) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL, + "dict allocation failed"); + goto err; + } + + ret = gf_asprintf(&portstr, "%d", GF_MOUNTV1_PORT); + if (ret == -1) + goto err; + + ret = dict_set_dynstr(options, "transport.socket.listen-port", portstr); + if (ret == -1) + goto err; + ret = dict_set_str(options, "transport-type", "socket"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + if (nfs->allow_insecure) { + ret = dict_set_str(options, "rpc-auth-allow-insecure", "on"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + ret = dict_set_str(options, "rpc-auth.ports.insecure", "on"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + } + +#ifdef IPV6_DEFAULT + ret = dict_set_str(options, "transport.address-family", "inet6"); + if (ret == -1) { + gf_log(GF_NFS, GF_LOG_ERROR, + "dict_set_str error when trying to enable ipv6"); + goto err; + } +#endif + + ret = rpcsvc_create_listeners(nfs->rpcsvc, options, nfsx->name); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_LISTENERS_CREATE_FAIL, + "Unable to create listeners"); + dict_unref(options); + goto err; + } + + return &mnt1prog; +err: + return NULL; +} + +int +mount_reconfigure_state(xlator_t *nfsx, dict_t *options) +{ + int ret = -1; + struct nfs_state *nfs = NULL; + struct mount3_state *ms = NULL; + struct mnt3_export *exp = NULL; + struct mnt3_export *texp = NULL; + + if ((!nfsx) || (!options)) + return (-1); + + nfs = (struct nfs_state *)nfs_state(nfsx); + if (!nfs) + return (-1); + + ms = nfs->mstate; + if (!ms) + return (-1); + + /* + * Free() up the old export list. mnt3_init_options() will + * rebuild the export list from scratch. Do it with locking + * to avoid unnecessary race conditions. + */ + LOCK(&ms->mountlock); + list_for_each_entry_safe(exp, texp, &ms->exportlist, explist) + { + list_del(&exp->explist); + mnt3_export_free(exp); + } + ret = mnt3_init_options(ms, options); + UNLOCK(&ms->mountlock); + + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RECONF_FAIL, + "Options reconfigure failed"); + return (-1); + } + + return (0); +} diff --git a/xlators/nfs/server/src/mount3.h b/xlators/nfs/server/src/mount3.h new file mode 100644 index 00000000000..b185df835ae --- /dev/null +++ b/xlators/nfs/server/src/mount3.h @@ -0,0 +1,188 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _MOUNT3_H_ +#define _MOUNT3_H_ + +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include <glusterfs/iobuf.h> +#include "nfs.h" +#include <glusterfs/list.h> +#include "xdr-nfs3.h" +#include <glusterfs/locking.h> +#include "nfs3-fh.h" +#include <glusterfs/compat-uuid.h> +#include "exports.h" +#include "mount3-auth.h" +#include "auth-cache.h" + +/* Registered with portmap */ +#define GF_MOUNTV3_PORT 38465 +#define GF_MOUNTV3_IOB (2 * GF_UNIT_KB) +#define GF_MOUNTV3_IOBPOOL (GF_MOUNTV3_IOB * 50) + +#define GF_MOUNTV1_PORT 38466 +#define GF_MNT GF_NFS "-mount" + +extern rpcsvc_program_t * +mnt3svc_init(xlator_t *nfsx); + +extern rpcsvc_program_t * +mnt1svc_init(xlator_t *nfsx); + +extern void +mnt3svc_deinit(xlator_t *nfsx); + +extern int +mount_init_state(xlator_t *nfsx); + +extern int +mount_reconfigure_state(xlator_t *nfsx, dict_t *options); + +void +mount_rewrite_rmtab(struct mount3_state *ms, char *new_rmtab); + +struct mnt3_export * +mnt3_mntpath_to_export(struct mount3_state *ms, const char *dirpath, + gf_boolean_t export_parsing_match); + +extern int +mnt3svc_update_mountlist(struct mount3_state *ms, rpcsvc_request_t *req, + const char *expname, const char *fullpath); + +int +mnt3_authenticate_request(struct mount3_state *ms, rpcsvc_request_t *req, + struct nfs3_fh *fh, const char *volname, + const char *path, char **authorized_path, + char **authorized_host, gf_boolean_t is_write_op); + +/* Data structure used to store the list of mounts points currently + * in use by NFS clients. + */ +struct mountentry { + /* Links to mount3_state->mountlist. */ + struct list_head mlist; + + /* The export name */ + char exname[MNTPATHLEN]; + char hostname[MNTPATHLEN]; + char fullpath[MNTPATHLEN]; + + gf_boolean_t has_full_path; + + /* Since this is stored in a dict, we want to be able + * to find easily get the key we used to store + * the struct in our dict + */ + char hashkey[MNTPATHLEN * 2 + 2]; +}; + +#define MNT3_EXPTYPE_VOLUME 1 +#define MNT3_EXPTYPE_DIR 2 + +/* Structure to hold export-dir AUTH parameter */ +struct host_auth_spec { + char *host_addr; /* Allowed IP or host name */ + uint32_t netmask; /* Network mask (Big-Endian) */ + struct host_auth_spec *next; /* Pointer to next AUTH struct */ +}; + +struct mnt3_export { + struct list_head explist; + + /* The string that may contain either the volume name if the full volume + * is exported or the subdirectory in the volume. + */ + char *expname; + /* + * IP address, hostname or subnets who are allowed to connect to expname + * subvolume or subdirectory + */ + struct host_auth_spec *hostspec; + xlator_t *vol; + int exptype; + + /* This holds the full path that the client requested including + * the volume name AND the subdirectory in the volume. + */ + char *fullpath; + + /* Extracted from nfs volume options if nfs.dynamicvolumes is on. + */ + uuid_t volumeid; + uuid_t mountid; +}; + +struct mount3_state { + xlator_t *nfsx; + + /* The NFS state that this belongs to */ + struct nfs_state *nfs; + + /* The buffers for all network IO are got from this pool. */ + struct iobuf_pool *iobpool; + + /* List of exports, can be volumes or directories in those volumes. */ + struct list_head exportlist; + + /* List of current mount points over all the exports from this + * server. + */ + struct list_head mountlist; + + /* Dict of current mount points over all the exports from this + * server. Mirrors the mountlist above, but can be used for + * faster lookup in the event that there are several mounts. + * Currently, each NFSOP is validated against this dict: each + * op is checked to see if the host that operates on the path + * does in fact have an entry in the mount dict. + */ + dict_t *mountdict; + + /* Used to protect the mountlist & the mount dict */ + gf_lock_t mountlock; + + /* Used to insert additional authentication parameters */ + struct mnt3_auth_params *auth_params; + + /* Set to 0 if exporting full volumes is disabled. On by default. */ + gf_boolean_t export_volumes; + gf_boolean_t export_dirs; + + pthread_t auth_refresh_thread; + gf_boolean_t stop_refresh; + + struct auth_cache *authcache; +}; + +#define gf_mnt3_export_dirs(mst) ((mst)->export_dirs) + +struct mount3_resolve_state { + struct mnt3_export *exp; + struct mount3_state *mstate; + rpcsvc_request_t *req; + + char remainingdir[MNTPATHLEN]; + loc_t resolveloc; + struct nfs3_fh parentfh; +}; + +typedef struct mount3_resolve_state mnt3_resolve_t; + +int +mnt3_parse_dir_exports(rpcsvc_request_t *req, struct mount3_state *ms, + char *subdir, gf_boolean_t send_reply); + +char * +mnt3_get_volume_subdir(char *path, char **volname); + +#endif diff --git a/xlators/nfs/server/src/mount3udp_svc.c b/xlators/nfs/server/src/mount3udp_svc.c new file mode 100644 index 00000000000..1a2b0f85453 --- /dev/null +++ b/xlators/nfs/server/src/mount3udp_svc.c @@ -0,0 +1,238 @@ +/* + Copyright (c) 2012 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "xdr-nfs3.h" +#include <glusterfs/logging.h> +#include <glusterfs/mem-pool.h> +#include "nfs-mem-types.h" +#include "nfs-messages.h" +#include "mount3.h" +#include <stdio.h> +#include <stdlib.h> +#include <rpc/pmap_clnt.h> +#include <string.h> +#include <memory.h> +#include <sys/socket.h> +#include <netinet/in.h> + +extern struct nfs3_fh * +nfs3_rootfh(struct svc_req *req, xlator_t *nfsx, char *dp, char *expname); + +extern mountres3 +mnt3svc_set_mountres3(mountstat3 stat, struct nfs3_fh *fh, int *authflavor, + u_int aflen); +extern int +mount3udp_add_mountlist(xlator_t *nfsx, char *host, char *expname); + +extern int +mount3udp_delete_mountlist(xlator_t *nfsx, char *host, char *expname); + +extern mountstat3 +mnt3svc_errno_to_mnterr(int32_t errnum); + +/* only this thread will use this, no locking needed */ +char mnthost[INET_ADDRSTRLEN + 1]; + +#define MNT3UDP_AUTH_LEN 1 /* Only AUTH_UNIX for now */ + +mountres3 * +mountudpproc3_mnt_3_svc(dirpath **dpp, struct svc_req *req) +{ + struct mountres3 *res = NULL; + int *autharr = NULL; + struct nfs3_fh *fh = NULL; + char *mpath = NULL; + xlator_t *nfsx = THIS; + char expname[PATH_MAX] = { + 0, + }; + mountstat3 stat = MNT3ERR_SERVERFAULT; + + errno = 0; /* RESET errno */ + + mpath = (char *)*dpp; + while (*mpath == '/') + mpath++; + + res = GF_CALLOC(1, sizeof(*res), gf_nfs_mt_mountres3); + if (res == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Unable to allocate memory"); + goto err; + } + autharr = GF_CALLOC(MNT3UDP_AUTH_LEN, sizeof(int), gf_nfs_mt_int); + if (autharr == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Unable to allocate memory"); + goto err; + } + + autharr[0] = AUTH_UNIX; + + fh = nfs3_rootfh(req, nfsx, mpath, (char *)expname); + + /* FAILURE: No FH */ + if (fh == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_GET_FH_FAIL, + "Unable to get fh for %s", mpath); + if (errno) + stat = mnt3svc_errno_to_mnterr(errno); + *res = mnt3svc_set_mountres3(stat, NULL /* fh */, autharr, + MNT3UDP_AUTH_LEN); + return res; + } + + /* SUCCESS */ + stat = MNT3_OK; + *res = mnt3svc_set_mountres3(stat, fh, autharr, MNT3UDP_AUTH_LEN); + (void)mount3udp_add_mountlist(nfsx, mnthost, (char *)expname); + return res; + +err: + GF_FREE(fh); + GF_FREE(res); + GF_FREE(autharr); + return NULL; +} + +mountstat3 * +mountudpproc3_umnt_3_svc(dirpath **dp, struct svc_req *req) +{ + mountstat3 *stat = NULL; + char *mpath = (char *)*dp; + xlator_t *nfsx = THIS; + + stat = GF_MALLOC(sizeof(mountstat3), gf_nfs_mt_mountstat3); + if (stat == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Unable to allocate memory"); + return NULL; + } + *stat = MNT3_OK; + (void)mount3udp_delete_mountlist(nfsx, mnthost, mpath); + return stat; +} + +static void +mountudp_program_3(struct svc_req *rqstp, register SVCXPRT *transp) +{ + union { + dirpath mountudpproc3_mnt_3_arg; + } argument; + char *result = NULL; + xdrproc_t _xdr_argument = NULL, _xdr_result = NULL; + char *(*local)(char *, struct svc_req *) = NULL; + mountres3 *res = NULL; + struct sockaddr_in *sin = NULL; + +#if !defined(_TIRPC_SVC_H) + sin = svc_getcaller(transp); +#else + sin = (struct sockaddr_in *)svc_getcaller(transp); + /* TIRPC's svc_getcaller() returns a pointer to a sockaddr_in6, even + * though it might actually be an IPv4 address. It ought return a + * struct sockaddr and make the caller upcast it to the proper + * address family. Sigh. + */ +#endif + /* And let's make sure that it's actually an IPv4 address. */ + GF_ASSERT(sin->sin_family == AF_INET); + + inet_ntop(AF_INET, &sin->sin_addr, mnthost, INET_ADDRSTRLEN + 1); + + switch (rqstp->rq_proc) { + case NULLPROC: + (void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL); + return; + + case MOUNT3_MNT: + _xdr_argument = (xdrproc_t)xdr_dirpath; + _xdr_result = (xdrproc_t)xdr_mountres3; + local = (char *(*)(char *, + struct svc_req *))mountudpproc3_mnt_3_svc; + break; + + case MOUNT3_UMNT: + _xdr_argument = (xdrproc_t)xdr_dirpath; + _xdr_result = (xdrproc_t)xdr_mountstat3; + local = (char *(*)(char *, + struct svc_req *))mountudpproc3_umnt_3_svc; + break; + + default: + svcerr_noproc(transp); + return; + } + memset((char *)&argument, 0, sizeof(argument)); + if (!svc_getargs(transp, (xdrproc_t)_xdr_argument, (caddr_t)&argument)) { + svcerr_decode(transp); + return; + } + result = (*local)((char *)&argument, rqstp); + if (result == NULL) { + gf_msg_debug(GF_MNT, 0, "PROC returned error"); + svcerr_systemerr(transp); + } + if (result != NULL && + !svc_sendreply(transp, (xdrproc_t)_xdr_result, result)) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SVC_ERROR, + "svc_sendreply returned error"); + svcerr_systemerr(transp); + } + if (!svc_freeargs(transp, (xdrproc_t)_xdr_argument, (caddr_t)&argument)) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ARG_FREE_FAIL, + "Unable to free arguments"); + } + if (result == NULL) + return; + /* free the result */ + switch (rqstp->rq_proc) { + case MOUNT3_MNT: + res = (mountres3 *)result; + GF_FREE(res->mountres3_u.mountinfo.fhandle.fhandle3_val); + GF_FREE(res->mountres3_u.mountinfo.auth_flavors.auth_flavors_val); + GF_FREE(res); + break; + + case MOUNT3_UMNT: + GF_FREE(result); + break; + } + return; +} + +void * +mount3udp_thread(void *argv) +{ + xlator_t *nfsx = argv; + register SVCXPRT *transp = NULL; + + GF_ASSERT(nfsx); + + THIS = nfsx; + + transp = svcudp_create(RPC_ANYSOCK); + if (transp == NULL) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SVC_ERROR, + "svcudp_create error"); + return NULL; + } + if (!svc_register(transp, MOUNT_PROGRAM, MOUNT_V3, mountudp_program_3, + IPPROTO_UDP)) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SVC_ERROR, + "svc_register error"); + return NULL; + } + + svc_run(); + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SVC_RUN_RETURNED, + "svc_run returned"); + return NULL; +} diff --git a/xlators/nfs/server/src/netgroups.c b/xlators/nfs/server/src/netgroups.c new file mode 100644 index 00000000000..f68a7f14a26 --- /dev/null +++ b/xlators/nfs/server/src/netgroups.c @@ -0,0 +1,1161 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2),in all + cases as published by the Free Software Foundation. +*/ + +#include "netgroups.h" +#include <glusterfs/parse-utils.h> +#include "nfs-messages.h" + +static void +_nge_print(const struct netgroup_entry *nge); +static void +_netgroup_entry_deinit(struct netgroup_entry *ptr); +static void +_netgroup_host_deinit(struct netgroup_host *host); + +static dict_t *__deleted_entries; +static struct parser *ng_file_parser; +static struct parser *ng_host_parser; + +/** + * _ng_init_parser -- Initialize the parsers used in this file + * + * @return: success: 0 (on success the parsers are initialized) + * failure: -1 + */ +static int +_ng_init_parsers() +{ + int ret = -1; + + /* Initialize the parsers. The only reason this should + * ever fail is because of 1) memory allocation errors + * 2) the regex in netgroups.h has been changed and no + * longer compiles. + */ + ng_file_parser = parser_init(NG_FILE_PARSE_REGEX); + if (!ng_file_parser) + goto out; + + ng_host_parser = parser_init(NG_HOST_PARSE_REGEX); + if (!ng_host_parser) + goto out; + + ret = 0; +out: + return ret; +} + +/** + * _ng_deinit_parsers - Free the parsers used in this file + */ +static void +_ng_deinit_parsers() +{ + parser_deinit(ng_file_parser); + parser_deinit(ng_host_parser); +} + +/** + * _netgroups_file_init - allocate a netgroup file struct + * @return: success: Pointer to an allocated netgroup file struct + * failure: NULL + * + * Not for external use. + */ +static struct netgroups_file * +_netgroups_file_init() +{ + struct netgroups_file *file = GF_MALLOC(sizeof(*file), + gf_common_mt_nfs_netgroups); + + if (!file) + goto out; + + file->filename = NULL; + file->ng_file_dict = NULL; +out: + return file; +} + +/** + * __ngf_free_walk - walk the netgroup file dict and free each element + * + * This is passed as a function pointer to dict_foreach () + * + * @dict: the dict we are walking + * @key : the key we are processing in the dict + * @val : the corresponding value in the dict + * @tmp : Pointer to additional data that may be passed in (not used) + * + * @return : Nothing + * + * Not for external use. + */ +static int +__ngf_free_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + struct netgroup_entry *nge = NULL; + + if (val) { + nge = (struct netgroup_entry *)val->data; + _netgroup_entry_deinit(nge); + val->data = NULL; + dict_del(dict, key); /* Remove the key from this dict */ + } + return 0; +} + +/** + * __deleted_entries_free_walk - free the strings in the temporary dict + * + * This is passed as a function pointer to dict_foreach () + * + * @dict: the dict we are walking + * @key : the key we are processing in the dict + * @val : the corresponding value in the dict + * @tmp : Pointer to additional data that may be passed in (not used) + * + * @return : Nothing + * + * Not for external use. + */ +static int +__deleted_entries_free_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + dict_del(dict, key); + return 0; +} + +/** + * ng_file_deinit - Free the netgroup file struct and any memory + * that is allocated for its members. + * + * @ngfile : Pointer to the netgroup file structure that needs to be freed + * @return : Nothing + * + * External facing function. + * + * Should be called by the caller of ng_file_parse () in order to free + * the memory allocated when parsing the file. + */ +void +ng_file_deinit(struct netgroups_file *ngfile) +{ + if (!ngfile) { + return; + } + + __deleted_entries = dict_new(); + GF_VALIDATE_OR_GOTO(GF_NG, __deleted_entries, out); + + GF_FREE(ngfile->filename); + dict_foreach(ngfile->ng_file_dict, __ngf_free_walk, NULL); + dict_unref(ngfile->ng_file_dict); + GF_FREE(ngfile); + + /* Clean up temporary dict we used to store "freed" names */ + dict_foreach(__deleted_entries, __deleted_entries_free_walk, NULL); + dict_unref(__deleted_entries); + __deleted_entries = NULL; +out: + return; +} + +/** + * _netgroup_entry_init - Initializes a netgroup entry struct. + * A netgroup entry struct represents a single line in a netgroups file. + * + * @return : success: Pointer to a netgroup entry struct + * : failure: NULL + * + * Not for external use. + */ +static struct netgroup_entry * +_netgroup_entry_init() +{ + struct netgroup_entry *entry = GF_CALLOC(1, sizeof(*entry), + gf_common_mt_nfs_netgroups); + return entry; +} + +/** + * __ngh_free_walk - walk the netgroup host dict and free the host + * structure associated with the key. + * + * This is passed as a function pointer to dict_foreach () + * + * @dict: the dict we are walking + * @key : the key we are processing in the dict + * @val : the corresponding value in the dict + * @tmp : Pointer to additional data that may be passed in (not used) + * + * @return : Nothing + * + * Not for external use. + */ +static int +__ngh_free_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + struct netgroup_host *ngh = NULL; + + if (val) { + ngh = (struct netgroup_host *)val->data; + _netgroup_host_deinit(ngh); + val->data = NULL; + dict_del(dict, key); + } + return 0; +} + +/** + * __nge_free_walk - walk the netgroup entry dict and free the netgroup entry + * structure associated with the key. + * + * This is passed as a function pointer to dict_foreach () + * + * @dict: the dict we are walking + * @key : the key we are processing in the dict + * @val : the corresponding value in the dict + * @tmp : Pointer to additional data that may be passed in (not used) + * + * @return : Nothing + * + * Not for external use. + */ +static int +__nge_free_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + struct netgroup_entry *nge = NULL; + + GF_VALIDATE_OR_GOTO(GF_NG, dict, out); + + if (val) { + nge = (struct netgroup_entry *)val->data; + if (!dict_get(__deleted_entries, key)) { + _netgroup_entry_deinit(nge); + val->data = NULL; + } + dict_del(dict, key); + } + +out: + return 0; +} + +/** + * _netgroup_entry_deinit - Free memory pointed to by the parameter + * and any memory allocated for members + * in the struct. This function walks the + * netgroups and hosts dicts if they + * are allocated and frees them. + * + * @ngentry: Pointer to a netgroup entry struct that needs to be freed + * + * @return : Nothing + * + * Not for external use. + */ +static void +_netgroup_entry_deinit(struct netgroup_entry *ngentry) +{ + dict_t *ng_dict = NULL; + dict_t *host_dict = NULL; + char *name = NULL; + data_t *dint = NULL; + + if (!ngentry) + return; + + ng_dict = ngentry->netgroup_ngs; + host_dict = ngentry->netgroup_hosts; + + if (ng_dict) { + /* Free the dict of netgroup entries */ + dict_foreach(ng_dict, __nge_free_walk, NULL); + dict_unref(ng_dict); + ngentry->netgroup_ngs = NULL; + } + + if (host_dict) { + /* Free the dict of host entries */ + dict_foreach(host_dict, __ngh_free_walk, NULL); + dict_unref(host_dict); + ngentry->netgroup_hosts = NULL; + } + + if (ngentry->netgroup_name) { + /* Keep track of the netgroup names we've deallocated + * We need to do this because of the nature of this data + * structure. This data structure may hold multiple + * pointers to an already freed object, but these are + * uniquely identifiable by the name. We keep track + * of these names so when we encounter a key who has + * an association to an already freed object, we don't + * free it twice. + */ + name = strdupa(ngentry->netgroup_name); + + dint = int_to_data(1); + dict_set(__deleted_entries, name, dint); + + GF_FREE(ngentry->netgroup_name); + ngentry->netgroup_name = NULL; + } + + GF_FREE(ngentry); +} + +/** + * _netgroup_host_init - Initializes a netgroup host structure. + * A netgroup host struct represents an item in a line of a netgroups file that + * looks like this : (hostname,user,domain) + * + * @return : success: Pointer to a netgroup host struct + * : failure: NULL + * + * Not for external use. + */ +static struct netgroup_host * +_netgroup_host_init() +{ + struct netgroup_host *host = GF_CALLOC(1, sizeof(*host), + gf_common_mt_nfs_netgroups); + return host; +} + +/** + * _netgroup_host_deinit - Free memory pointed to by the parameter + * and any memory allocated for members in the struct. + * + * @nghost : Pointer to a netgroup host struct that needs to be freed + * + * @return : Nothing + * + * Not for external use. + */ +static void +_netgroup_host_deinit(struct netgroup_host *host) +{ + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_NG, host, err); + + GF_FREE(host->hostname); + host->hostname = NULL; + + GF_FREE(host->user); + host->user = NULL; + + GF_FREE(host->domain); + host->domain = NULL; + + GF_FREE(host); +err: + return; +} + +/** + * _nge_dict_get - Lookup a netgroup entry from the dict based + * on the netgroup name. + * + * @dict : The dict we are looking up from. This function makes the + * assumption that the type of underlying data in the dict is of type + * struct netgroup_entry. The behavior is not defined otherwise. + * + * @ngname : Key used to lookup in the dict. + * + * @return : success: Pointer to a netgroup entry + * failure: NULL (if no such key exists in the dict) + * + * Not for external use. + */ +static struct netgroup_entry * +_nge_dict_get(dict_t *dict, const char *ngname) +{ + data_t *ngdata = NULL; + + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_NG, dict, err); + GF_VALIDATE_OR_GOTO(GF_NG, ngname, err); + + ngdata = dict_get(dict, (char *)ngname); + if (ngdata) + return (struct netgroup_entry *)ngdata->data; +err: + return NULL; +} + +/** + * _nge_dict_insert - Insert a netgroup entry into the dict using + * the netgroup name as the key. + * + * @dict : The dict we are inserting into. + * + * @nge : The data to insert into the dict. + * + * @return : nothing + * + * Not for external use. + */ +static void +_nge_dict_insert(dict_t *dict, struct netgroup_entry *nge) +{ + data_t *ngdata = NULL; + + GF_VALIDATE_OR_GOTO(GF_NG, dict, err); + GF_VALIDATE_OR_GOTO(GF_NG, nge, err); + + ngdata = bin_to_data(nge, sizeof(*nge)); + dict_set(dict, nge->netgroup_name, ngdata); +err: + return; +} + +/** + * _ngh_dict_get - Lookup a netgroup host entry from the dict based + * on the hostname. + * + * @dict : The dict we are looking up from. This function makes the + * assumption that the type of underlying data in the dict is of type + * struct netgroup_host. The behavior is not defined otherwise. + * + * @ngname : Key used to lookup in the dict. + * + * @return : success: Pointer to a netgroup host entry + * failure: NULL (if no such key exists in the dict) + * + * Externally usable. + */ +struct netgroup_host * +ngh_dict_get(dict_t *dict, const char *hostname) +{ + data_t *ngdata = NULL; + + GF_VALIDATE_OR_GOTO(GF_NG, dict, err); + GF_VALIDATE_OR_GOTO(GF_NG, hostname, err); + + ngdata = dict_get(dict, (char *)hostname); + if (!ngdata) + goto err; + + return (struct netgroup_host *)ngdata->data; + +err: + return NULL; +} + +/** + * _ngh_dict_insert - Insert a netgroup host entry into the dict using + * the netgroup name as the key. + * + * @dict : The dict we are inserting into. + * + * @nge : The data to insert into the dict. + * + * @return : nothing + * + * Not for external use. + */ +static void +_ngh_dict_insert(dict_t *dict, struct netgroup_host *ngh) +{ + data_t *ngdata = NULL; + + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_NG, dict, err); + GF_VALIDATE_OR_GOTO(GF_NG, ngh, err); + + ngdata = bin_to_data(ngh, sizeof(*ngh)); + dict_set(dict, ngh->hostname, ngdata); +err: + return; +} + +/** + * _ngh_print - Prints the netgroup host in the + * format '(hostname,user,domain)' + * + * @ngh : The netgroup host to print out + * + * @return : nothing + * + * Not for external use. + */ +static void +_ngh_print(const struct netgroup_host *ngh) +{ + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_NG, ngh, err); + + printf("(%s,%s,%s)", ngh->hostname, ngh->user ? ngh->user : "", + ngh->domain ? ngh->domain : ""); +err: + return; +} + +/** + * __nge_print_walk - walk the netgroup entry dict and print each entry + * associated with the key. This function prints + * entries of type 'struct netgroup_entry'. + * + * This is passed as a function pointer to dict_foreach () + * + * @dict: the dict we are walking + * @key : the key we are processing in the dict + * @val : the corresponding value in the dict + * @tmp : Pointer to additional data that may be passed in (not used) + * + * @return : Nothing + * + * Not for external use. + */ +static int +__nge_print_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + if (val) + _nge_print((struct netgroup_entry *)val->data); + + return 0; +} + +/** + * __ngh_print_walk - walk the netgroup entry dict and print each entry + * associated with the key. This function prints entries + * of type 'struct netgroup_host' + * + * This is passed as a function pointer to dict_foreach (), + * which is called from _nge_print (). + * + * @dict: the dict we are walking + * @key : the key we are processing in the dict + * @val : the corresponding value in the dict + * @tmp : Pointer to additional data that may be passed in (not used) + * + * @return : Nothing + * + * Not for external use. + */ +static int +__ngh_print_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + if (val) + _ngh_print((struct netgroup_host *)val->data); + + return 0; +} + +/** + * _nge_print - Prints the netgroup entry in the + * format '<netgroup name> <following entries>' + * + * @ngh : The netgroup entry to print out + * + * @return : nothing + * + * Not for external use. + */ +static void +_nge_print(const struct netgroup_entry *nge) +{ + /* Validate args */ + GF_VALIDATE_OR_GOTO(GF_NG, nge, err); + + printf("%s ", nge->netgroup_name); + if (nge->netgroup_ngs) + dict_foreach(nge->netgroup_ngs, __nge_print_walk, NULL); + + if (nge->netgroup_hosts) + dict_foreach(nge->netgroup_hosts, __ngh_print_walk, NULL); + +err: + return; +} + +/** + * __ngf_print_walk - walk through each entry in the netgroups file and print it + * out. This calls helper functions _nge_print () to print + * the netgroup entries. + * + * This is passed as a function pointer to dict_foreach (), + * which is called from ng_file_print (). + * + * @dict: the dict we are walking + * @key : the key we are processing in the dict + * @val : the corresponding value in the dict + * @tmp : Pointer to additional data that may be passed in (not used) + * + * @return : Nothing + * + * Not for external use. + */ +static int +__ngf_print_walk(dict_t *dict, char *key, data_t *val, void *tmp) +{ + struct netgroup_entry *snge = NULL; + + if (val) { + snge = (struct netgroup_entry *)val->data; + _nge_print(snge); + printf("\n"); + } + return 0; +} + +/** + * ng_file_print - Prints the netgroup file in the + * format '<netgroup name> <following entries>', etc. + * The netgroup file is a dict of netgroup entries + * which, in turn is a combination of a other 'sub' netgroup + * entries and host entries. This function prints + * all of that out by calling the corresponding print functions + * + * @ngfile : The netgroup file to print out + * + * @return : nothing + * + * External facing function. + * + * Can be called on any valid 'struct netgroups_file *' type. + */ +void +ng_file_print(const struct netgroups_file *ngfile) +{ + dict_foreach(ngfile->ng_file_dict, __ngf_print_walk, NULL); +} + +/** + * ng_file_get_netgroup - Look up a netgroup entry from the netgroups file + * based on the netgroup name and return a pointer + * to the netgroup entry. + * + * @ngfile : The netgroup file to lookup from. + * @netgroup : The netgroup name used to lookup from the netgroup file. + * + * @return : nothing + * + * External facing function. + * + * Can be called on any valid 'struct netgroups_file *' type with a valid 'char + * *' as the lookup key. + */ +struct netgroup_entry * +ng_file_get_netgroup(const struct netgroups_file *ngfile, const char *netgroup) +{ + data_t *ndata = NULL; + + GF_VALIDATE_OR_GOTO(GF_NG, ngfile, err); + GF_VALIDATE_OR_GOTO(GF_NG, netgroup, err); + + ndata = dict_get(ngfile->ng_file_dict, (char *)netgroup); + if (!ndata) + goto err; + + return (struct netgroup_entry *)ndata->data; + +err: + return NULL; +} + +/** + * __check_host_entry_str - Check if the host string which should be + * in the format '(host,user,domain)' is + * valid to be parsed. Currently checks + * if the # of commas is correct and there + * are no spaces in the string, but more + * checks can be added. + * + * @host_str : String to check + * @return : success: TRUE if valid + * failure: FALSE if not + * + * Not for external use. + */ +static gf_boolean_t +__check_host_entry_str(const char *host_str) +{ + unsigned int comma_count = 0; + unsigned int i = 0; + gf_boolean_t str_valid = _gf_true; + + GF_VALIDATE_OR_GOTO(GF_NG, host_str, out); + + for (i = 0; i < strlen(host_str); i++) { + if (host_str[i] == ',') + comma_count++; + + /* Spaces are not allowed in this string. e.g, (a,b,c) is valid + * but (a, b,c) is not. + */ + if (host_str[i] == ' ') { + str_valid = _gf_false; + goto out; + } + } + + str_valid = (comma_count == 2); +out: + return str_valid; +} + +/** + * _parse_ng_host - Parse the netgroup host string into a netgroup host struct. + * The netgroup host string is structured as follows: + * (host, user, domain) + * + * @ng_str : String to parse + * @return : success: 0 if the parsing succeeded + * failure: -EINVAL for bad args, -ENOMEM for allocation errors, + * 1 for parsing errors. + * + * Not for external use. + */ +static int +_parse_ng_host(char *ng_str, struct netgroup_host **ngh) +{ + struct netgroup_host *ng_host = NULL; + unsigned int parts = 0; + char *match = NULL; + int ret = -EINVAL; + + GF_VALIDATE_OR_GOTO(GF_NG, ng_str, out); + GF_VALIDATE_OR_GOTO(GF_NG, ngh, out); + + if (!__check_host_entry_str(ng_str)) { + ret = 1; /* Parse failed */ + goto out; + } + + ret = parser_set_string(ng_host_parser, ng_str); + if (ret < 0) + goto out; + + gf_msg_trace(GF_NG, 0, "parsing host string: %s", ng_str); + + ng_host = _netgroup_host_init(); + GF_CHECK_ALLOC(ng_host, ret, free_and_out); /* Sets ret to -ENOMEM on + * failure. + */ + while ((match = parser_get_next_match(ng_host_parser)) != NULL) { + gf_msg_trace(GF_NG, 0, "found match: %s (parts=%d)", match, parts); + + switch (parts) { + case 0: + ng_host->hostname = match; + break; + case 1: + ng_host->user = match; + break; + case 2: + ng_host->domain = match; + break; + default: + GF_FREE(match); + break; + }; + + /* We only allow three parts in the host string; + * The format for the string is (a,b,c) + */ + parts++; + if (parts > 2) + break; + } + + /* Set the parameter */ + *ngh = ng_host; + ret = 0; + +free_and_out: + parser_unset_string(ng_host_parser); +out: + return ret; +} + +/** + * _ng_handle_host_part - Parse the host string that looks like this : + * '(dev1763.prn2.facebook.com,,)' into a host + * struct and insert it into the parent netgroup's + * host dict. + * @match : The host string + * @ngp : The parent netgroup + * + * @return: success: 0 if parsing succeeded + * failure: -EINVAL for bad args, other errors bubbled up + * from _parse_ng_host. + * + * + * Not for external use. + */ +static int +_ng_handle_host_part(char *match, struct netgroup_entry *ngp) +{ + struct netgroup_host *ngh = NULL; + int ret = -EINVAL; + + GF_VALIDATE_OR_GOTO(GF_NG, match, out); + GF_VALIDATE_OR_GOTO(GF_NG, ngp, out); + + if (!ngp->netgroup_name) { + gf_msg(GF_NG, GF_LOG_WARNING, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid: Line starts with hostname!"); + goto out; + } + + /* Parse the host string and get a struct for it */ + ret = _parse_ng_host(match, &ngh); + if (ret < 0) { + gf_msg(GF_NG, GF_LOG_CRITICAL, -ret, NFS_MSG_PARSE_FAIL, + "Critical error : %s", strerror(-ret)); + goto out; + } + if (ret != 0) { + /* Cannot change to gf_msg + * gf_msg not giving output to STDOUT + * Bug id : BZ1215017 + */ + gf_log(GF_NG, GF_LOG_WARNING, "Parse error for: %s", match); + goto out; + } + + /* Make dict for the parent entry's netgroup hosts */ + if (!ngp->netgroup_hosts) { + ngp->netgroup_hosts = dict_new(); + GF_CHECK_ALLOC(ngp->netgroup_hosts, ret, out); + } + + /* Insert this entry into the parent netgroup dict */ + _ngh_dict_insert(ngp->netgroup_hosts, ngh); + +out: + return ret; +} + +/** + * _ng_handle_netgroup_part - Parse the netgroup string that should just be one + * string. This may insert the netgroup into the file + * struct if it does not already exist. Frees the + * parameter match if the netgroup was already found + * in the file. + * + * @match : The netgroup string + * @ngp : The netgroup file we may insert the entry into + * @ng_entry : Double pointer to the netgroup entry we want to allocate and set. + * + * @return: success: 0 if parsing succeeded + * failure: -EINVAL for bad args, other errors bubbled up + * from _parse_ng_host. + * + * + * Not for external use. + */ +static int +_ng_setup_netgroup_entry(char *match, struct netgroups_file *file, + struct netgroup_entry **ng_entry) +{ + struct netgroup_entry *nge = NULL; + int ret = -EINVAL; + + GF_VALIDATE_OR_GOTO(GF_NG, match, out); + GF_VALIDATE_OR_GOTO(GF_NG, file, out); + GF_VALIDATE_OR_GOTO(GF_NG, ng_entry, out); + + nge = _netgroup_entry_init(); + GF_CHECK_ALLOC(nge, ret, out); + + nge->netgroup_name = match; + + /* Insert this new entry into the file dict */ + _nge_dict_insert(file->ng_file_dict, nge); + + *ng_entry = nge; + + ret = 0; +out: + return ret; +} + +/** + * _parse_ng_line - Parse a line in the netgroups file into a netgroup entry + * struct. The netgroup line is structured as follows: + * 'netgroupx netgroupy (hosta,usera,domaina)...' OR + * 'netgroupx netgroupy netgroupz...' OR + * 'netgroupx (hosta,usera,domaina) (hostb,userb,domainb)' + * This function parses this into a netgroup entry + * which will hold either a dict of netgroups and/or + * a dict of hosts that make up this netgroup. + * + * In general terms, the data structure to represent a netgroups file + * is a set of nested dictionaries. Each line in the netgroups file + * is compiled into a struct netgroup_entry structure that holds a dict + * of netgroups and a dict of hostnames. The first string in the netgroups + * line is the parent netgroup entry and the rest of the items in the line + * are the children of that parent netgroup entry. (Hence variables ngp + * and nge). + * + * A sample netgroup file may look like this: + * + * async async.ash3 async.ash4 + * async.ash3 async.04.ash3 + * async04.ash3 (async001.ash3.facebook.com,,) (async002.ash3.facebook.com,,) + * + * _parse_ng_line will get called on each line, so on the first call to this + * function, our data structure looks like this: + * + * + * dict [ + * 'async' --> dict [ + * 'async.ash3' + * 'async.ash4' + * ] + * ] + * + * On the second call to the function with the second line, our data structure + * looks like this: + * + * dict [ + * 'async' --> dict [ + * 'async.ash3' -> dict [ 'async.04.ash3' ] + * 'async.ash4' ^ + * ] | + * | + * 'async.ash3' ------------------------------ + * ] + * + * And so on. + * + * The obvious answer to storing this file in a data structure may be a tree + * but lookups from a tree are expensive and since we may be looking up stuff + * in this file in the I/O path, we can't afford expensive lookups. + * + * @ng_str : String to parse + * @file : Netgroup file to put the parsed line into + * @ng_entry : Double pointer to struct that we are going to allocate and fill + * + * The string gets parsed into a structure pointed to by + * the parameter 'ng_entry' + * + * @return : success: 0 if parsing succeeded + * failure: NULL if not + * + * Not for external use. + */ +static int +_parse_ng_line(char *ng_str, struct netgroups_file *file, + struct netgroup_entry **ng_entry) +{ + struct netgroup_entry *ngp = NULL; /* Parent netgroup entry */ + struct netgroup_entry *nge = NULL; /* Generic netgroup entry */ + char *match = NULL; + int ret = -EINVAL; + unsigned int num_entries = 0; + + /* Validate arguments */ + GF_VALIDATE_OR_GOTO(GF_NG, ng_str, out); + GF_VALIDATE_OR_GOTO(GF_NG, file, out); + + if (*ng_str == ' ' || *ng_str == '\0' || *ng_str == '\n') { + ret = 0; + goto out; + } + + ret = parser_set_string(ng_file_parser, ng_str); + if (ret < 0) + goto out; + + /* This is the first name in the line, and should be the + * parent netgroup entry. + */ + match = parser_get_next_match(ng_file_parser); + if (!match) { + ret = 1; + gf_msg(GF_NG, GF_LOG_WARNING, 0, NFS_MSG_FIND_FIRST_MATCH_FAIL, + "Unable to find " + "first match."); + gf_msg(GF_NG, GF_LOG_WARNING, 0, NFS_MSG_PARSE_FAIL, + "Error parsing str: %s", ng_str); + goto out; + } + + /* Lookup to see if the match already exists, + * if not, set the parent. + */ + ngp = _nge_dict_get(file->ng_file_dict, match); + if (!ngp) { + ret = _ng_setup_netgroup_entry(match, file, &ngp); + if (ret < 0) { + /* Bubble up error to caller. We don't need to free ngp + * here because this can only fail if allocating the + * struct fails. + */ + goto out; + } + } else + GF_FREE(match); + + if (!ngp->netgroup_ngs) { + /* If a netgroup dict has not been allocated + * for this parent, allocate it. + */ + ngp->netgroup_ngs = dict_new(); + GF_CHECK_ALLOC(ngp->netgroup_ngs, ret, out); + /* No need to free anything here since ngp is already + * a part of the file. When the file gets + * deallocated, we will free ngp. + */ + } + + while ((match = parser_get_next_match(ng_file_parser)) != NULL) { + num_entries++; + /* This means that we hit a host entry in the line */ + if (*match == '(') { + ret = _ng_handle_host_part(match, ngp); + GF_FREE(match); + if (ret != 0) { + /* If parsing the host fails, bubble the error + * code up to the caller. + */ + goto out; + } + } else { + nge = _nge_dict_get(file->ng_file_dict, match); + if (!nge) { + ret = _ng_setup_netgroup_entry(match, file, &nge); + if (ret < 0) { + /* Bubble up error to caller. We don't + * need to free nge here because this + * can only fail if allocating the + * struct fails. + */ + goto out; + } + } else + GF_FREE(match); + + /* Insert the netgroup into the parent's dict */ + _nge_dict_insert(ngp->netgroup_ngs, nge); + } + } + + /* If there are no entries on the RHS, log an error, but continue */ + if (!num_entries) { + /* Cannot change to gf_msg + * gf_msg not giving output to STDOUT + * Bug id : BZ1215017 + */ + gf_log(GF_NG, GF_LOG_WARNING, + "No netgroups were specified except for the parent."); + } + + *ng_entry = ngp; + ret = 0; + +out: + parser_unset_string(ng_file_parser); + return ret; +} + +/** + * ng_file_parse - Parse a netgroups file into a the netgroups file struct. + * This is the external facing function that must be called + * to parse a netgroups file. This function returns a netgroup + * file struct that is allocated and must be freed using + * ng_file_deinit. + * + * @filepath : Path to the netgroups file we need to parse + * + * @return : success: Pointer to a netgroup file struct if parsing succeeded + * failure: NULL if not + * + * Externally facing function + */ +struct netgroups_file * +ng_file_parse(const char *filepath) +{ + FILE *fp = NULL; + size_t len = 0; + size_t read = 0; + char *line = NULL; + struct netgroups_file *file = NULL; + struct netgroup_entry *nge = NULL; + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_NG, filepath, err); + + fp = fopen(filepath, "r"); + if (!fp) + goto err; + + file = _netgroups_file_init(); + if (!file) + goto err; + + file->ng_file_dict = dict_new(); + if (!file->ng_file_dict) { + gf_msg(GF_NG, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate netgroup file dict"); + goto err; + } + + file->filename = gf_strdup(filepath); + if (!file->filename) { + gf_msg(GF_NG, GF_LOG_CRITICAL, errno, NFS_MSG_FILE_OP_FAILED, + "Failed to duplicate filename"); + goto err; + } + + ret = _ng_init_parsers(); + if (ret < 0) + goto err; + + /* Read the file line-by-line and parse it */ + while ((read = getline(&line, &len, fp)) != -1) { + if (*line == '#') /* Lines starting with # are comments */ + continue; + + /* Parse the line into a netgroup entry */ + ret = _parse_ng_line(line, file, &nge); + if (ret == -ENOMEM) { + gf_msg(GF_NG, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Allocation error " + "while parsing line!"); + goto err; + } + if (ret != 0) { + gf_msg_debug(GF_NG, 0, "Failed to parse line %s", line); + continue; + } + } + + /* line got allocated through getline(), don't use GF_FREE() for it */ + free(line); + + if (fp) + fclose(fp); + + _ng_deinit_parsers(); + + return file; + +err: + if (line) + free(line); + + if (file) + ng_file_deinit(file); + + _ng_deinit_parsers(); + + if (fp) + fclose(fp); + return NULL; +} diff --git a/xlators/nfs/server/src/netgroups.h b/xlators/nfs/server/src/netgroups.h new file mode 100644 index 00000000000..9c715f75d3c --- /dev/null +++ b/xlators/nfs/server/src/netgroups.h @@ -0,0 +1,53 @@ +/* + Copyright 2014-present Facebook. All Rights Reserved + + This file is part of GlusterFS. + + Author : + Shreyas Siravara <shreyas.siravara@gmail.com> + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NETGROUPS_H +#define _NETGROUPS_H + +#include "nfs-mem-types.h" +#include <glusterfs/dict.h> +#include "nfs.h" + +#define GF_NG GF_NFS "-netgroup" + +#define NG_FILE_PARSE_REGEX "([a-zA-Z0-9.(,)-]+)" +#define NG_HOST_PARSE_REGEX "([a-zA-Z0-9.-]+)" + +struct netgroup_host { + char *hostname; /* Hostname of entry */ + char *user; /* User field in the entry */ + char *domain; /* Domain field in the entry */ +}; + +struct netgroup_entry { + char *netgroup_name; /* Name of the netgroup */ + dict_t *netgroup_ngs; /* Dict of netgroups in this netgroup */ + dict_t *netgroup_hosts; /* Dict of hosts in this netgroup. */ +}; + +struct netgroups_file { + char *filename; /* Filename on disk */ + dict_t *ng_file_dict; /* Dict of netgroup entries */ +}; + +struct netgroups_file * +ng_file_parse(const char *filepath); + +struct netgroup_entry * +ng_file_get_netgroup(const struct netgroups_file *ngfile, const char *netgroup); + +void +ng_file_deinit(struct netgroups_file *ngfile); + +#endif /* _NETGROUPS_H */ diff --git a/xlators/nfs/server/src/nfs-common.c b/xlators/nfs/server/src/nfs-common.c new file mode 100644 index 00000000000..b8f6b6f318e --- /dev/null +++ b/xlators/nfs/server/src/nfs-common.c @@ -0,0 +1,450 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include "xdr-nfs3.h" +#include "msg-nfs3.h" +#include <glusterfs/iobuf.h> +#include "nfs-common.h" +#include "nfs-fops.h" +#include "nfs-mem-types.h" +#include "rpcsvc.h" +#include <glusterfs/iatt.h> +#include "nfs-messages.h" + +#include <libgen.h> + +xlator_t * +nfs_xlid_to_xlator(xlator_list_t *cl, uint8_t xlid) +{ + xlator_t *xl = NULL; + uint8_t id = 0; + + while (id <= xlid) { + if (!cl) { + xl = NULL; + break; + } + + xl = cl->xlator; + cl = cl->next; + id++; + } + + return xl; +} + +xlator_t * +nfs_path_to_xlator(xlator_list_t *cl, char *path) +{ + return NULL; +} + +uint16_t +nfs_xlator_to_xlid(xlator_list_t *cl, xlator_t *xl) +{ + uint16_t xlid = 0; + + if ((!cl) || (!xl)) + return 0; + + while (cl) { + if (xl == cl->xlator) + break; + cl = cl->next; + ++xlid; + } + + return xlid; +} + +xlator_t * +nfs_mntpath_to_xlator(xlator_list_t *cl, char *path) +{ + char *volname = NULL; /* volume name only */ + char *volptr = NULL; /* ptr to original volname */ + size_t pathlen = -1; + xlator_t *targetxl = NULL; + int i = 0; + + if ((!cl) || (!path)) + return NULL; + + gf_msg_trace(GF_NFS, 0, "Subvolume search: %s", path); + + volname = volptr = gf_strdup(path); + if (!volname) + return NULL; + + if (volname[0] == '/') + volname++; + + pathlen = strlen(volname); + for (i = 0; i < pathlen; i++) { + if (volname[i] == '/') { + volname[i] = '\0'; + break; + } + } + + while (cl) { + gf_msg_trace(GF_NFS, 0, "Volname: %s and cl->xlator->name: %s", volname, + cl->xlator->name); + + if (strcmp(volname, cl->xlator->name) == 0) { + targetxl = cl->xlator; + break; + } + + cl = cl->next; + } + + GF_FREE(volptr); + + return targetxl; +} + +void +nfs_loc_wipe(loc_t *loc) +{ + loc_wipe(loc); +} + +int +nfs_loc_copy(loc_t *dst, loc_t *src) +{ + int ret = -1; + + ret = loc_copy(dst, src); + + return ret; +} + +int +nfs_loc_fill(loc_t *loc, inode_t *inode, inode_t *parent, char *path) +{ + int ret = -EFAULT; + + if (!loc) + return ret; + + if (inode) { + loc->inode = inode_ref(inode); + if (!gf_uuid_is_null(inode->gfid)) + gf_uuid_copy(loc->gfid, inode->gfid); + } + + if (parent) + loc->parent = inode_ref(parent); + + if (path) { + loc->path = gf_strdup(path); + if (!loc->path) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "strdup failed"); + goto loc_wipe; + } + loc->name = strrchr(loc->path, '/'); + if (loc->name) + loc->name++; + } + + ret = 0; +loc_wipe: + if (ret < 0) + nfs_loc_wipe(loc); + + return ret; +} + +int +nfs_inode_loc_fill(inode_t *inode, loc_t *loc, int how) +{ + char *resolvedpath = NULL; + inode_t *parent = NULL; + int ret = -EFAULT; + + if ((!inode) || (!loc)) + return ret; + + /* If gfid is not null, then the inode is already linked to + * the inode table, and not a newly created one. For newly + * created inode, inode_path returns null gfid as the path. + */ + if (!gf_uuid_is_null(inode->gfid)) { + ret = inode_path(inode, NULL, &resolvedpath); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PATH_RESOLVE_FAIL, + "path resolution " + "failed %s", + resolvedpath); + goto err; + } + } + + if (resolvedpath == NULL) { + char tmp_path[GFID_STR_PFX_LEN + 1] = { + 0, + }; + snprintf(tmp_path, sizeof(tmp_path), "<gfid:%s>", uuid_utoa(loc->gfid)); + resolvedpath = gf_strdup(tmp_path); + } else { + parent = inode_parent(inode, loc->pargfid, NULL); + } + + ret = nfs_loc_fill(loc, inode, parent, resolvedpath); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_LOC_FILL_RESOLVE_FAIL, + "loc fill resolution failed %s", resolvedpath); + goto err; + } + + ret = 0; +err: + if (parent) + inode_unref(parent); + + GF_FREE(resolvedpath); + + return ret; +} + +int +nfs_gfid_loc_fill(inode_table_t *itable, uuid_t gfid, loc_t *loc, int how) +{ + int ret = -EFAULT; + inode_t *inode = NULL; + + if (!loc) + return ret; + + inode = inode_find(itable, gfid); + if (!inode) { + gf_msg_trace(GF_NFS, 0, + "Inode not found in itable, will " + "try to create one."); + if (how == NFS_RESOLVE_CREATE) { + gf_msg_trace(GF_NFS, 0, "Inode needs to be created."); + inode = inode_new(itable); + if (!inode) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to " + "allocate memory"); + ret = -ENOMEM; + goto err; + } + + } else { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOENT, NFS_MSG_INODE_NOT_FOUND, + "Inode not found in " + "itable and no creation was requested."); + ret = -ENOENT; + goto err; + } + } else { + gf_msg_trace(GF_NFS, 0, "Inode was found in the itable."); + } + + gf_uuid_copy(loc->gfid, gfid); + + ret = nfs_inode_loc_fill(inode, loc, how); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_INODE_LOC_FILL_ERROR, + "Inode loc filling failed.: %s", strerror(-ret)); + goto err; + } + +err: + if (inode) + inode_unref(inode); + return ret; +} + +int +nfs_root_loc_fill(inode_table_t *itable, loc_t *loc) +{ + static uuid_t rootgfid = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + return nfs_gfid_loc_fill(itable, rootgfid, loc, NFS_RESOLVE_EXIST); +} + +int +nfs_parent_inode_loc_fill(inode_t *parent, inode_t *entryinode, char *entry, + loc_t *loc) +{ + int ret = -EFAULT; + char *path = NULL; + + if ((!parent) || (!entry) || (!loc) || (!entryinode)) + return ret; + + ret = inode_path(parent, entry, &path); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PATH_RESOLVE_FAIL, + "path resolution failed %s", path); + goto err; + } + + ret = nfs_loc_fill(loc, entryinode, parent, path); + GF_FREE(path); +err: + return ret; +} + +/* Returns -1 if parent is not available, return -2 if the entry is not + * available. In case the return is going to be -2, and how = NFS_RESOLVE_CREATE + * it does however fill in the loc so that it can be used to perform a lookup + * fop for the entry. + * On other errors, return -3. 0 on success. + */ +int +nfs_entry_loc_fill(xlator_t *this, inode_table_t *itable, uuid_t pargfid, + char *entry, loc_t *loc, int how, gf_boolean_t *freshlookup) +{ + inode_t *parent = NULL; + inode_t *entryinode = NULL; + int ret = -3; + char *resolvedpath = NULL; + int pret = -3; + + if ((!itable) || (!entry) || (!loc)) + return ret; + + parent = inode_find(itable, pargfid); + + ret = -1; + /* Will need hard resolution now */ + if (!parent || inode_ctx_get(parent, this, NULL)) + goto err; + + gf_uuid_copy(loc->pargfid, pargfid); + + ret = -2; + entryinode = inode_grep(itable, parent, entry); + if (!entryinode || inode_ctx_get(entryinode, this, NULL)) { + if (how == NFS_RESOLVE_CREATE) { + /* Even though we'll create the inode and the loc for + * a missing inode, we still need to return -2 so + * that the caller can use the filled loc to call + * lookup. + */ + if (!entryinode) { + entryinode = inode_new(itable); + if (freshlookup) + *freshlookup = _gf_true; + } + /* Cannot change ret because that must + * continue to have -2. + */ + pret = nfs_parent_inode_loc_fill(parent, entryinode, entry, loc); + /* Only if parent loc fill fails, should we notify error + * through ret, otherwise, we still need to force a + * lookup by returning -2. + */ + if (pret < 0) + ret = -3; + } + goto err; + } + + ret = inode_path(parent, entry, &resolvedpath); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PATH_RESOLVE_FAIL, + "path resolution failed %s", resolvedpath); + ret = -3; + goto err; + } + + ret = nfs_loc_fill(loc, entryinode, parent, resolvedpath); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_INODE_LOC_FILL_ERROR, + "loc_fill failed %s", resolvedpath); + ret = -3; + } + +err: + if (parent) + inode_unref(parent); + + if (entryinode) + inode_unref(entryinode); + + GF_FREE(resolvedpath); + + return ret; +} + +uint32_t +nfs_hash_gfid(uuid_t gfid) +{ + uint32_t hash = 0; + uint64_t msb64 = 0; + uint64_t lsb64 = 0; + uint32_t a1 = 0; + uint32_t a2 = 0; + uint32_t a3 = 0; + uint32_t a4 = 0; + uint32_t b1 = 0; + uint32_t b2 = 0; + + if (__is_root_gfid(gfid)) + return 0x1; + + memcpy(&msb64, &gfid[8], 8); + memcpy(&lsb64, &gfid[0], 8); + + a1 = (msb64 << 32); + a2 = (msb64 >> 32); + a3 = (lsb64 << 32); + a4 = (lsb64 >> 32); + + b1 = a1 ^ a4; + b2 = a2 ^ a3; + + hash = b1 ^ b2; + + return hash; +} + +void +nfs_fix_generation(xlator_t *this, inode_t *inode) +{ + uint64_t raw_ctx = 0; + struct nfs_inode_ctx *ictx = NULL; + struct nfs_state *priv = NULL; + int ret = -1; + + if (!inode) { + return; + } + priv = this->private; + + if (inode_ctx_get(inode, this, &raw_ctx) == 0) { + ictx = (struct nfs_inode_ctx *)(uintptr_t)raw_ctx; + ictx->generation = priv->generation; + } else { + ictx = GF_CALLOC(1, sizeof(struct nfs_inode_ctx), gf_nfs_mt_inode_ctx); + if (!ictx) { + gf_msg(this->name, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "could not allocate nfs inode ctx"); + return; + } + INIT_LIST_HEAD(&ictx->shares); + ictx->generation = priv->generation; + ret = inode_ctx_put(inode, this, (uint64_t)(uintptr_t)ictx); + if (ret) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_INODE_CTX_STORE_FAIL, + "could not store nfs inode ctx"); + return; + } + } +} diff --git a/xlators/nfs/server/src/nfs-common.h b/xlators/nfs/server/src/nfs-common.h new file mode 100644 index 00000000000..bd80d8b3be5 --- /dev/null +++ b/xlators/nfs/server/src/nfs-common.h @@ -0,0 +1,73 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS_COMMON_H_ +#define _NFS_COMMON_H_ + +#include <unistd.h> + +#include <glusterfs/xlator.h> +#include "rpcsvc.h" +#include <glusterfs/iatt.h> +#include <glusterfs/compat-uuid.h> + +// NFS_PATH_MAX hard-coded to 4096 as a work around for bug 2476. +// nfs server crashes when path received is longer than PATH_MAX +#define NFS_PATH_MAX 4096 +#define NFS_NAME_MAX NAME_MAX + +#define NFS_DEFAULT_CREATE_MODE 0600 + +extern xlator_t * +nfs_xlid_to_xlator(xlator_list_t *cl, uint8_t xlid); + +extern uint16_t +nfs_xlator_to_xlid(xlator_list_t *cl, xlator_t *xl); + +extern xlator_t * +nfs_path_to_xlator(xlator_list_t *cl, char *path); + +extern xlator_t * +nfs_mntpath_to_xlator(xlator_list_t *cl, char *path); + +extern void +nfs_loc_wipe(loc_t *loc); + +extern int +nfs_loc_copy(loc_t *dst, loc_t *src); + +extern int +nfs_loc_fill(loc_t *loc, inode_t *inode, inode_t *parent, char *path); + +#define NFS_RESOLVE_EXIST 1 +#define NFS_RESOLVE_CREATE 2 + +extern int +nfs_inode_loc_fill(inode_t *inode, loc_t *loc, int how); + +extern int +nfs_ino_loc_fill(inode_table_t *itable, uuid_t gfid, loc_t *l); + +extern int +nfs_entry_loc_fill(xlator_t *this, inode_table_t *itable, uuid_t pargfid, + char *entry, loc_t *loc, int how, gf_boolean_t *freshlookup); + +extern int +nfs_root_loc_fill(inode_table_t *itable, loc_t *loc); + +extern uint32_t +nfs_hash_gfid(uuid_t gfid); + +extern int +nfs_gfid_loc_fill(inode_table_t *itable, uuid_t gfid, loc_t *loc, int how); + +void +nfs_fix_generation(xlator_t *this, inode_t *inode); +#endif diff --git a/xlators/nfs/server/src/nfs-fops.c b/xlators/nfs/server/src/nfs-fops.c new file mode 100644 index 00000000000..4d8540c2c3e --- /dev/null +++ b/xlators/nfs/server/src/nfs-fops.c @@ -0,0 +1,1632 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include <pwd.h> + +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include <glusterfs/iobuf.h> +#include <glusterfs/call-stub.h> +#include "nfs.h" +#include "nfs-fops.h" +#include "nfs-common.h" +#include "nfs3-helpers.h" +#include "nfs-mem-types.h" +#include "nfs-messages.h" +#include <libgen.h> +#include <semaphore.h> + +static int gf_auth_max_groups_nfs_log = 0; + +void +nfs_fix_groups(xlator_t *this, call_stack_t *root) +{ + struct passwd mypw; + char mystrs[1024]; + struct passwd *result; + gid_t *mygroups; + int ngroups; + int i; + int max_groups; + struct nfs_state *priv = this->private; + const gid_list_t *agl; + gid_list_t gl; + + if (!priv->server_aux_gids) { + return; + } + + /* RPC enforces the GF_AUTH_GLUSTERFS_MAX_GROUPS limit */ + max_groups = GF_AUTH_GLUSTERFS_MAX_GROUPS(root->lk_owner.len, + AUTH_GLUSTERFS_v2); + + agl = gid_cache_lookup(&priv->gid_cache, root->uid, 0, 0); + if (agl) { + if (agl->gl_count > max_groups) { + GF_LOG_OCCASIONALLY(gf_auth_max_groups_nfs_log, this->name, + GF_LOG_WARNING, + "too many groups, reducing %d -> %d", + agl->gl_count, max_groups); + } + + for (ngroups = 0; ngroups < agl->gl_count && ngroups <= max_groups; + ngroups++) { + root->groups[ngroups] = agl->gl_list[ngroups]; + } + root->ngrps = ngroups; + gid_cache_release(&priv->gid_cache, agl); + return; + } + + /* No cached list found. */ + if (getpwuid_r(root->uid, &mypw, mystrs, sizeof(mystrs), &result) != 0) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_GETPWUID_FAIL, + "getpwuid_r(%u) failed", root->uid); + return; + } + + if (!result) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_GETPWUID_FAIL, + "getpwuid_r(%u) found nothing", root->uid); + return; + } + + gf_msg_trace(this->name, 0, "mapped %u => %s", root->uid, result->pw_name); + + ngroups = gf_getgrouplist(result->pw_name, root->gid, &mygroups); + if (ngroups == -1) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_MAP_GRP_LIST_FAIL, + "could not map %s to group list", result->pw_name); + return; + } + + /* RPC enforces the GF_AUTH_GLUSTERFS_MAX_GROUPS limit */ + if (ngroups > max_groups) { + GF_LOG_OCCASIONALLY( + gf_auth_max_groups_nfs_log, this->name, GF_LOG_WARNING, + "too many groups, reducing %d -> %d", ngroups, max_groups); + } + + /* Copy data to the frame. */ + for (i = 0; i < ngroups && i < max_groups; ++i) { + gf_msg_trace(this->name, 0, "%s is in group %u", result->pw_name, + mygroups[i]); + root->groups[i] = mygroups[i]; + } + root->ngrps = ngroups; + + /* Add the group data to the cache. */ + gl.gl_list = mygroups; + gl.gl_id = root->uid; + gl.gl_uid = 0; + gl.gl_gid = 0; + gl.gl_count = ngroups; + if (gid_cache_add(&priv->gid_cache, &gl) != 1) + GF_FREE(mygroups); +} + +struct nfs_fop_local * +nfs_fop_local_init(xlator_t *nfsx) +{ + struct nfs_fop_local *l = NULL; + + if (!nfsx) + return NULL; + + l = mem_get(nfs_fop_mempool(nfsx)); + if (!l) { + gf_msg_nomem(GF_NFS, GF_LOG_ERROR, 4096); + return NULL; + } + + memset(l, 0, sizeof(*l)); + return l; +} + +void +nfs_fop_local_wipe(xlator_t *nfsx, struct nfs_fop_local *l) +{ + if ((!nfsx) || (!l)) + return; + + if (l->iobref) + iobref_unref(l->iobref); + + if (l->parent) + inode_unref(l->parent); + + if (l->inode) + inode_unref(l->inode); + + if (l->newparent) + inode_unref(l->newparent); + + if (l->dictgfid) + dict_unref(l->dictgfid); + + mem_put(l); + + return; +} + +#define nfs_stack_destroy(nfl, fram) \ + do { \ + nfs_fop_local_wipe((nfl)->nfsx, nfl); \ + (fram)->local = NULL; \ + STACK_DESTROY((fram)->root); \ + } while (0) + +pthread_mutex_t ctr = PTHREAD_MUTEX_INITIALIZER; +unsigned int cval = 1; + +int +nfs_frame_getctr() +{ + uint64_t val = 0; + + pthread_mutex_lock(&ctr); + { + if (cval == 0) + cval = 1; + val = cval; + cval++; + } + pthread_mutex_unlock(&ctr); + + return val; +} + +call_frame_t * +nfs_create_frame(xlator_t *xl, nfs_user_t *nfu) +{ + call_frame_t *frame = NULL; + int x = 0; + int y = 0; + + if ((!xl) || (!nfu) || (nfu->ngrps > NFS_NGROUPS)) + return NULL; + + frame = create_frame(xl, (call_pool_t *)xl->ctx->pool); + if (!frame) + goto err; + if (call_stack_alloc_groups(frame->root, nfu->ngrps) != 0) { + STACK_DESTROY(frame->root); + frame = NULL; + goto err; + } + + frame->root->pid = NFS_PID; + frame->root->uid = nfu->uid; + frame->root->gid = nfu->gids[NFS_PRIMGID_IDX]; + memcpy(&frame->root->identifier, &nfu->identifier, UNIX_PATH_MAX); + frame->root->lk_owner = nfu->lk_owner; + + if (nfu->ngrps != 1) { + frame->root->ngrps = nfu->ngrps - 1; + + gf_msg_trace(GF_NFS, 0, "uid: %d, gid %d, gids: %d", frame->root->uid, + frame->root->gid, frame->root->ngrps); + for (y = 0, x = 1; y < frame->root->ngrps; x++, y++) { + gf_msg_trace(GF_NFS, 0, "gid: %d", nfu->gids[x]); + frame->root->groups[y] = nfu->gids[x]; + } + } + + /* + * It's tempting to do this *instead* of using nfu above, but we need + * to have those values in case nfs_fix_groups doesn't do anything. + */ + nfs_fix_groups(xl, frame->root); + +err: + return frame; +} + +#define nfs_fop_handle_frame_create(fram, xla, nfuser, retval, errlabel) \ + do { \ + fram = nfs_create_frame(xla, (nfuser)); \ + if (!fram) { \ + retval = (-ENOMEM); \ + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, \ + "Frame creation failed"); \ + goto errlabel; \ + } \ + } while (0) + +/* Look into the inode and parent inode of a loc and save enough state + * for us to determine in the callback whether to funge the ino in the stat buf + * with 1 for the parent. + */ +#define nfs_fop_save_root_ino(locl, loc) \ + do { \ + if (((loc)->inode) && __is_root_gfid((loc)->inode->gfid)) \ + (locl)->rootinode = 1; \ + else if (((loc)->parent) && __is_root_gfid((loc)->parent->gfid)) \ + (locl)->rootparentinode = 1; \ + } while (0) + +/* Do the same for an fd */ +#define nfs_fop_save_root_fd_ino(locl, fdesc) \ + do { \ + if (__is_root_gfid((fdesc)->inode->gfid)) \ + (locl)->rootinode = 1; \ + } while (0) + +/* Use the state saved by the previous macro to funge the ino in the appropriate + * structure. + */ +#define nfs_fop_restore_root_ino(locl, fopret, preattr, postattr, prepar, \ + postpar) \ + do { \ + if (fopret == -1) \ + break; \ + if ((locl)->rootinode) { \ + if ((preattr)) { \ + ((struct iatt *)(preattr))->ia_ino = 1; \ + ((struct iatt *)(preattr))->ia_dev = 0; \ + } \ + if ((postattr)) { \ + ((struct iatt *)(postattr))->ia_ino = 1; \ + ((struct iatt *)(postattr))->ia_dev = 0; \ + } \ + } else if ((locl)->rootparentinode) { \ + if ((prepar)) { \ + ((struct iatt *)(prepar))->ia_ino = 1; \ + ((struct iatt *)(prepar))->ia_dev = 0; \ + } \ + if ((postpar)) { \ + ((struct iatt *)(postpar))->ia_ino = 1; \ + ((struct iatt *)(postpar))->ia_dev = 0; \ + } \ + } \ + } while (0) + +/* If the newly created, inode's parent is root, we'll need to funge the ino + * in the parent attr when we receive them in the callback. + */ +#define nfs_fop_newloc_save_root_ino(locl, newloc) \ + do { \ + if (((newloc)->inode) && __is_root_gfid((newloc)->inode->gfid)) \ + (locl)->newrootinode = 1; \ + else if (((newloc)->parent) && __is_root_gfid((newloc)->parent->gfid)) \ + (locl)->newrootparentinode = 1; \ + } while (0) + +#define nfs_fop_newloc_restore_root_ino(locl, fopret, preattr, postattr, \ + prepar, postpar) \ + do { \ + if (fopret == -1) \ + break; \ + \ + if ((locl)->newrootinode) { \ + if ((preattr)) \ + ((struct iatt *)(preattr))->ia_ino = 1; \ + if ((postattr)) \ + ((struct iatt *)(postattr))->ia_ino = 1; \ + } else if ((locl)->newrootparentinode) { \ + if ((prepar)) \ + ((struct iatt *)(prepar))->ia_ino = 1; \ + if ((postpar)) \ + ((struct iatt *)(postpar))->ia_ino = 1; \ + } \ + } while (0) + +dict_t * +nfs_gfid_dict(inode_t *inode) +{ + uuid_t newgfid = { + 0, + }; + unsigned char *dyngfid = NULL; + dict_t *dictgfid = NULL; + int ret = -1; + static uuid_t rootgfid = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + dyngfid = GF_MALLOC(sizeof(uuid_t), gf_common_mt_char); + if (dyngfid == NULL) + return (NULL); + + gf_uuid_generate(newgfid); + + if (gf_uuid_compare(inode->gfid, rootgfid) == 0) + memcpy(dyngfid, rootgfid, sizeof(uuid_t)); + else + memcpy(dyngfid, newgfid, sizeof(uuid_t)); + + dictgfid = dict_new(); + if (!dictgfid) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_GFID_DICT_CREATE_FAIL, + "Failed to create gfid dict"); + GF_FREE(dyngfid); + return (NULL); + } + + ret = dict_set_gfuuid(dictgfid, "gfid-req", dyngfid, false); + if (ret < 0) { + GF_FREE(dyngfid); + dict_unref(dictgfid); + return (NULL); + } + + return dictgfid; +} + +#define nfs_fop_gfid_setup(nflcl, inode, retval, erlbl) \ + do { \ + if (nflcl) { \ + (nflcl)->dictgfid = nfs_gfid_dict(inode); \ + \ + if (!((nflcl)->dictgfid)) { \ + retval = -EFAULT; \ + goto erlbl; \ + } \ + } \ + } while (0) + +/* Fops Layer Explained + * The fops layer has three types of functions. They can all be identified by + * their names. Here are the three patterns: + * + * nfs_fop_<fopname> + * This is the lowest level function that knows nothing about states and + * callbacks. At most this is required to create a frame and call the + * fop. The idea here is to provide a convenient way to call fops than + * directly use STACK_WINDs. If this type of interface is used, the caller's + * callback is responsible for doing the relevant GlusterFS state + * maintenance operations on the data returned in the callbacks. + * + * nfs_<fopname> + * Unlike the nfs_fop_<fopname> variety, this is the stateful type of fop, in + * that it silently performs all the relevant GlusterFS state maintenance + * operations on the data returned to the callbacks, leaving the caller's + * callback to just use the data returned for whatever it needs to do with that + * data, for eg. the nfs_lookup, will take care of looking up the inodes, + * revalidating them if needed and linking new inodes into the table, while + * the caller's callback, for eg, the NFSv3 LOOKUP callback can just use + * the stat bufs returned to create file handle, map the file handle into the + * fh cache and finally encode the fh and the stat bufs into a NFS reply. + * + */ + +int32_t +nfs_fop_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) +{ + struct nfs_fop_local *local = NULL; + fop_lookup_cbk_t progcbk; + + if (op_ret == 0) { + nfs_fix_generation(this, inode); + } + + nfl_to_prog_data(local, progcbk, frame); + nfs_fop_restore_root_ino(local, op_ret, buf, NULL, NULL, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, xattr, + postparent); + + nfs_stack_destroy(local, frame); + return 0; +} + +int +nfs_fop_lookup(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + fop_lookup_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!xl) || (!loc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Lookup: %s", loc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, loc); + nfs_fop_gfid_setup(nfl, loc->inode, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_lookup_cbk, xl, xl, xl->fops->lookup, loc, + nfl->dictgfid); + + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_access_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_access_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_access(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + int32_t accesstest, fop_access_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + uint32_t accessbits = 0; + + if ((!xl) || (!loc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Access: %s", loc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, loc); + + accessbits = nfs3_request_to_accessbits(accesstest); + STACK_WIND_COOKIE(frame, nfs_fop_access_cbk, xl, xl, xl->fops->access, loc, + accessbits, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_stat_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, buf, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_stat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + fop_stat_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!xl) || (!loc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Stat: %s", loc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, loc); + + STACK_WIND_COOKIE(frame, nfs_fop_stat_cbk, xl, xl, xl->fops->stat, loc, + NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_fstat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_fstat_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, buf, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_fstat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_fstat_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!fd) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "FStat"); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_fd_ino(nfl, fd); + + STACK_WIND_COOKIE(frame, nfs_fop_fstat_cbk, xl, xl, xl->fops->fstat, fd, + NULL); + + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_opendir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_opendir_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, fd, xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_opendir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fd_t *dirfd, fop_opendir_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!dirfd) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Opendir: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_opendir_cbk, xl, xl, xl->fops->opendir, + pathloc, dirfd, NULL); + ret = 0; + +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int +nfs_fop_flush_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_flush_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_flush(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_flush_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!fd) || (!nfu)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_flush_cbk, xl, xl, xl->fops->flush, fd, + NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_readdirp_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, gf_dirent_t *entries, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_readdirp_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, entries, xdata); + + nfs_stack_destroy(nfl, frame); + + return 0; +} + +int +nfs_fop_readdirp(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *dirfd, + size_t bufsize, off_t offset, fop_readdirp_cbk_t cbk, + void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!dirfd) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "readdir"); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_readdirp_cbk, xl, xl, xl->fops->readdirp, + dirfd, bufsize, offset, 0); + + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_statfs_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct statvfs *buf, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_statfs_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, buf, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_statfs(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_statfs_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Statfs: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_statfs_cbk, xl, xl, xl->fops->statfs, + pathloc, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_create_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_create_cbk_t progcbk = NULL; + + if (op_ret == 0) { + nfs_fix_generation(this, inode); + } + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, preparent, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, fd, inode, buf, + preparent, postparent, NULL); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_create(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int flags, mode_t mode, fd_t *fd, fop_create_cbk_t cbk, + void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Create: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + nfs_fop_gfid_setup(nfl, pathloc->inode, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_create_cbk, xl, xl, xl->fops->create, + pathloc, flags, mode, 0, fd, nfl->dictgfid); + + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_setattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *pre, + struct iatt *post, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_setattr_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, pre, post, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, pre, post, xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_setattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + struct iatt *buf, int32_t valid, fop_setattr_cbk_t cbk, + void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Setattr: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + + STACK_WIND_COOKIE(frame, nfs_fop_setattr_cbk, xl, xl, xl->fops->setattr, + pathloc, buf, valid, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_mkdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_mkdir_cbk_t progcbk = NULL; + + if (op_ret == 0) { + nfs_fix_generation(this, inode); + } + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, preparent, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_mkdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, fop_mkdir_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Mkdir: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + nfs_fop_gfid_setup(nfl, pathloc->inode, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_mkdir_cbk, xl, xl, xl->fops->mkdir, + pathloc, mode, 0, nfl->dictgfid); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_symlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_symlink_cbk_t progcbk = NULL; + + if (op_ret == 0) { + nfs_fix_generation(this, inode); + } + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, preparent, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_symlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, char *target, + loc_t *pathloc, fop_symlink_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!target) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Symlink: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + nfs_fop_gfid_setup(nfl, pathloc->inode, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_symlink_cbk, xl, xl, xl->fops->symlink, + target, pathloc, 0, nfl->dictgfid); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_readlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, const char *path, + struct iatt *buf, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_readlink_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, path, buf, xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_readlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + size_t size, fop_readlink_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Readlink: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + + STACK_WIND_COOKIE(frame, nfs_fop_readlink_cbk, xl, xl, xl->fops->readlink, + pathloc, size, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_mknod_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_mknod_cbk_t progcbk = NULL; + + if (op_ret == 0) { + nfs_fix_generation(this, inode); + } + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, preparent, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_mknod(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, dev_t dev, fop_mknod_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Mknod: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + nfs_fop_gfid_setup(nfl, pathloc->inode, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_mknod_cbk, xl, xl, xl->fops->mknod, + pathloc, mode, dev, 0, nfl->dictgfid); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_rmdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = frame->local; + fop_rmdir_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, NULL, NULL, preparent, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, preparent, postparent, + NULL); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_rmdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_rmdir_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Rmdir: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + + STACK_WIND_COOKIE(frame, nfs_fop_rmdir_cbk, xl, xl, xl->fops->rmdir, + pathloc, 0, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_unlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = frame->local; + fop_unlink_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, NULL, NULL, preparent, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, preparent, postparent, + xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_unlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_unlink_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Unlink: %s", pathloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, pathloc); + + STACK_WIND_COOKIE(frame, nfs_fop_unlink_cbk, xl, xl, xl->fops->unlink, + pathloc, 0, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_link_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_link_cbk_t progcbk = NULL; + + if (op_ret == 0) { + nfs_fix_generation(this, inode); + } + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, buf, NULL, preparent, postparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_link(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_link_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!oldloc) || (!newloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Link: %s -> %s", newloc->path, oldloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, newloc); + + STACK_WIND_COOKIE(frame, nfs_fop_link_cbk, xl, xl, xl->fops->link, oldloc, + newloc, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_rename_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + struct iatt *preoldparent, struct iatt *postoldparent, + struct iatt *prenewparent, struct iatt *postnewparent, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_rename_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + /* The preattr arg needs to be NULL instead of @buf because it is + * possible that the new parent is not root whereas the source dir + * could have been. That is handled in the next macro. + */ + nfs_fop_restore_root_ino(nfl, op_ret, NULL, NULL, preoldparent, + postoldparent); + nfs_fop_newloc_restore_root_ino(nfl, op_ret, buf, NULL, prenewparent, + postnewparent); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, buf, preoldparent, + postoldparent, prenewparent, postnewparent, xdata); + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_rename(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_rename_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!oldloc) || (!newloc) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Rename: %s -> %s", oldloc->path, newloc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, oldloc); + nfs_fop_newloc_save_root_ino(nfl, newloc); + + STACK_WIND_COOKIE(frame, nfs_fop_rename_cbk, xl, xl, xl->fops->rename, + oldloc, newloc, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_open_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_open_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, fd, xdata); + nfs_stack_destroy(nfl, frame); + + return 0; +} + +int +nfs_fop_open(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + int32_t flags, fd_t *fd, fop_open_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!loc) || (!fd) || (!nfu)) + return ret; + + gf_msg_trace(GF_NFS, 0, "Open: %s", loc->path); + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_open_cbk, xl, xl, xl->fops->open, loc, + flags, fd, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_writev_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_writev_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, prebuf, postbuf, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, prebuf, postbuf, xdata); + + nfs_stack_destroy(nfl, frame); + + return 0; +} + +int +nfs_fop_write(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + struct iobref *srciobref, struct iovec *vector, int32_t count, + off_t offset, fop_writev_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + int flags = 0; + nfs3_call_state_t *cs = local; + + if ((!nfsx) || (!xl) || (!fd) || (!vector) || (!nfu) || (!srciobref)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_fd_ino(nfl, fd); + /* + nfl->iobref = iobref_new (); + if (!nfl->iobref) { + gf_log (GF_NFS, GF_LOG_ERROR, "iobref creation failed"); + ret = -ENOMEM; + goto err; + } + + iobref_add (nfl->iobref, srciob); + */ + + switch (cs->writetype) { + case UNSTABLE: + break; + case DATA_SYNC: + flags |= O_DSYNC; + break; + case FILE_SYNC: + flags |= O_SYNC; + break; + } + + STACK_WIND_COOKIE(frame, nfs_fop_writev_cbk, xl, xl, xl->fops->writev, fd, + vector, count, offset, flags, srciobref, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_fsync_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_fsync_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, prebuf, postbuf, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, prebuf, postbuf, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_fsync(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + int32_t datasync, fop_fsync_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!fd)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_fd_ino(nfl, fd); + + STACK_WIND_COOKIE(frame, nfs_fop_fsync_cbk, xl, xl, xl->fops->fsync, fd, + datasync, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_readv_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iovec *vector, + int32_t count, struct iatt *stbuf, struct iobref *iobref, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_readv_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, stbuf, NULL, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, vector, count, stbuf, + iobref, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_read(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + size_t size, off_t offset, fop_readv_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!xl) || (!fd) || (!nfu)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_fd_ino(nfl, fd); + + STACK_WIND_COOKIE(frame, nfs_fop_readv_cbk, xl, xl, xl->fops->readv, fd, + size, offset, 0, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_lk_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct gf_flock *flock, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_lk_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + + if (!op_ret) + fd_lk_insert_and_merge(nfl->fd, nfl->cmd, &nfl->flock); + + fd_unref(nfl->fd); + + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, flock, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_lk(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, int cmd, + struct gf_flock *flock, fop_lk_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!xl) || (!fd) || (!nfu)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + nfl->cmd = cmd; + nfl->fd = fd_ref(fd); + nfl->flock = *flock; + + STACK_WIND_COOKIE(frame, nfs_fop_lk_cbk, xl, xl, xl->fops->lk, fd, cmd, + flock, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_getxattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *dict, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_getxattr_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, dict, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_getxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + char *name, dict_t *xdata, fop_getxattr_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!xl) || (!loc) || (!nfu)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_getxattr_cbk, xl, xl, xl->fops->getxattr, + loc, name, NULL); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_setxattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_setxattr_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_setxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + dict_t *dict, int32_t flags, dict_t *xdata, + fop_setxattr_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!xl) || (!loc) || (!nfu)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + + STACK_WIND_COOKIE(frame, nfs_fop_setxattr_cbk, xl, xl, xl->fops->setxattr, + loc, dict, flags, xdata); + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} + +int32_t +nfs_fop_truncate_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_truncate_cbk_t progcbk = NULL; + + nfl_to_prog_data(nfl, progcbk, frame); + nfs_fop_restore_root_ino(nfl, op_ret, prebuf, postbuf, NULL, NULL); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, prebuf, postbuf, xdata); + + nfs_stack_destroy(nfl, frame); + return 0; +} + +int +nfs_fop_truncate(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + off_t offset, fop_truncate_cbk_t cbk, void *local) +{ + call_frame_t *frame = NULL; + int ret = -EFAULT; + struct nfs_fop_local *nfl = NULL; + + if ((!nfsx) || (!xl) || (!loc) || (!nfu)) + return ret; + + nfs_fop_handle_frame_create(frame, nfsx, nfu, ret, err); + nfs_fop_handle_local_init(frame, nfsx, nfl, cbk, local, ret, err); + nfs_fop_save_root_ino(nfl, loc); + + STACK_WIND_COOKIE(frame, nfs_fop_truncate_cbk, xl, xl, xl->fops->truncate, + loc, offset, NULL); + + ret = 0; +err: + if (ret < 0) { + if (frame) + nfs_stack_destroy(nfl, frame); + } + + return ret; +} diff --git a/xlators/nfs/server/src/nfs-fops.h b/xlators/nfs/server/src/nfs-fops.h new file mode 100644 index 00000000000..005cb788a45 --- /dev/null +++ b/xlators/nfs/server/src/nfs-fops.h @@ -0,0 +1,242 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS_FOPS_H_ +#define _NFS_FOPS_H_ + +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include <glusterfs/iobuf.h> +#include <glusterfs/call-stub.h> +#include "nfs.h" +#include "nfs-common.h" +#include "nfs-messages.h" +#include <semaphore.h> + +/* This structure used to communicate state between a fop and its callback. + * The problem is, when we're calling a fop in the nfs op handler, the callback + * is the NFS protocol's callback and we have to perform all GlusterFS + * inode, inode table, fd_ts and fd table operations in the NFS callback. That + * approach soon gets extremely complicated and confusing because, then we have + * to try and separate in our heads which source lines in those callbacks are + * required for serving the NFS op and which ones are needed for satisfying + * GlusterFS requirements. This structure allows us avoid performing GlusterFS + * state maintenance operations inside the fops layer itself. Now, before + * we call the callback registered by the NFS operation, a hidden fops-layer + * specific callback is called which performs the state maintenance and then + * calls the NFS callback. + * + * These are allocated from a mem-pool stored in the nfs xlator's state. + * i.e. struct nfs_state. + * That is initiated in nfs_init_subvolumes in nfs.c. + */ +struct nfs_fop_local { + /* The local sent along by the user of the fop. */ + void *proglocal; + + /* The address of the callback supplied by the user. After our + * callback is executed this one is called. + * The exact cast destination of this pointer will depend on the + * fop that is being called. + */ + void *progcbk; + + /* Used only for write requests. */ + struct iobref *iobref; + + inode_t *parent; + inode_t *newparent; + inode_t *inode; + + /* Set to 1 by nfs-inodes layer, which uses this to decide whether to + * link the newly allocated inode into the itable, in case the fop was + * successful. + */ + int newinode; + + /* Used by nfs-fops layer in order to determine whether to funge the + * ino in a dir's stbuf. This funging of root ino is needed to ensure + * that the root ino remains 1 even when the NFS server has been + * restarted. Note that in distribute, a fresh lookup and a revalidate + * on the root inode returns two different inode numbers and this we + * need to handle by ourself. + */ + int rootinode; + + /* This member is used to determine whether the new parent of a file + * being renamed is the root directory. If yes, the ino is funged. + */ + int newrootinode; + int newrootparentinode; + + /* Determines whether to funge the ino in the post and pre parent + * stbufs for a file/dir where the parent directory could be the root + * dir. Needed here because of the same reason as above. + */ + int rootparentinode; + + char path[NFS_NAME_MAX + 1]; + char newpath[NFS_NAME_MAX + 1]; + xlator_t *nfsx; + dict_t *dictgfid; + + fd_t *fd; + int cmd; + struct gf_flock flock; +}; + +extern struct nfs_fop_local * +nfs_fop_local_init(xlator_t *xl); + +extern void +nfs_fop_local_wipe(xlator_t *xl, struct nfs_fop_local *l); + +#define nfs_state(nfsxl) (nfsxl)->private +#define nfs_fop_mempool(nfxl) (((struct nfs_state *)nfs_state(nfxl))->foppool) + +#define prog_data_to_nfl(nf, nflocal, fram, pcbk, plocal) \ + do { \ + nflocal = nfs_fop_local_init(nf); \ + if (nflocal) { \ + nflocal->proglocal = plocal; \ + nflocal->progcbk = *VOID(&pcbk); \ + nflocal->nfsx = nf; \ + if (fram) \ + ((call_frame_t *)fram)->local = nflocal; \ + } \ + } while (0) + +#define nfl_to_prog_data(nflocal, pcbk, fram) \ + do { \ + nflocal = fram->local; \ + fram->local = nflocal->proglocal; \ + pcbk = nflocal->progcbk; \ + } while (0) + +#define nfs_fop_handle_local_init(fram, nfx, nfloc, cbck, prgloc, retval, lab) \ + do { \ + prog_data_to_nfl(nfx, nfloc, fram, cbck, prgloc); \ + if (!nfloc) { \ + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, \ + "Failed to init local"); \ + retval = -ENOMEM; \ + goto lab; \ + } \ + } while (0) + +extern int +nfs_fop_fstat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_stat_cbk_t cbk, void *local); + +extern int +nfs_fop_readdirp(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *dirfd, + size_t bufsize, off_t offset, fop_readdir_cbk_t cbk, + void *local); +extern int +nfs_fop_lookup(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + fop_lookup_cbk_t cbk, void *local); + +extern int +nfs_fop_create(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int flags, mode_t mode, fd_t *fd, fop_create_cbk_t cbk, + void *local); +extern int +nfs_fop_flush(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_flush_cbk_t cbk, void *local); + +extern int +nfs_fop_mkdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, fop_mkdir_cbk_t cbk, void *local); + +extern int +nfs_fop_truncate(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + off_t offset, fop_truncate_cbk_t cbk, void *local); + +extern int +nfs_fop_read(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + size_t size, off_t offset, fop_readv_cbk_t cbk, void *local); + +extern int +nfs_fop_fsync(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + int32_t datasync, fop_fsync_cbk_t cbk, void *local); + +extern int +nfs_fop_write(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + struct iobref *srciobref, struct iovec *vector, int32_t count, + off_t offset, fop_writev_cbk_t cbk, void *local); + +extern int +nfs_fop_open(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + int32_t flags, fd_t *fd, fop_open_cbk_t cbk, void *local); + +extern int +nfs_fop_rename(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_rename_cbk_t cbk, void *local); + +extern int +nfs_fop_link(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_link_cbk_t cbk, void *local); + +extern int +nfs_fop_unlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_unlink_cbk_t cbk, void *local); + +extern int +nfs_fop_rmdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_rmdir_cbk_t cbk, void *local); + +extern int +nfs_fop_mknod(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, dev_t dev, fop_mknod_cbk_t cbk, void *local); + +extern int +nfs_fop_readlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + size_t size, fop_readlink_cbk_t cbk, void *local); + +extern int +nfs_fop_symlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, char *target, + loc_t *pathloc, fop_symlink_cbk_t cbk, void *local); + +extern int +nfs_fop_setattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + struct iatt *buf, int32_t valid, fop_setattr_cbk_t cbk, + void *local); + +extern int +nfs_fop_statfs(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_statfs_cbk_t cbk, void *local); + +extern int +nfs_fop_opendir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fd_t *dirfd, fop_opendir_cbk_t cbk, void *local); + +extern int +nfs_fop_stat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + fop_stat_cbk_t cbk, void *local); + +extern int +nfs_fop_access(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + int32_t accesstest, fop_access_cbk_t cbk, void *local); + +extern int +nfs_fop_lk(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, int cmd, + struct gf_flock *flock, fop_lk_cbk_t cbk, void *local); + +extern int +nfs_fop_getxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + char *name, dict_t *xdata, fop_getxattr_cbk_t cbk, + void *local); + +extern int +nfs_fop_setxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + dict_t *dict, int32_t flags, dict_t *xdata, + fop_setxattr_cbk_t cbk, void *local); + +#endif diff --git a/xlators/nfs/server/src/nfs-generics.c b/xlators/nfs/server/src/nfs-generics.c new file mode 100644 index 00000000000..009991877cb --- /dev/null +++ b/xlators/nfs/server/src/nfs-generics.c @@ -0,0 +1,312 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "string.h" + +#include "nfs.h" +#include "nfs-fops.h" +#include "nfs-inodes.h" +#include "nfs-generics.h" +#include <glusterfs/xlator.h> + +int +nfs_fstat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_stat_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!fd) || (!nfu)) + return ret; + + ret = nfs_fop_fstat(nfsx, xl, nfu, fd, cbk, local); + return ret; +} + +int +nfs_access(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int32_t accesstest, fop_access_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_fop_access(nfsx, xl, nfu, pathloc, accesstest, cbk, local); + + return ret; +} + +int +nfs_stat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_stat_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_fop_stat(nfsx, xl, nfu, pathloc, cbk, local); + + return ret; +} + +int +nfs_readdirp(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *dirfd, + size_t bufsize, off_t offset, fop_readdir_cbk_t cbk, void *local) +{ + if ((!nfsx) || (!xl) || (!dirfd) || (!nfu)) + return -EFAULT; + + return nfs_fop_readdirp(nfsx, xl, nfu, dirfd, bufsize, offset, cbk, local); +} + +int +nfs_lookup(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_lookup_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_fop_lookup(nfsx, xl, nfu, pathloc, cbk, local); + return ret; +} + +int +nfs_create(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int flags, mode_t mode, fop_create_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_inode_create(nfsx, xl, nfu, pathloc, flags, mode, cbk, local); + return ret; +} + +int +nfs_flush(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_flush_cbk_t cbk, void *local) +{ + return nfs_fop_flush(nfsx, xl, nfu, fd, cbk, local); +} + +int +nfs_mkdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, fop_mkdir_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_inode_mkdir(nfsx, xl, nfu, pathloc, mode, cbk, local); + return ret; +} + +int +nfs_truncate(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + off_t offset, fop_truncate_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_fop_truncate(nfsx, xl, nfu, pathloc, offset, cbk, local); + return ret; +} + +int +nfs_read(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, size_t size, + off_t offset, fop_readv_cbk_t cbk, void *local) +{ + return nfs_fop_read(nfsx, xl, nfu, fd, size, offset, cbk, local); +} + +int +nfs_lk(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, int cmd, + struct gf_flock *flock, fop_lk_cbk_t cbk, void *local) +{ + return nfs_fop_lk(nfsx, xl, nfu, fd, cmd, flock, cbk, local); +} + +int +nfs_getxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + char *name, dict_t *xdata, fop_getxattr_cbk_t cbk, void *local) +{ + return nfs_fop_getxattr(nfsx, xl, nfu, loc, name, xdata, cbk, local); +} + +int +nfs_setxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + dict_t *dict, int32_t flags, dict_t *xdata, fop_setxattr_cbk_t cbk, + void *local) +{ + return nfs_fop_setxattr(nfsx, xl, nfu, loc, dict, flags, xdata, cbk, local); +} + +int +nfs_fsync(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + int32_t datasync, fop_fsync_cbk_t cbk, void *local) +{ + return nfs_fop_fsync(nfsx, xl, nfu, fd, datasync, cbk, local); +} + +int +nfs_write(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + struct iobref *srciobref, struct iovec *vector, int32_t count, + off_t offset, fop_writev_cbk_t cbk, void *local) +{ + return nfs_fop_write(nfsx, xl, nfu, fd, srciobref, vector, count, offset, + cbk, local); +} + +int +nfs_open(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int32_t flags, fop_open_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_inode_open(nfsx, xl, nfu, pathloc, flags, cbk, local); + return ret; +} + +int +nfs_rename(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_rename_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!oldloc) || (!newloc) || (!nfu)) + return ret; + + ret = nfs_inode_rename(nfsx, xl, nfu, oldloc, newloc, cbk, local); + return ret; +} + +int +nfs_link(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_link_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!oldloc) || (!newloc) || (!nfu)) + return ret; + + ret = nfs_inode_link(nfsx, xl, nfu, oldloc, newloc, cbk, local); + return ret; +} + +int +nfs_unlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_unlink_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_inode_unlink(nfsx, xl, nfu, pathloc, cbk, local); + return ret; +} + +int +nfs_rmdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *path, + fop_rmdir_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!path) || (!nfu)) + return ret; + + ret = nfs_inode_rmdir(nfsx, xl, nfu, path, cbk, local); + return ret; +} + +int +nfs_mknod(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, dev_t dev, fop_mknod_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_inode_mknod(nfsx, xl, nfu, pathloc, mode, dev, cbk, local); + return ret; +} + +int +nfs_readlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *linkloc, + fop_readlink_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!linkloc) || (!nfu)) + return ret; + + ret = nfs_fop_readlink(nfsx, xl, nfu, linkloc, NFS_PATH_MAX, cbk, local); + return ret; +} + +int +nfs_symlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, char *target, + loc_t *linkloc, fop_symlink_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!linkloc) || (!target) || (!nfu)) + return ret; + + ret = nfs_inode_symlink(nfsx, xl, nfu, target, linkloc, cbk, local); + return ret; +} + +int +nfs_setattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + struct iatt *buf, int32_t valid, fop_setattr_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_fop_setattr(nfsx, xl, nfu, pathloc, buf, valid, cbk, local); + return ret; +} + +int +nfs_statfs(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_statfs_cbk_t cbk, void *local) +{ + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + ret = nfs_fop_statfs(nfsx, xl, nfu, pathloc, cbk, local); + return ret; +} + +int +nfs_opendir(xlator_t *nfsx, xlator_t *fopxl, nfs_user_t *nfu, loc_t *pathloc, + fop_opendir_cbk_t cbk, void *local) +{ + if ((!nfsx) || (!fopxl) || (!pathloc) || (!nfu)) + return -EFAULT; + + return nfs_inode_opendir(nfsx, fopxl, nfu, pathloc, cbk, local); +} diff --git a/xlators/nfs/server/src/nfs-generics.h b/xlators/nfs/server/src/nfs-generics.h new file mode 100644 index 00000000000..07a79994a4b --- /dev/null +++ b/xlators/nfs/server/src/nfs-generics.h @@ -0,0 +1,161 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS_GENERICS_H_ +#define _NFS_GENERICS_H_ + +#include "nfs.h" +#include <glusterfs/xlator.h> +#include "nfs-fops.h" +#include "nfs-inodes.h" + +struct nfs_direntcache { + gf_dirent_t entries; /* Head of list of cached dirents. */ + gf_dirent_t *next; /* Pointer to the next entry that + * should be sent by readdir */ + uint64_t prev_off; /* Offset where the next read will + * happen. + */ +}; + +/* WE're trying to abstract the fops interface from the NFS xlator so that + * different NFS versions can simply call a standard interface and have fop + * interface dependent functions be handled internally. + * This structure is part of such an abstraction. The fops layer stores any + * state is requires in the fd. E.g. the dirent cache for a directory fd_t. + */ +typedef struct nfs_fop_fdcontext { + pthread_mutex_t lock; + size_t dirent_bufsize; + off_t offset; + struct nfs_direntcache *dcache; + xlator_t *dirvol; +} nfs_fdctx_t; + +extern int +nfs_fstat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_stat_cbk_t cbk, void *local); + +extern int +nfs_readdirp(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *dirfd, + size_t bufsize, off_t offset, fop_readdir_cbk_t cbk, void *local); + +extern int +nfs_lookup(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_lookup_cbk_t cbk, void *local); + +extern int +nfs_create(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int flags, mode_t mode, fop_create_cbk_t cbk, void *local); + +extern int +nfs_flush(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + fop_flush_cbk_t cbk, void *local); + +extern int +nfs_mkdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, fop_mkdir_cbk_t cbk, void *local); + +extern int +nfs_truncate(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + off_t offset, fop_truncate_cbk_t cbk, void *local); + +extern int +nfs_read(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, size_t size, + off_t offset, fop_readv_cbk_t cbk, void *local); + +extern int +nfs_fsync(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + int32_t datasync, fop_fsync_cbk_t cbk, void *local); + +extern int +nfs_write(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, + struct iobref *srciobref, struct iovec *vector, int32_t count, + off_t offset, fop_writev_cbk_t cbk, void *local); + +extern int +nfs_open(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int32_t flags, fop_open_cbk_t cbk, void *local); + +extern int +nfs_rename(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_rename_cbk_t cbk, void *local); + +extern int +nfs_link(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_link_cbk_t cbk, void *local); + +extern int +nfs_unlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_unlink_cbk_t cbk, void *local); + +extern int +nfs_rmdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_rmdir_cbk_t cbk, void *local); + +extern int +nfs_mknod(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, dev_t dev, fop_mknod_cbk_t cbk, void *local); + +extern int +nfs_readlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *linkloc, + fop_readlink_cbk_t cbk, void *local); + +extern int +nfs_setattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + struct iatt *buf, int32_t valid, fop_setattr_cbk_t cbk, + void *local); + +extern int +nfs_statfs(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_statfs_cbk_t cbk, void *local); + +extern int +nfs_stat(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_stat_cbk_t cbk, void *local); + +extern int +nfs_symlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, char *target, + loc_t *linkloc, fop_symlink_cbk_t cbk, void *local); + +/* Synchronous equivalents */ + +extern call_stub_t * +nfs_open_sync(xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, int32_t flags); + +extern call_stub_t * +nfs_write_sync(xlator_t *xl, nfs_user_t *nfu, fd_t *fd, struct iobuf *srciob, + struct iovec *vec, int count, off_t offset); + +extern call_stub_t * +nfs_read_sync(xlator_t *xl, nfs_user_t *nfu, fd_t *fd, size_t size, + off_t offset); + +extern int +nfs_opendir(xlator_t *nfsx, xlator_t *fopxl, nfs_user_t *nfu, loc_t *pathloc, + fop_opendir_cbk_t cbk, void *local); + +extern int +nfs_access(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int32_t accesstest, fop_access_cbk_t cbk, void *local); +extern int +nfs_lk(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, fd_t *fd, int cmd, + struct gf_flock *flock, fop_lk_cbk_t cbk, void *local); + +extern int +nfs_getxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + char *name, dict_t *xdata, fop_getxattr_cbk_t cbk, void *local); + +extern int +nfs_setxattr(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + dict_t *dict, int32_t flags, dict_t *xdata, fop_setxattr_cbk_t cbk, + void *local); + +#endif diff --git a/xlators/nfs/server/src/nfs-inodes.c b/xlators/nfs/server/src/nfs-inodes.c new file mode 100644 index 00000000000..6f34ca705ba --- /dev/null +++ b/xlators/nfs/server/src/nfs-inodes.c @@ -0,0 +1,579 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "string.h" + +#include "nfs.h" +#include "nfs-inodes.h" +#include "nfs-fops.h" +#include <glusterfs/xlator.h> +#include "nfs-messages.h" + +#include <libgen.h> + +#define inodes_nfl_to_prog_data(nflocal, pcbk, fram) \ + do { \ + nflocal = fram->local; \ + fram->local = nflocal->proglocal; \ + *VOID(&pcbk) = nflocal->progcbk; \ + nfs_fop_local_wipe(nflocal->nfsx, nflocal); \ + } while (0) + +void +nfl_inodes_init(struct nfs_fop_local *nfl, inode_t *inode, inode_t *parent, + inode_t *newparent, const char *name, const char *newname) +{ + if (!nfl) + return; + + if (inode) + nfl->inode = inode_ref(inode); + + if (parent) + nfl->parent = inode_ref(parent); + + if (newparent) + nfl->newparent = inode_ref(newparent); + + if (name) + snprintf(nfl->path, NFS_NAME_MAX, "%s", name); + + if (newname) + snprintf(nfl->newpath, NFS_NAME_MAX, "%s", newname); + return; +} + +int32_t +nfs_inode_create_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = frame->local; + fop_create_cbk_t progcbk = NULL; + inode_t *linked_inode = NULL; + + if (op_ret == -1) + goto do_not_link; + + linked_inode = inode_link(inode, nfl->parent, nfl->path, buf); + +do_not_link: + /* NFS does not need it, upper layers should not expect the pointer to + * be a valid fd. + */ + fd_unref(fd); + + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, fd, inode, buf, + preparent, postparent, xdata); + + if (linked_inode) { + inode_lookup(linked_inode); + inode_unref(linked_inode); + } + + return 0; +} + +int +nfs_inode_create(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int flags, int mode, fop_create_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + fd_t *newfd = NULL; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + + newfd = fd_create(pathloc->inode, 0); + if (!newfd) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to create new fd"); + ret = -ENOMEM; + goto wipe_nfl; + } + + /* The parent and base name will be needed to link the new inode + * into the inode table. + */ + nfl_inodes_init(nfl, pathloc->inode, pathloc->parent, NULL, pathloc->name, + NULL); + ret = nfs_fop_create(nfsx, xl, nfu, pathloc, flags, mode, newfd, + nfs_inode_create_cbk, nfl); +wipe_nfl: + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + +err: + return ret; +} + +int32_t +nfs_inode_mkdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = frame->local; + fop_mkdir_cbk_t progcbk = NULL; + inode_t *linked_inode = NULL; + + if (op_ret == -1) + goto do_not_link; + + linked_inode = inode_link(inode, nfl->parent, nfl->path, buf); + +do_not_link: + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + + if (linked_inode) { + inode_lookup(linked_inode); + inode_unref(linked_inode); + } + + return 0; +} + +int +nfs_inode_mkdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int mode, fop_mkdir_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + nfl_inodes_init(nfl, pathloc->inode, pathloc->parent, NULL, pathloc->name, + NULL); + ret = nfs_fop_mkdir(nfsx, xl, nfu, pathloc, mode, nfs_inode_mkdir_cbk, nfl); + if (ret < 0) + nfs_fop_local_wipe(nfsx, nfl); + +err: + return ret; +} + +int32_t +nfs_inode_open_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_open_cbk_t progcbk = NULL; + + if ((op_ret == -1) && (fd)) + fd_unref(fd); + /* Not needed here since the fd is cached in higher layers and the bind + * must happen atomically when the fd gets added to the fd LRU. + */ + /* else + fd_bind (fd); + */ + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, fd, xdata); + return 0; +} + +int +nfs_inode_open(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + int32_t flags, fop_open_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + fd_t *newfd = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!loc) || (!nfu)) + return ret; + + newfd = fd_create(loc->inode, 0); + if (!newfd) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to create fd"); + ret = -ENOMEM; + goto err; + } + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, fd_err); + ret = nfs_fop_open(nfsx, xl, nfu, loc, flags, newfd, nfs_inode_open_cbk, + nfl); + + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + +fd_err: + if (ret < 0) + if (newfd) + fd_unref(newfd); + +err: + + return ret; +} + +int32_t +nfs_inode_rename_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + struct iatt *preoldparent, struct iatt *postoldparent, + struct iatt *prenewparent, struct iatt *postnewparent, + dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_rename_cbk_t progcbk = NULL; + + nfl = frame->local; + if (op_ret == -1) + goto do_not_link; + + inode_rename(this->itable, nfl->parent, nfl->path, nfl->newparent, + nfl->newpath, nfl->inode, buf); + +do_not_link: + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, buf, preoldparent, + postoldparent, prenewparent, postnewparent, xdata); + return 0; +} + +int +nfs_inode_rename(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_rename_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!oldloc) || (!newloc)) + return ret; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + nfl_inodes_init(nfl, oldloc->inode, oldloc->parent, newloc->parent, + oldloc->name, newloc->name); + ret = nfs_fop_rename(nfsx, xl, nfu, oldloc, newloc, nfs_inode_rename_cbk, + nfl); + +err: + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + + return ret; +} + +int32_t +nfs_inode_link_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_link_cbk_t progcbk = NULL; + inode_t *linked_inode = NULL; + + if (op_ret == -1) + goto do_not_link; + + nfl = frame->local; + linked_inode = inode_link(inode, nfl->newparent, nfl->path, buf); + +do_not_link: + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + + if (linked_inode) { + inode_lookup(linked_inode); + inode_unref(linked_inode); + } + + return 0; +} + +int +nfs_inode_link(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_link_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!oldloc) || (!newloc) || (!nfu)) + return -EFAULT; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + nfl_inodes_init(nfl, NULL, NULL, newloc->parent, newloc->name, NULL); + ret = nfs_fop_link(nfsx, xl, nfu, oldloc, newloc, nfs_inode_link_cbk, nfl); + +err: + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + + return ret; +} + +int32_t +nfs_inode_unlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_unlink_cbk_t progcbk = NULL; + + nfl = frame->local; + + if (op_ret == -1) + goto do_not_unlink; + + inode_unlink(nfl->inode, nfl->parent, nfl->path); + inode_forget(nfl->inode, 0); + +do_not_unlink: + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, preparent, postparent, + xdata); + return 0; +} + +int +nfs_inode_unlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_unlink_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return -EFAULT; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + nfl_inodes_init(nfl, pathloc->inode, pathloc->parent, NULL, pathloc->name, + NULL); + ret = nfs_fop_unlink(nfsx, xl, nfu, pathloc, nfs_inode_unlink_cbk, nfl); + +err: + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + + return ret; +} + +int32_t +nfs_inode_rmdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_rmdir_cbk_t progcbk = NULL; + + nfl = frame->local; + + if (op_ret == -1) + goto do_not_unlink; + + inode_unlink(nfl->inode, nfl->parent, nfl->path); + inode_forget(nfl->inode, 0); + +do_not_unlink: + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, preparent, postparent, + xdata); + + return 0; +} + +int +nfs_inode_rmdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_rmdir_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + nfl_inodes_init(nfl, pathloc->inode, pathloc->parent, NULL, pathloc->name, + NULL); + + ret = nfs_fop_rmdir(nfsx, xl, nfu, pathloc, nfs_inode_rmdir_cbk, nfl); + +err: + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + return ret; +} + +int32_t +nfs_inode_mknod_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_mknod_cbk_t progcbk = NULL; + inode_t *linked_inode = NULL; + + nfl = frame->local; + + if (op_ret == -1) + goto do_not_link; + + linked_inode = inode_link(inode, nfl->parent, nfl->path, buf); + +do_not_link: + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + + if (linked_inode) { + inode_lookup(linked_inode); + inode_unref(linked_inode); + } + + return 0; +} + +int +nfs_inode_mknod(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, dev_t dev, fop_mknod_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!pathloc) || (!nfu)) + return ret; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + nfl_inodes_init(nfl, pathloc->inode, pathloc->parent, NULL, pathloc->name, + NULL); + + ret = nfs_fop_mknod(nfsx, xl, nfu, pathloc, mode, dev, nfs_inode_mknod_cbk, + nfl); + +err: + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + + return ret; +} + +int32_t +nfs_inode_symlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_symlink_cbk_t progcbk = NULL; + inode_t *linked_inode = NULL; + + nfl = frame->local; + if (op_ret == -1) + goto do_not_link; + + linked_inode = inode_link(inode, nfl->parent, nfl->path, buf); + +do_not_link: + inodes_nfl_to_prog_data(nfl, progcbk, frame); + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + + if (linked_inode) { + inode_lookup(linked_inode); + inode_unref(linked_inode); + } + + return 0; +} + +int +nfs_inode_symlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, char *target, + loc_t *pathloc, fop_symlink_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!target) || (!pathloc) || (!nfu)) + return ret; + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + nfl_inodes_init(nfl, pathloc->inode, pathloc->parent, NULL, pathloc->name, + NULL); + ret = nfs_fop_symlink(nfsx, xl, nfu, target, pathloc, nfs_inode_symlink_cbk, + nfl); + +err: + if (ret < 0) + nfs_fop_local_wipe(xl, nfl); + + return ret; +} + +int32_t +nfs_inode_opendir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, dict_t *xdata) +{ + struct nfs_fop_local *nfl = NULL; + fop_open_cbk_t progcbk = NULL; + + if (op_ret != -1) + fd_bind(fd); + + inodes_nfl_to_prog_data(nfl, progcbk, frame); + + if (progcbk) + progcbk(frame, cookie, this, op_ret, op_errno, fd, xdata); + + return 0; +} + +int +nfs_inode_opendir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + fop_opendir_cbk_t cbk, void *local) +{ + struct nfs_fop_local *nfl = NULL; + fd_t *newfd = NULL; + int ret = -EFAULT; + + if ((!nfsx) || (!xl) || (!loc) || (!nfu)) + return ret; + + newfd = fd_create(loc->inode, 0); + if (!newfd) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to create fd"); + ret = -ENOMEM; + goto err; + } + + nfs_fop_handle_local_init(NULL, nfsx, nfl, cbk, local, ret, err); + ret = nfs_fop_opendir(nfsx, xl, nfu, loc, newfd, nfs_inode_opendir_cbk, + nfl); + +err: + if (ret < 0) { + if (newfd) + fd_unref(newfd); + nfs_fop_local_wipe(xl, nfl); + } + + return ret; +} diff --git a/xlators/nfs/server/src/nfs-inodes.h b/xlators/nfs/server/src/nfs-inodes.h new file mode 100644 index 00000000000..e8efd1e127c --- /dev/null +++ b/xlators/nfs/server/src/nfs-inodes.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS_INODES_H_ +#define _NFS_INODES_H_ + +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include <glusterfs/iobuf.h> +#include <glusterfs/call-stub.h> +#include "nfs-fops.h" + +extern int +nfs_link_inode(inode_t *newi, inode_t *parent, char *name, + struct iatt *newstat); + +extern int +nfs_inode_create(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int flags, int mode, fop_create_cbk_t cbk, void *local); + +extern int +nfs_inode_mkdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + int mode, fop_mkdir_cbk_t cbk, void *local); + +extern int +nfs_inode_open(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + int32_t flags, fop_open_cbk_t cbk, void *local); + +extern int +nfs_inode_rename(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_rename_cbk_t cbk, void *local); + +extern int +nfs_inode_link(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *oldloc, + loc_t *newloc, fop_link_cbk_t cbk, void *local); + +extern int +nfs_inode_unlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_unlink_cbk_t cbk, void *local); + +extern int +nfs_inode_rmdir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_rmdir_cbk_t cbk, void *local); + +extern int +nfs_inode_symlink(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, char *target, + loc_t *pathloc, fop_symlink_cbk_t cbk, void *local); + +extern int +nfs_inode_opendir(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *loc, + fop_opendir_cbk_t cbk, void *local); + +extern int +nfs_inode_mknod(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + mode_t mode, dev_t dev, fop_mknod_cbk_t cbk, void *local); + +extern int +nfs_inode_lookup(xlator_t *nfsx, xlator_t *xl, nfs_user_t *nfu, loc_t *pathloc, + fop_lookup_cbk_t cbk, void *local); +#endif diff --git a/xlators/nfs/server/src/nfs-mem-types.h b/xlators/nfs/server/src/nfs-mem-types.h new file mode 100644 index 00000000000..0a3c887eff1 --- /dev/null +++ b/xlators/nfs/server/src/nfs-mem-types.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2008-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef __NFS_MEM_TYPES_H__ +#define __NFS_MEM_TYPES_H__ + +#include <glusterfs/mem-types.h> + +enum gf_nfs_mem_types_ { + gf_nfs_mt_mountentry = gf_common_mt_end + 1, + gf_nfs_mt_mountbody, + gf_nfs_mt_nfs_state, + gf_nfs_mt_char, + gf_nfs_mt_exportnode, + gf_nfs_mt_groupnode, + gf_nfs_mt_mount3_state, + gf_nfs_mt_nfs3_export, + gf_nfs_mt_nfs3_state, + gf_nfs_mt_entry3, + gf_nfs_mt_entryp3, + gf_nfs_mt_nfs3_fh, + gf_nfs_mt_nfs_initer_list, + gf_nfs_mt_xlator_t, + gf_nfs_mt_mnt3_resolve, + gf_nfs_mt_mnt3_export, + gf_nfs_mt_mnt3_auth_params, + gf_nfs_mt_int, + gf_nfs_mt_mountres3, + gf_nfs_mt_mountstat3, + gf_nfs_mt_nlm4_fde, + gf_nfs_mt_nlm4_nlmclnt, + gf_nfs_mt_nlm4_share, + gf_nfs_mt_inode_ctx, + gf_nfs_mt_auth_spec, + gf_nfs_mt_arr, + gf_nfs_mt_auth_cache, + gf_nfs_mt_auth_cache_entry, + gf_nfs_mt_nlm4_notify, + gf_nfs_mt_end +}; +#endif diff --git a/xlators/nfs/server/src/nfs-messages.h b/xlators/nfs/server/src/nfs-messages.h new file mode 100644 index 00000000000..04e15cbe78c --- /dev/null +++ b/xlators/nfs/server/src/nfs-messages.h @@ -0,0 +1,102 @@ +/*Copyright (c) 2015 Red Hat, Inc. <http://www.redhat.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS_MESSAGES_H_ +#define _NFS_MESSAGES_H_ + +#include <glusterfs/glfs-message-id.h> + +/* To add new message IDs, append new identifiers at the end of the list. + * + * Never remove a message ID. If it's not used anymore, you can rename it or + * leave it as it is, but not delete it. This is to prevent reutilization of + * IDs by other messages. + * + * The component name must match one of the entries defined in + * glfs-message-id.h. + */ + +GLFS_MSGID( + NFS, NFS_MSG_UNUSED_1, NFS_MSG_UNUSED_2, NFS_MSG_INVALID_ENTRY, + NFS_MSG_INODE_LOC_FILL_ERROR, NFS_MSG_HARD_RESOLVE_FAIL, + NFS_MSG_ARGS_DECODE_ERROR, NFS_MSG_LOOKUP_PROC_FAIL, NFS_MSG_UNUSED_8, + NFS_MSG_UNUSED_9, NFS_MSG_READLINK_PROC_FAIL, NFS_MSG_UNUSED_11, + NFS_MSG_ANONYMOUS_FD_FAIL, NFS_MSG_READ_FAIL, NFS_MSG_UNUSED_14, + NFS_MSG_UNUSED_15, NFS_MSG_STATE_WRONG, NFS_MSG_WRITE_FAIL, + NFS_MSG_UNUSED_18, NFS_MSG_UNUSED_19, NFS_MSG_UNUSED_20, + NFS_MSG_CREATE_FAIL, NFS_MSG_UNUSED_22, NFS_MSG_UNUSED_23, + NFS_MSG_DIR_OP_FAIL, NFS_MSG_UNUSED_25, NFS_MSG_SYMLINK_FAIL, + NFS_MSG_UNUSED_27, NFS_MSG_MKNOD_FAIL, NFS_MSG_OPT_INIT_FAIL, + NFS_MSG_UNUSED_30, NFS_MSG_REMOVE_FAIL, NFS_MSG_RMDIR_CBK, + NFS_MSG_UNUSED_33, NFS_MSG_RENAME_FAIL, NFS_MSG_UNUSED_35, + NFS_MSG_LINK_FAIL, NFS_MSG_UNUSED_37, NFS_MSG_UNUSED_38, + NFS_MSG_READDIR_FAIL, NFS_MSG_READDIRP_FAIL, NFS_MSG_UNUSED_41, + NFS_MSG_UNUSED_42, NFS_MSG_FSTAT_FAIL, NFS_MSG_UNUSED_44, + NFS_MSG_FSINFO_FAIL, NFS_MSG_UNUSED_46, NFS_MSG_PATHCONF_FAIL, + NFS_MSG_UNUSED_48, NFS_MSG_COMMIT_FAIL, NFS_MSG_PROT_INIT_ADD_FAIL, + NFS_MSG_FORMAT_FAIL, NFS_MSG_SNPRINTF_FAIL, NFS_MSG_VOLID_MISSING, + NFS_MSG_PARSE_VOL_UUID_FAIL, NFS_MSG_STR2BOOL_FAIL, + NFS_MSG_SUBVOL_INIT_FAIL, NFS_MSG_NO_MEMORY, NFS_MSG_LISTENERS_CREATE_FAIL, + NFS_MSG_STATE_INIT_FAIL, NFS_MSG_RECONF_FAIL, NFS_MSG_RECONF_SUBVOL_FAIL, + NFS_MSG_STR_TOO_LONG, NFS_MSG_STATE_MISSING, NFS_MSG_INDEX_NOT_FOUND, + NFS_MSG_EXPORT_ID_FAIL, NFS_MSG_NO_RW_ACCESS, NFS_MSG_BAD_HANDLE, + NFS_MSG_RESOLVE_FH_FAIL, NFS_MSG_RESOLVE_STAT, NFS_MSG_VOL_DISABLE, + NFS_MSG_INIT_CALL_STAT_FAIL, NFS_MSG_ENCODE_FAIL, + NFS_MSG_SERIALIZE_REPLY_FAIL, NFS_MSG_SUBMIT_REPLY_FAIL, NFS_MSG_UNUSED_75, + NFS_MSG_UNUSED_76, NFS_MSG_STAT_FOP_FAIL, NFS_MSG_GETATTR_FAIL, + NFS_MSG_UNUSED_79, NFS_MSG_UNUSED_80, NFS_MSG_TIMESTAMP_NO_SYNC, + NFS_MSG_SETATTR_INVALID, NFS_MSG_SETATTR_FAIL, NFS_MSG_UNUSED_84, + NFS_MSG_ACCESS_PROC_FAIL, NFS_MSG_PGM_NOT_FOUND, NFS_MSG_PGM_INIT_FAIL, + NFS_MSG_PGM_REG_FAIL, NFS_MSG_LOOKUP_ROOT_FAIL, NFS_MSG_ROOT_LOC_INIT_FAIL, + NFS_MSG_STARTUP_FAIL, NFS_MSG_XLATOR_INIT_FAIL, NFS_MSG_NFS_MAN_DISABLE, + NFS_MSG_DICT_GET_FAILED, NFS_MSG_PARSE_FAIL, NFS_MSG_NLM_MAN_DISABLE, + NFS_MSG_ACL_MAN_DISABLE, NFS_MSG_DICT_SET_FAILED, + NFS_MSG_INIT_GRP_CACHE_FAIL, NFS_MSG_NO_PERM, NFS_MSG_REG_FILE_ERROR, + NFS_MSG_RPC_INIT_FAIL, NFS_MSG_RPC_CONFIG_FAIL, NFS_MSG_RECONFIG_PATH, + NFS_MSG_RECONFIG_VALUE, NFS_MSG_RECONFIG_VOL, NFS_MSG_NLM_INFO, + NFS_MSG_ACL_INFO, NFS_MSG_INIT_FAIL, NFS_MSG_STARTED, NFS_MSG_VOL_NOT_FOUND, + NFS_MSG_RECONFIG_ENABLE, NFS_MSG_RECONFIG_FAIL, NFS_MSG_MNT_STATE_NOT_FOUND, + NFS_MSG_ENCODE_MSG_FAIL, NFS_MSG_REP_SUBMIT_FAIL, NFS_MSG_READ_LOCKED, + NFS_MSG_MODIFY_LOCKED, NFS_MSG_RWTAB_OVERWRITE_FAIL, NFS_MSG_UPDATE_FAIL, + NFS_MSG_OPEN_FAIL, NFS_MSG_LOCK_FAIL, NFS_MSG_REWRITE_ERROR, + NFS_MSG_HASH_PATH_FAIL, NFS_MSG_LOOKUP_MNT_ERROR, + NFS_MSG_GET_ROOT_INODE_FAIL, NFS_MSG_RESOLVE_INODE_FAIL, + NFS_MSG_RESOLVE_SUBDIR_FAIL, NFS_MSG_RESOLVE_SYMLINK_ERROR, + NFS_MSG_RESOLVE_ERROR, NFS_MSG_UNSUPPORTED_VERSION, + NFS_MSG_AUTH_VERIFY_FAILED, NFS_MSG_PEER_NOT_ALLOWED, + NFS_MSG_GET_PEER_ADDR_FAIL, NFS_MSG_BAD_PEER, NFS_MSG_PEER_TOO_LONG, + NFS_MSG_CALLER_NOT_FOUND, NFS_MSG_GET_REMOTE_NAME_FAIL, + NFS_MSG_UNKNOWN_MNT_TYPE, NFS_MSG_PARSE_HOSTSPEC_FAIL, + NFS_MSG_PARSE_AUTH_PARAM_FAIL, NFS_MSG_SET_EXP_FAIL, + NFS_MSG_INIT_DIR_EXP_FAIL, NFS_MSG_DIR_EXP_SETUP_FAIL, + NFS_MSG_VOL_INIT_FAIL, NFS_MSG_AUTH_ERROR, NFS_MSG_UPDATING_EXP, + NFS_MSG_SET_EXP_AUTH_PARAM_FAIL, NFS_MSG_UPDATING_NET_GRP, + NFS_MSG_SET_NET_GRP_FAIL, NFS_MSG_PURGING_AUTH_CACHE, + NFS_MSG_MNT_STATE_INIT_FAIL, NFS_MSG_EXP_AUTH_DISABLED, + NFS_MSG_FH_TO_VOL_FAIL, NFS_MSG_INODE_SHARES_NOT_FOUND, + NFS_MSG_VOLUME_ERROR, NFS_MSG_GET_USER_ACL_FAIL, NFS_MSG_GET_DEF_ACL_FAIL, + NFS_MSG_SET_USER_ACL_FAIL, NFS_MSG_SET_DEF_ACL_FAIL, NFS_MSG_ACL_INIT_FAIL, + NFS_MSG_LOAD_PARSE_ERROR, NFS_MSG_CLNT_CALL_ERROR, + NFS_MSG_CLNT_CREATE_ERROR, NFS_MSG_NLM_GRACE_PERIOD, NFS_MSG_RPC_CLNT_ERROR, + NFS_MSG_GET_PORT_ERROR, NFS_MSG_NLMCLNT_NOT_FOUND, NFS_MSG_FD_LOOKUP_NULL, + NFS_MSG_SM_NOTIFY, NFS_MSG_NLM_INIT_FAIL, NFS_MSG_START_ERROR, + NFS_MSG_UNLINK_ERROR, NFS_MSG_SHARE_LIST_STORE_FAIL, + NFS_MSG_CLIENT_NOT_FOUND, NFS_MSG_SHARE_CALL_FAIL, + NFS_MSG_UNSHARE_CALL_FAIL, NFS_MSG_GET_PID_FAIL, NFS_MSG_ARG_FREE_FAIL, + NFS_MSG_PMAP_UNSET_FAIL, NFS_MSG_UDP_SERV_FAIL, NFS_MSG_REG_NLMCBK_FAIL, + NFS_MSG_TCP_SERV_FAIL, NFS_MSG_SVC_RUN_RETURNED, NFS_MSG_XLATOR_SET_FAIL, + NFS_MSG_SVC_ERROR, NFS_MSG_GET_FH_FAIL, NFS_MSG_FIND_FIRST_MATCH_FAIL, + NFS_MSG_NETGRP_NOT_FOUND, NFS_MSG_FILE_OP_FAILED, NFS_MSG_PATH_RESOLVE_FAIL, + NFS_MSG_LOC_FILL_RESOLVE_FAIL, NFS_MSG_INODE_NOT_FOUND, + NFS_MSG_INODE_CTX_STORE_FAIL, NFS_MSG_GETPWUID_FAIL, + NFS_MSG_MAP_GRP_LIST_FAIL, NFS_MSG_PARSE_DIR_FAIL, NFS_MSG_LOOKUP_FAIL, + NFS_MSG_STAT_ERROR, NFS_MSG_GFID_DICT_CREATE_FAIL, NFS_MSG_HASH_XLATOR_FAIL, + NFS_MSG_ENABLE_THROTTLE_FAIL); + +#endif /* _NFS_MESSAGES_H_ */ diff --git a/xlators/nfs/server/src/nfs.c b/xlators/nfs/server/src/nfs.c new file mode 100644 index 00000000000..39b73f88ac3 --- /dev/null +++ b/xlators/nfs/server/src/nfs.c @@ -0,0 +1,2073 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +/* This is the primary translator source for NFS. + * Every other protocol version gets initialized from here. + */ + +#include <glusterfs/defaults.h> +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include "nfs.h" +#include <glusterfs/mem-pool.h> +#include <glusterfs/logging.h> +#include "nfs-fops.h" +#include "mount3.h" +#include "nfs3.h" +#include "nfs-mem-types.h" +#include "nfs3-helpers.h" +#include "nlm4.h" +#include <glusterfs/options.h> +#include "acl3.h" +#include "rpc-drc.h" +#include <glusterfs/syscall.h> +#include "rpcsvc.h" +#include "nfs-messages.h" +#include "glusterfs/statedump.h" + +#define OPT_SERVER_AUX_GIDS "nfs.server-aux-gids" +#define OPT_SERVER_GID_CACHE_TIMEOUT "nfs.server.aux-gid-timeout" +#define OPT_SERVER_RPC_STATD "nfs.rpc-statd" +#define OPT_SERVER_RPC_STATD_PIDFILE "nfs.rpc-statd-pidfile" +#define OPT_SERVER_RPC_STATD_NOTIFY_PIDFILE "nfs.rpc-statd-notify-pidfile" + +#define NFS_DATADIR GLUSTERD_DEFAULT_WORKDIR "/nfs" + +/* Forward declaration */ +static int +nfs_add_initer(struct list_head *list, nfs_version_initer_t init, + gf_boolean_t required); + +static int +nfs_init_version(xlator_t *this, nfs_version_initer_t init, + gf_boolean_t required) +{ + int ret = -1; + struct nfs_initer_list *version = NULL; + struct nfs_initer_list *tmp = NULL; + rpcsvc_program_t *prog = NULL; + struct list_head *versions = NULL; + struct nfs_state *nfs = NULL; + gf_boolean_t found = _gf_false; + + if ((!this) || (!this->private) || (!init)) + return (-1); + + nfs = (struct nfs_state *)this->private; + + ret = nfs_add_initer(&nfs->versions, init, required); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PROT_INIT_ADD_FAIL, + "Failed to add protocol initializer"); + goto err; + } + + versions = &nfs->versions; + list_for_each_entry_safe(version, tmp, versions, list) + { + prog = version->program; + if (version->init == init) { + prog = init(this); + if (!prog) { + ret = -1; + goto err; + } + version->program = prog; + found = _gf_true; + break; + } + } + + /* program not added */ + if (!found) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PGM_NOT_FOUND, + "Program: %s NOT found", prog->progname); + goto err; + } + + /* Check if nfs.port is configured */ + if (nfs->override_portnum) + prog->progport = nfs->override_portnum; + + gf_msg_debug(GF_NFS, 0, "Starting program: %s", prog->progname); + + ret = rpcsvc_program_register(nfs->rpcsvc, prog, _gf_false); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PGM_INIT_FAIL, + "Program: %s init failed", prog->progname); + goto err; + } + + /* Registration with portmapper is disabled, Nothing to do */ + if (!nfs->register_portmap) + goto err; + + ret = rpcsvc_program_register_portmap(prog, prog->progport); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PGM_REG_FAIL, + "Program %s registration failed", prog->progname); + goto err; + } + ret = 0; /* All well */ +err: + return ret; +} + +static int +nfs_deinit_version(struct nfs_state *nfs, nfs_version_initer_t init) +{ + int ret = -1; + struct nfs_initer_list *version = NULL; + struct nfs_initer_list *tmp = NULL; + rpcsvc_program_t *prog = NULL; + struct list_head *versions = NULL; + + if ((!nfs) || (!init)) + return (-1); + + versions = &nfs->versions; + list_for_each_entry_safe(version, tmp, versions, list) + { + prog = version->program; + if (version->init == init) { + prog = version->program; + ret = rpcsvc_program_unregister(nfs->rpcsvc, prog); + if (ret != 0) + return (-1); + list_del(&version->list); + GF_FREE(version); + return (0); + } + } + + return (-1); +} + +static int +nfs_reconfigure_acl3(xlator_t *this) +{ + struct nfs_state *nfs = NULL; + + if ((!this) || (!this->private)) + return (-1); + + nfs = (struct nfs_state *)this->private; + + /* ACL is enabled */ + if (nfs->enable_acl) + return nfs_init_version(this, acl3svc_init, _gf_false); + + /* ACL is disabled */ + return nfs_deinit_version(nfs, acl3svc_init); +} + +static int +nfs_reconfigure_nlm4(xlator_t *this) +{ + struct nfs_state *nfs = NULL; + + if ((!this) || (!this->private)) + return (-1); + + nfs = (struct nfs_state *)this->private; + + /* NLM is enabled */ + if (nfs->enable_nlm) + return nfs_init_version(this, nlm4svc_init, _gf_false); + + /* NLM is disabled */ + return nfs_deinit_version(nfs, nlm4svc_init); +} + +static int +nfs_program_register_portmap_all(struct nfs_state *nfs) +{ + struct list_head *versions = NULL; + struct nfs_initer_list *version = NULL; + struct nfs_initer_list *tmp = NULL; + rpcsvc_program_t *prog = NULL; + + if (nfs == NULL) + return (-1); + + versions = &nfs->versions; + list_for_each_entry_safe(version, tmp, versions, list) + { + prog = version->program; + if (prog == NULL) + continue; + if (nfs->override_portnum) + prog->progport = nfs->override_portnum; + (void)rpcsvc_program_register_portmap(prog, prog->progport); +#ifdef IPV6_DEFAULT + (void)rpcsvc_program_register_rpcbind6(prog, prog->progport); +#endif + } + + return (0); +} + +static int +nfs_program_unregister_portmap_all(struct nfs_state *nfs) +{ + struct list_head *versions = NULL; + struct nfs_initer_list *version = NULL; + struct nfs_initer_list *tmp = NULL; + rpcsvc_program_t *prog = NULL; + + if (nfs == NULL) + return (-1); + + versions = &nfs->versions; + list_for_each_entry_safe(version, tmp, versions, list) + { + prog = version->program; + if (prog == NULL) + continue; + (void)rpcsvc_program_unregister_portmap(prog); +#ifdef IPV6_DEFAULT + (void)rpcsvc_program_unregister_rpcbind6(prog); +#endif + } + + return (0); +} + +/* Every NFS version must call this function with the init function + * for its particular version. + */ +static int +nfs_add_initer(struct list_head *list, nfs_version_initer_t init, + gf_boolean_t required) +{ + struct nfs_initer_list *new = NULL; + if ((!list) || (!init)) + return -1; + + new = GF_CALLOC(1, sizeof(*new), gf_nfs_mt_nfs_initer_list); + if (!new) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + return -1; + } + + new->init = init; + new->required = required; + list_add_tail(&new->list, list); + return 0; +} + +int +nfs_deinit_versions(struct list_head *versions, xlator_t *this) +{ + struct nfs_initer_list *version = NULL; + struct nfs_initer_list *tmp = NULL; + struct nfs_state *nfs = NULL; + + if ((!versions) || (!this)) + return -1; + + nfs = (struct nfs_state *)this->private; + list_for_each_entry_safe(version, tmp, versions, list) + { + /* TODO: Add version specific destructor. + * if (!version->deinit) + goto err; + + version->deinit (this); + */ + if (version->program) + rpcsvc_program_unregister(nfs->rpcsvc, (version->program)); + + list_del(&version->list); + GF_FREE(version); + } + + return 0; +} + +int +nfs_init_versions(struct nfs_state *nfs, xlator_t *this) +{ + struct nfs_initer_list *version = NULL; + struct nfs_initer_list *tmp = NULL; + rpcsvc_program_t *prog = NULL; + int ret = -1; + struct list_head *versions = NULL; + + if ((!nfs) || (!this)) + return -1; + + gf_msg_debug(GF_NFS, 0, "Initing protocol versions"); + versions = &nfs->versions; + list_for_each_entry_safe(version, tmp, versions, list) + { + if (!version->init) { + ret = -1; + goto err; + } + + prog = version->init(this); + if (!prog) { + ret = -1; + goto err; + } + + version->program = prog; + if (nfs->override_portnum) + prog->progport = nfs->override_portnum; + gf_msg_debug(GF_NFS, 0, "Starting program: %s", prog->progname); + + ret = rpcsvc_program_register(nfs->rpcsvc, prog, _gf_false); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PGM_INIT_FAIL, + "Program: %s init failed", prog->progname); + goto err; + } + if (nfs->register_portmap) { + ret = rpcsvc_program_register_portmap(prog, prog->progport); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PGM_REG_FAIL, + "%s program %s registration failed", + version->required ? "Required" : "Optional", + prog->progname); + + /* fatal error if the program is required */ + if (version->required) + goto err; + } +#ifdef IPV6_DEFAULT + ret = rpcsvc_program_register_rpcbind6(prog, prog->progport); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PGM_REG_FAIL, + "Program (ipv6) %s registration failed", prog->progname); + goto err; + } +#endif + } + } + + ret = 0; +err: + return ret; +} + +int +nfs_add_all_initiators(struct nfs_state *nfs) +{ + int ret = 0; + + /* Add the initializers for all versions. */ + ret = nfs_add_initer(&nfs->versions, mnt3svc_init, _gf_true); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PROT_INIT_ADD_FAIL, + "Failed to add MOUNT3 protocol initializer"); + goto ret; + } + + ret = nfs_add_initer(&nfs->versions, mnt1svc_init, _gf_true); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PROT_INIT_ADD_FAIL, + "Failed to add MOUNT1 protocol initializer"); + goto ret; + } + + ret = nfs_add_initer(&nfs->versions, nfs3svc_init, _gf_true); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PROT_INIT_ADD_FAIL, + "Failed to add NFS3 protocol initializer"); + goto ret; + } + + if (nfs->enable_nlm == _gf_true) { + ret = nfs_add_initer(&nfs->versions, nlm4svc_init, _gf_false); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PROT_INIT_ADD_FAIL, + "Failed to add protocol initializer"); + goto ret; + } + } + + if (nfs->enable_acl == _gf_true) { + ret = nfs_add_initer(&nfs->versions, acl3svc_init, _gf_false); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_PROT_INIT_ADD_FAIL, + "Failed to add ACL protocol initializer"); + goto ret; + } + } + + ret = 0; +ret: + return ret; +} + +int +nfs_subvolume_started(struct nfs_state *nfs, xlator_t *xl) +{ + int x = 0; + int started = 0; + + if ((!nfs) || (!xl)) + return 1; + + LOCK(&nfs->svinitlock); + { + for (; x < nfs->allsubvols; ++x) { + if (nfs->initedxl[x] == xl) { + started = 1; + goto unlock; + } + } + } +unlock: + UNLOCK(&nfs->svinitlock); + + return started; +} + +int +nfs_subvolume_set_started(struct nfs_state *nfs, xlator_t *xl) +{ + int x = 0; + + if ((!nfs) || (!xl)) + return 1; + + LOCK(&nfs->svinitlock); + { + for (; x < nfs->allsubvols; ++x) { + if (nfs->initedxl[x] == xl) { + gf_msg_debug(GF_NFS, 0, "Volume already started %s", xl->name); + break; + } + + if (nfs->initedxl[x] == NULL) { + nfs->initedxl[x] = xl; + ++nfs->upsubvols; + gf_msg_debug(GF_NFS, 0, + "Starting up: %s " + ", vols started till now: %d", + xl->name, nfs->upsubvols); + goto unlock; + } + } + } +unlock: + UNLOCK(&nfs->svinitlock); + + return 0; +} + +int32_t +nfs_start_subvol_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) +{ + if (op_ret == -1) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, op_errno, NFS_MSG_LOOKUP_ROOT_FAIL, + "Failed to lookup root: %s", strerror(op_errno)); + goto err; + } + + nfs_subvolume_set_started(this->private, ((xlator_t *)cookie)); + gf_msg_trace(GF_NFS, 0, "Started %s", ((xlator_t *)cookie)->name); +err: + return 0; +} + +int +nfs_startup_subvolume(xlator_t *nfsx, xlator_t *xl) +{ + int ret = -1; + loc_t rootloc = { + 0, + }; + nfs_user_t nfu = { + 0, + }; + + if ((!nfsx) || (!xl)) + return -1; + + if (nfs_subvolume_started(nfsx->private, xl)) { + gf_msg_trace(GF_NFS, 0, "Subvolume already started: %s", xl->name); + ret = 0; + goto err; + } + + ret = nfs_root_loc_fill(xl->itable, &rootloc); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, 0, NFS_MSG_ROOT_LOC_INIT_FAIL, + "Failed to init root loc"); + goto err; + } + + nfs_user_root_create(&nfu); + ret = nfs_fop_lookup(nfsx, xl, &nfu, &rootloc, nfs_start_subvol_lookup_cbk, + (void *)nfsx->private); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, -ret, NFS_MSG_LOOKUP_ROOT_FAIL, + "Failed to lookup root: %s", strerror(-ret)); + goto err; + } + + nfs_loc_wipe(&rootloc); + +err: + return ret; +} + +int +nfs_startup_subvolumes(xlator_t *nfsx) +{ + int ret = -1; + xlator_list_t *cl = NULL; + struct nfs_state *nfs = NULL; + + if (!nfsx) + return -1; + + nfs = nfsx->private; + cl = nfs->subvols; + while (cl) { + gf_msg_debug(GF_NFS, 0, "Starting subvolume: %s", cl->xlator->name); + ret = nfs_startup_subvolume(nfsx, cl->xlator); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, 0, NFS_MSG_STARTUP_FAIL, + "Failed to start-up xlator: %s", cl->xlator->name); + goto err; + } + cl = cl->next; + } + + ret = 0; +err: + return ret; +} + +int +nfs_init_subvolume(struct nfs_state *nfs, xlator_t *xl) +{ + unsigned int lrusize = 0; + int ret = -1; + + if ((!nfs) || (!xl)) + return -1; + + lrusize = nfs->memfactor * GF_NFS_INODE_LRU_MULT; + xl->itable = inode_table_new(lrusize, xl); + if (!xl->itable) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate inode table"); + goto err; + } + ret = 0; +err: + return ret; +} + +int +nfs_init_subvolumes(struct nfs_state *nfs, xlator_list_t *cl) +{ + int ret = -1; + unsigned int lrusize = 0; + int svcount = 0; + + if ((!nfs) || (!cl)) + return -1; + + lrusize = nfs->memfactor * GF_NFS_INODE_LRU_MULT; + nfs->subvols = cl; + gf_msg_trace(GF_NFS, 0, "inode table lru: %d", lrusize); + + while (cl) { + gf_msg_debug(GF_NFS, 0, "Initing subvolume: %s", cl->xlator->name); + ret = nfs_init_subvolume(nfs, cl->xlator); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, 0, NFS_MSG_XLATOR_INIT_FAIL, + "Failed to init " + "xlator: %s", + cl->xlator->name); + goto err; + } + ++svcount; + cl = cl->next; + } + + LOCK_INIT(&nfs->svinitlock); + nfs->initedxl = GF_CALLOC(svcount, sizeof(xlator_t *), gf_nfs_mt_xlator_t); + if (!nfs->initedxl) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocated inited xls"); + ret = -1; + goto err; + } + + gf_msg_trace(GF_NFS, 0, "Inited volumes: %d", svcount); + nfs->allsubvols = svcount; + ret = 0; +err: + return ret; +} + +int +nfs_user_root_create(nfs_user_t *newnfu) +{ + if (!newnfu) + return -1; + + newnfu->uid = 0; + newnfu->gids[0] = 0; + newnfu->ngrps = 1; + + return 0; +} + +int +nfs_user_create(nfs_user_t *newnfu, uid_t uid, gid_t gid, + rpc_transport_t *trans, gid_t *auxgids, int auxcount) +{ + int x = 1; + int y = 0; + + /* We test for GF_REQUEST_MAXGROUPS instead of NFS_FOP_NGROUPS because + * the latter accounts for the @gid being in @auxgids, which is not the + * case here. + */ + if ((!newnfu) || (auxcount > GF_REQUEST_MAXGROUPS)) + return -1; + + newnfu->uid = uid; + newnfu->gids[0] = gid; + newnfu->ngrps = 1; + if (trans) { + memcpy(&newnfu->identifier, trans->peerinfo.identifier, UNIX_PATH_MAX); + } + + gf_msg_trace(GF_NFS, 0, "uid: %d, gid %d, gids: %d", uid, gid, auxcount); + + if (!auxgids) + return 0; + + for (; y < auxcount; ++x, ++y) { + newnfu->gids[x] = auxgids[y]; + ++newnfu->ngrps; + gf_msg_trace(GF_NFS, 0, "gid: %d", auxgids[y]); + } + + return 0; +} + +void +nfs_request_user_init(nfs_user_t *nfu, rpcsvc_request_t *req) +{ + gid_t *gidarr = NULL; + int gids = 0; + + if ((!req) || (!nfu)) + return; + + gidarr = rpcsvc_auth_unix_auxgids(req, &gids); + nfs_user_create(nfu, rpcsvc_request_uid(req), rpcsvc_request_gid(req), + rpcsvc_request_transport(req), gidarr, gids); + + return; +} + +void +nfs_request_primary_user_init(nfs_user_t *nfu, rpcsvc_request_t *req, uid_t uid, + gid_t gid) +{ + gid_t *gidarr = NULL; + int gids = 0; + + if ((!req) || (!nfu)) + return; + + gidarr = rpcsvc_auth_unix_auxgids(req, &gids); + nfs_user_create(nfu, uid, gid, rpcsvc_request_transport(req), gidarr, gids); + + return; +} + +int32_t +mem_acct_init(xlator_t *this) +{ + int ret = -1; + + if (!this) + return ret; + + ret = xlator_mem_acct_init(this, gf_nfs_mt_end + 1); + + if (ret != 0) { + gf_msg(this->name, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory accounting init failed"); + return ret; + } + + return ret; +} + +struct nfs_state * +nfs_init_state(xlator_t *this) +{ + struct nfs_state *nfs = NULL; + int i = 0, ret = -1; + unsigned int fopspoolsize = 0; + char *optstr = NULL; + gf_boolean_t boolt = _gf_false; + struct stat stbuf = { + 0, + }; + + if (!this) + return NULL; + + if (!this->children) { + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_NFS_MAN_DISABLE, + "NFS is manually disabled: Exiting"); + /* Nothing for nfs process to do, exit cleanly */ + kill(getpid(), SIGTERM); + } + + nfs = GF_CALLOC(1, sizeof(*nfs), gf_nfs_mt_nfs_state); + if (!nfs) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "memory allocation failed"); + return NULL; + } + + nfs->memfactor = GF_NFS_DEFAULT_MEMFACTOR; + if (dict_get(this->options, "nfs.mem-factor")) { + ret = dict_get_str(this->options, "nfs.mem-factor", &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_DICT_GET_FAILED, + "Failed to parse dict"); + goto free_rpcsvc; + } + + ret = gf_string2uint(optstr, &nfs->memfactor); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse uint string"); + goto free_rpcsvc; + } + } + + fopspoolsize = nfs->memfactor * GF_NFS_CONCURRENT_OPS_MULT; + /* FIXME: Really saddens me to see this as xlator wide. */ + nfs->foppool = mem_pool_new(struct nfs_fop_local, fopspoolsize); + if (!nfs->foppool) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to allocate fops local pool"); + goto free_rpcsvc; + } + + nfs->dynamicvolumes = GF_NFS_DVM_OFF; + if (dict_get(this->options, "nfs.dynamic-volumes")) { + ret = dict_get_str(this->options, "nfs.dynamic-volumes", &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_DICT_GET_FAILED, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse bool string"); + goto free_foppool; + } + + if (boolt == _gf_true) + nfs->dynamicvolumes = GF_NFS_DVM_ON; + } + + nfs->enable_nlm = _gf_true; + ret = dict_get_str_boolean(this->options, "nfs.nlm", _gf_true); + if (ret == _gf_false) { + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_NLM_MAN_DISABLE, + "NLM is manually disabled"); + nfs->enable_nlm = _gf_false; + } + + nfs->enable_acl = _gf_true; + ret = dict_get_str_boolean(this->options, "nfs.acl", _gf_true); + if (ret == _gf_false) { + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_ACL_MAN_DISABLE, + "ACL is manually disabled"); + nfs->enable_acl = _gf_false; + } + + nfs->enable_ino32 = 0; + if (dict_get(this->options, "nfs.enable-ino32")) { + ret = dict_get_str(this->options, "nfs.enable-ino32", &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse bool string"); + goto free_foppool; + } + + if (boolt == _gf_true) + nfs->enable_ino32 = 1; + } + + if (dict_get(this->options, "nfs.port")) { + ret = dict_get_str(this->options, "nfs.port", &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2uint(optstr, &nfs->override_portnum); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse uint string"); + goto free_foppool; + } + } + + if (dict_get(this->options, "transport.socket.bind-address")) { + ret = dict_get_str(this->options, "transport.socket.bind-address", + &optstr); + if (ret < 0) { + gf_log(GF_NFS, GF_LOG_ERROR, + "Failed to parse " + "transport.socket.bind-address string"); + } else { + this->instance_name = gf_strdup(optstr); + for (i = 0; i < strlen(this->instance_name); i++) { + if (this->instance_name[i] == '.' || + this->instance_name[i] == ':') + this->instance_name[i] = '_'; + } + } + } + + if (dict_get(this->options, "transport.socket.listen-port") == NULL) { + if (nfs->override_portnum) + ret = gf_asprintf(&optstr, "%d", nfs->override_portnum); + else + ret = gf_asprintf(&optstr, "%d", GF_NFS3_PORT); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "failed mem-allocation"); + goto free_foppool; + } + ret = dict_set_dynstr(this->options, "transport.socket.listen-port", + optstr); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_DICT_SET_FAILED, + "dict_set_dynstr error"); + goto free_foppool; + } + } + +#ifdef IPV6_DEFAULT + ret = dict_set_str(this->options, "transport.address-family", "inet6"); + if (ret == -1) { + gf_log(GF_NFS, GF_LOG_ERROR, "dict_set_str error"); + goto free_foppool; + } +#endif + + /* Right only socket support exists between nfs client and + * gluster nfs, so we can set default value as socket + */ + ret = dict_set_str(this->options, "transport-type", "socket"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto free_foppool; + } + + nfs->mount_udp = 0; + if (dict_get(this->options, "nfs.mount-udp")) { + ret = dict_get_str(this->options, "nfs.mount-udp", &optstr); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse bool " + "string"); + goto free_foppool; + } + + if (boolt == _gf_true) + nfs->mount_udp = 1; + } + + nfs->exports_auth = GF_NFS_DEFAULT_EXPORT_AUTH; + if (dict_get(this->options, "nfs.exports-auth-enable")) { + ret = dict_get_str(this->options, "nfs.exports-auth-enable", &optstr); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse bool string"); + goto free_foppool; + } + + if (boolt == _gf_true) + nfs->exports_auth = 1; + } + + nfs->auth_refresh_time_secs = GF_NFS_DEFAULT_AUTH_REFRESH_INTERVAL_SEC; + if (dict_get(this->options, "nfs.auth-refresh-interval-sec")) { + ret = dict_get_str(this->options, "nfs.auth-refresh-interval-sec", + &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2uint(optstr, &nfs->auth_refresh_time_secs); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse uint string"); + goto free_foppool; + } + } + + nfs->auth_cache_ttl_sec = GF_NFS_DEFAULT_AUTH_CACHE_TTL_SEC; + if (dict_get(this->options, "nfs.auth-cache-ttl-sec")) { + ret = dict_get_str(this->options, "nfs.auth-cache-ttl-sec", &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2uint(optstr, &nfs->auth_cache_ttl_sec); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse uint string"); + goto free_foppool; + } + } + + /* TODO: Make this a configurable option in case we don't want to read + * exports/netgroup files off disk when they change. */ + nfs->refresh_auth = 1; + + nfs->rmtab = gf_strdup(NFS_DATADIR "/rmtab"); + if (dict_get(this->options, "nfs.mount-rmtab")) { + ret = dict_get_str(this->options, "nfs.mount-rmtab", &nfs->rmtab); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + /* check if writing the rmtab is disabled*/ + if (nfs->rmtab && strcmp("/-", nfs->rmtab) == 0) { + GF_FREE(nfs->rmtab); + nfs->rmtab = NULL; + } + } + + /* support both options rpc-auth.ports.insecure and + * rpc-auth-allow-insecure for backward compatibility + */ + nfs->allow_insecure = 1; + if (dict_get(this->options, "rpc-auth.ports.insecure")) { + ret = dict_get_str(this->options, "rpc-auth.ports.insecure", &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse bool " + "string"); + goto free_foppool; + } + + if (boolt == _gf_false) + nfs->allow_insecure = 0; + } + + if (dict_get(this->options, "rpc-auth-allow-insecure")) { + ret = dict_get_str(this->options, "rpc-auth-allow-insecure", &optstr); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_PARSE_FAIL, + "Failed to parse dict"); + goto free_foppool; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_PARSE_FAIL, + "Failed to parse bool string"); + goto free_foppool; + } + + if (boolt == _gf_false) + nfs->allow_insecure = 0; + } + + if (nfs->allow_insecure) { + /* blindly set both the options */ + ret = dict_set_str(this->options, "rpc-auth-allow-insecure", "on"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto free_foppool; + } + ret = dict_set_str(this->options, "rpc-auth.ports.insecure", "on"); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto free_foppool; + } + } + + GF_OPTION_INIT("nfs.rdirplus", nfs->rdirplus, bool, free_foppool); + + GF_OPTION_INIT(OPT_SERVER_RPC_STATD, nfs->rpc_statd, path, free_foppool); + + GF_OPTION_INIT(OPT_SERVER_RPC_STATD_PIDFILE, nfs->rpc_statd_pid_file, path, + free_foppool); + + GF_OPTION_INIT(OPT_SERVER_AUX_GIDS, nfs->server_aux_gids, bool, + free_foppool); + GF_OPTION_INIT(OPT_SERVER_GID_CACHE_TIMEOUT, nfs->server_aux_gids_max_age, + uint32, free_foppool); + + if (gid_cache_init(&nfs->gid_cache, nfs->server_aux_gids_max_age) < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_INIT_GRP_CACHE_FAIL, + "Failed to initialize group cache."); + goto free_foppool; + } + + ret = sys_access(nfs->rpc_statd, X_OK); + if (ret) { + gf_msg(GF_NFS, GF_LOG_WARNING, EPERM, NFS_MSG_NO_PERM, + "%s not enough permissions to access. Disabling NLM", + nfs->rpc_statd); + nfs->enable_nlm = _gf_false; + } + + ret = sys_stat(nfs->rpc_statd, &stbuf); + if (ret || !S_ISREG(stbuf.st_mode)) { + gf_msg(GF_NFS, GF_LOG_WARNING, 0, NFS_MSG_REG_FILE_ERROR, + "%s not a regular file. Disabling NLM", nfs->rpc_statd); + nfs->enable_nlm = _gf_false; + } + + nfs->rpcsvc = rpcsvc_init(this, this->ctx, this->options, fopspoolsize); + if (!nfs->rpcsvc) { + ret = -1; + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RPC_INIT_FAIL, + "RPC service init failed"); + goto free_foppool; + } + + ret = rpcsvc_set_throttle_on(nfs->rpcsvc); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_ENABLE_THROTTLE_FAIL, + "Enabling throttle failed"); + goto free_foppool; + } + + ret = rpcsvc_set_outstanding_rpc_limit( + nfs->rpcsvc, this->options, RPCSVC_DEF_NFS_OUTSTANDING_RPC_LIMIT); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RPC_CONFIG_FAIL, + "Failed to configure outstanding-rpc-limit"); + goto free_foppool; + } + + nfs->register_portmap = rpcsvc_register_portmap_enabled(nfs->rpcsvc); + + GF_OPTION_INIT("nfs.event-threads", nfs->event_threads, uint32, + free_foppool); + gf_event_reconfigure_threads(this->ctx->event_pool, nfs->event_threads); + + this->private = (void *)nfs; + INIT_LIST_HEAD(&nfs->versions); + nfs->generation = 1965; + + ret = 0; + +free_foppool: + if (ret < 0) + mem_pool_destroy(nfs->foppool); + +free_rpcsvc: + /* + * rpcsvc_deinit */ + if (ret < 0) { + GF_FREE(nfs); + nfs = NULL; + } + + return nfs; +} + +int +nfs_drc_init(xlator_t *this) +{ + int ret = -1; + rpcsvc_t *svc = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS, this, out); + GF_VALIDATE_OR_GOTO(GF_NFS, this->private, out); + + svc = ((struct nfs_state *)(this->private))->rpcsvc; + if (!svc) + goto out; + + ret = rpcsvc_drc_init(svc, this->options); + +out: + return ret; +} + +static int +nfs_reconfigure_state(xlator_t *this, dict_t *options) +{ + int ret = 0; + int keyindx = 0; + char *rmtab = NULL; + char *rpc_statd = NULL; + gf_boolean_t optbool; + uint32_t optuint32; + struct nfs_state *nfs = NULL; + static char *options_require_restart[] = {"nfs.port", "nfs.transport-type", + "nfs.mem-factor", NULL}; + + GF_VALIDATE_OR_GOTO(GF_NFS, this, out); + GF_VALIDATE_OR_GOTO(GF_NFS, this->private, out); + GF_VALIDATE_OR_GOTO(GF_NFS, options, out); + + nfs = (struct nfs_state *)this->private; + + /* Some listed options can't be reconfigured, they need + * NFS to be restarted. There are two cases 1. SET 2. UNSET. + * 1. SET */ + while (options_require_restart[keyindx]) { + if (dict_get(options, options_require_restart[keyindx])) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "Reconfiguring %s needs NFS restart", + options_require_restart[keyindx]); + goto out; + } + keyindx++; + } + + /* UNSET for nfs.mem-factor */ + if ((!dict_get(options, "nfs.mem-factor")) && + (nfs->memfactor != GF_NFS_DEFAULT_MEMFACTOR)) { + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_FAIL, + "Reconfiguring nfs.mem-factor needs NFS restart"); + goto out; + } + + /* UNSET for nfs.port */ + if ((!dict_get(options, "nfs.port")) && (nfs->override_portnum)) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "Reconfiguring nfs.port needs NFS restart"); + goto out; + } + + /* reconfig nfs.rpc-statd... */ + rpc_statd = GF_RPC_STATD_PROG; + if (dict_get(options, OPT_SERVER_RPC_STATD_PIDFILE)) { + ret = dict_get_str(options, "nfs.rpc-statd", &rpc_statd); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read reconfigured option: " + "nfs.rpc-statd"); + goto out; + } + } + + if (strcmp(nfs->rpc_statd, rpc_statd) != 0) { + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_FAIL, + "Reconfiguring nfs.rpc-statd needs NFS restart"); + goto out; + } + + /* reconfig nfs.mount-rmtab */ + rmtab = NFS_DATADIR "/rmtab"; + if (dict_get(options, "nfs.mount-rmtab")) { + ret = dict_get_str(options, "nfs.mount-rmtab", &rmtab); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read reconfigured option:" + " nfs.mount-rmtab"); + goto out; + } + gf_path_strip_trailing_slashes(rmtab); + } + /* check if writing the rmtab is disabled*/ + if (strcmp("/-", rmtab) == 0) { + GF_FREE(nfs->rmtab); + nfs->rmtab = NULL; + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_WRITE_FAIL, + "Disabled writing of nfs.mount-rmtab"); + } else if (!nfs->rmtab || strcmp(nfs->rmtab, rmtab) != 0) { + mount_rewrite_rmtab(nfs->mstate, rmtab); + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_PATH, + "Reconfigured nfs.mount-rmtab path: %s", nfs->rmtab); + } + + GF_OPTION_RECONF(OPT_SERVER_AUX_GIDS, optbool, options, bool, out); + if (nfs->server_aux_gids != optbool) { + nfs->server_aux_gids = optbool; + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_VALUE, + "Reconfigured %s with value %d", OPT_SERVER_AUX_GIDS, optbool); + } + + GF_OPTION_RECONF(OPT_SERVER_GID_CACHE_TIMEOUT, optuint32, options, uint32, + out); + if (nfs->server_aux_gids_max_age != optuint32) { + nfs->server_aux_gids_max_age = optuint32; + gid_cache_reconf(&nfs->gid_cache, optuint32); + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_VALUE, + "Reconfigured %s with value %d", OPT_SERVER_GID_CACHE_TIMEOUT, + optuint32); + } + + GF_OPTION_RECONF("nfs.rdirplus", optbool, options, bool, out); + if (nfs->rdirplus != optbool) { + nfs->rdirplus = optbool; + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_VALUE, + "Reconfigured nfs.rdirplus with value %d", optbool); + } + + /* reconfig nfs.dynamic-volumes */ + ret = dict_get_str_boolean(options, "nfs.dynamic-volumes", GF_NFS_DVM_OFF); + switch (ret) { + case GF_NFS_DVM_ON: + case GF_NFS_DVM_OFF: + optbool = ret; + break; + default: + optbool = GF_NFS_DVM_OFF; + break; + } + if (nfs->dynamicvolumes != optbool) { + nfs->dynamicvolumes = optbool; + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_VOL, + "Reconfigured nfs.dynamic-volumes with value %d", optbool); + } + + optbool = _gf_false; + if (dict_get(options, "nfs.enable-ino32")) { + ret = dict_get_str_boolean(options, "nfs.enable-ino32", _gf_false); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read reconfigured option: " + "nfs.enable-ino32"); + goto out; + } + optbool = ret; + } + if (nfs->enable_ino32 != optbool) { + nfs->enable_ino32 = optbool; + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_RECONFIG_ENABLE, + "Reconfigured nfs.enable-ino32 with value %d", optbool); + } + + /* nfs.nlm is enabled by default */ + ret = dict_get_str_boolean(options, "nfs.nlm", _gf_true); + if (ret < 0) { + optbool = _gf_true; + } else { + optbool = ret; + } + if (nfs->enable_nlm != optbool) { + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_NLM_INFO, + "NLM is" + " manually %s", + (optbool ? "enabled" : "disabled")); + nfs->enable_nlm = optbool; + nfs_reconfigure_nlm4(this); + } + + /* nfs.acl is enabled by default */ + ret = dict_get_str_boolean(options, "nfs.acl", _gf_true); + if (ret < 0) { + optbool = _gf_true; + } else { + optbool = ret; + } + if (nfs->enable_acl != optbool) { + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_ACL_INFO, + "ACL is " + "manually %s", + (optbool ? "enabled" : "disabled")); + nfs->enable_acl = optbool; + nfs_reconfigure_acl3(this); + } + + GF_OPTION_RECONF("nfs.event-threads", nfs->event_threads, options, uint32, + out); + gf_event_reconfigure_threads(this->ctx->event_pool, nfs->event_threads); + + ret = 0; +out: + return ret; +} + +/* + * reconfigure() for NFS server xlator. + */ +int +reconfigure(xlator_t *this, dict_t *options) +{ + int ret = 0; + struct nfs_state *nfs = NULL; + gf_boolean_t regpmap = _gf_true; + + if ((!this) || (!this->private) || (!options)) + return (-1); + + nfs = (struct nfs_state *)this->private; + + /* Reconfigure nfs options */ + ret = nfs_reconfigure_state(this, options); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "nfs reconfigure state failed"); + return (-1); + } + + /* Reconfigure nfs3 options */ + ret = nfs3_reconfigure_state(this, options); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "nfs3 reconfigure state failed"); + return (-1); + } + + /* Reconfigure mount options */ + ret = mount_reconfigure_state(this, options); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "mount reconfigure state failed"); + return (-1); + } + + /* Reconfigure rpc layer */ + ret = rpcsvc_reconfigure_options(nfs->rpcsvc, options); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "rpcsvc reconfigure options failed"); + return (-1); + } + + /* Reconfigure rpc.outstanding-rpc-limit */ + ret = rpcsvc_set_outstanding_rpc_limit( + nfs->rpcsvc, options, RPCSVC_DEF_NFS_OUTSTANDING_RPC_LIMIT); + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "Failed to reconfigure outstanding-rpc-limit"); + return (-1); + } + + regpmap = rpcsvc_register_portmap_enabled(nfs->rpcsvc); + if (nfs->register_portmap != regpmap) { + nfs->register_portmap = regpmap; + if (regpmap) { + (void)nfs_program_register_portmap_all(nfs); + } else { + (void)nfs_program_unregister_portmap_all(nfs); + } + } + + /* Reconfigure drc */ + ret = rpcsvc_drc_reconfigure(nfs->rpcsvc, options); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_RECONFIG_FAIL, + "rpcsvc DRC reconfigure failed"); + return (-1); + } + + return (0); +} + +/* Main init() routine for NFS server xlator. It inits NFS v3 protocol + * and its dependent protocols e.g. ACL, MOUNT v3 (mount3), NLM and + * DRC. + * + * Usage: glusterfsd: + * glusterfs_process_volfp() => + * glusterfs_graph_activate() => + * glusterfs_graph_init() => + * xlator_init () => NFS init() routine + * + * If init() routine fails, the glusterfsd cleans up the NFS process + * by invoking cleanup_and_exit(). + * + * RETURN: + * 0 (SUCCESS) if all protocol specific inits PASS. + * -1 (FAILURE) if any of them FAILS. + */ +int +init(xlator_t *this) +{ + struct nfs_state *nfs = NULL; + int ret = -1; + + if (!this) + return (-1); + + nfs = nfs_init_state(this); + if (!nfs) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_OPT_INIT_FAIL, + "Failed to init nfs option"); + return (-1); + } + + ret = nfs_add_all_initiators(nfs); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_INIT_FAIL, + "Failed to add initiators"); + return (-1); + } + + ret = nfs_init_subvolumes(nfs, this->children); + if (ret) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, 0, NFS_MSG_INIT_FAIL, + "Failed to init NFS exports"); + return (-1); + } + + ret = mount_init_state(this); + if (ret) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, 0, NFS_MSG_INIT_FAIL, + "Failed to init Mount state"); + return (-1); + } + + ret = nlm4_init_state(this); + if (ret) { + gf_msg(GF_NFS, GF_LOG_CRITICAL, 0, NFS_MSG_INIT_FAIL, + "Failed to init NLM state"); + return (-1); + } + + ret = nfs_init_versions(nfs, this); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_INIT_FAIL, + "Failed to initialize protocols"); + return (-1); + } + + ret = nfs_drc_init(this); + if (ret) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_INIT_FAIL, + "Failed to initialize DRC"); + return (-1); + } + + gf_msg(GF_NFS, GF_LOG_INFO, 0, NFS_MSG_STARTED, "NFS service started"); + return (0); /* SUCCESS */ +} + +int +notify(xlator_t *this, int32_t event, void *data, ...) +{ + xlator_t *subvol = NULL; + struct nfs_state *priv = NULL; + + subvol = (xlator_t *)data; + + gf_msg_trace(GF_NFS, 0, "Notification received: %d", event); + + switch (event) { + case GF_EVENT_CHILD_UP: + nfs_startup_subvolume(this, subvol); + break; + + case GF_EVENT_SOME_DESCENDENT_DOWN: + case GF_EVENT_SOME_DESCENDENT_UP: + priv = this->private; + ++(priv->generation); + break; + + case GF_EVENT_PARENT_UP: + default_notify(this, GF_EVENT_PARENT_UP, data); + break; + } + + return 0; +} + +void +fini(xlator_t *this) +{ + struct nfs_state *nfs = NULL; + + mnt3svc_deinit(this); + nfs = (struct nfs_state *)this->private; + gf_msg_debug(GF_NFS, 0, "NFS service going down"); + nfs_deinit_versions(&nfs->versions, this); + GF_FREE(this->instance_name); + return; +} + +int32_t +nfs_forget(xlator_t *this, inode_t *inode) +{ + uint64_t ctx = 0; + struct nfs_inode_ctx *ictx = NULL; + + if (inode_ctx_del(inode, this, &ctx)) + return -1; + + ictx = (struct nfs_inode_ctx *)(uintptr_t)ctx; + GF_FREE(ictx); + + return 0; +} + +gf_boolean_t +_nfs_export_is_for_vol(char *exname, char *volname) +{ + gf_boolean_t ret = _gf_false; + char *tmp = NULL; + + tmp = exname; + if (tmp[0] == '/') + tmp++; + + if (!strcmp(tmp, volname)) + ret = _gf_true; + + return ret; +} + +int +nfs_priv_to_dict(xlator_t *this, dict_t *dict, char *brickname) +{ + int ret = -1; + struct nfs_state *priv = NULL; + struct mountentry *mentry = NULL; + char *volname = NULL; + char key[1024] = { + 0, + }; + int count = 0; + + GF_VALIDATE_OR_GOTO(THIS->name, this, out); + GF_VALIDATE_OR_GOTO(THIS->name, dict, out); + + priv = this->private; + GF_ASSERT(priv); + + ret = dict_get_str(dict, "volname", &volname); + if (ret) { + gf_msg(this->name, GF_LOG_ERROR, -ret, NFS_MSG_VOL_NOT_FOUND, + "Could not get volname"); + goto out; + } + + list_for_each_entry(mentry, &priv->mstate->mountlist, mlist) + { + if (!_nfs_export_is_for_vol(mentry->exname, volname)) + continue; + + snprintf(key, sizeof(key), "client%d.hostname", count); + ret = dict_set_str(dict, key, mentry->hostname); + if (ret) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_WRITE_FAIL, + "Error writing hostname to dict"); + goto out; + } + + /* No connection data available yet in nfs server. + * Hence, setting to 0 to prevent cli failing + */ + snprintf(key, sizeof(key), "client%d.bytesread", count); + ret = dict_set_uint64(dict, key, 0); + if (ret) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_WRITE_FAIL, + "Error writing bytes read to dict"); + goto out; + } + + snprintf(key, sizeof(key), "client%d.byteswrite", count); + ret = dict_set_uint64(dict, key, 0); + if (ret) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_WRITE_FAIL, + "Error writing bytes write to dict"); + goto out; + } + + count++; + } + + ret = dict_set_int32(dict, "clientcount", count); + if (ret) + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_WRITE_FAIL, + "Error writing client count to dict"); + +out: + gf_msg_debug(THIS->name, 0, "Returning %d", ret); + return ret; +} + +extern int32_t +nlm_priv(xlator_t *this); + +int32_t +nfs_priv(xlator_t *this) +{ + int32_t ret = -1; + + /* DRC needs the global drc structure, xl is of no use to it. */ + ret = rpcsvc_drc_priv(((struct nfs_state *)(this->private))->rpcsvc->drc); + if (ret) { + gf_msg_debug(this->name, 0, "Statedump of DRC failed"); + goto out; + } + + ret = nlm_priv(this); + if (ret) { + gf_msg_debug(this->name, 0, "Statedump of NLM failed"); + goto out; + } +out: + return ret; +} + +int32_t +nfs_itable_dump(xlator_t *this) +{ + if (!this) + return -1; + + if (this->next && this->next->itable) { + gf_proc_dump_add_section("xlator.nfs.itable"); + inode_table_dump(this->next->itable, "xlator.nfs.itable"); + } + + return 0; +} + +struct xlator_cbks cbks = { + .forget = nfs_forget, +}; + +struct xlator_fops fops; + +struct xlator_dumpops dumpops = { + .priv = nfs_priv, + .priv_to_dict = nfs_priv_to_dict, + .inode = nfs_itable_dump, +}; + +/* TODO: If needed, per-volume options below can be extended to be export + * specific also because after export-dir is introduced, a volume is not + * necessarily an export whereas different subdirectories within that volume + * can be and may need these options to be specified separately. + */ +struct volume_options options[] = { + {.key = {"nfs3.read-size"}, + .type = GF_OPTION_TYPE_SIZET, + .min = GF_NFS3_RTMIN, + .max = GF_NFS3_RTMAX, + .default_value = TOSTRING(GF_NFS3_RTPREF), + .description = "Size in which the client should issue read requests " + "to the Gluster NFSv3 server. Must be a multiple of " + "4KB (4096). Min and Max supported values are 4KB " + "(4096) and 1MB (1048576) respectively. If the " + "specified value is within the supported range but " + "not a multiple of 4096, it is rounded up to the " + "nearest multiple of 4096."}, + {.key = {"nfs3.write-size"}, + .type = GF_OPTION_TYPE_SIZET, + .min = GF_NFS3_WTMIN, + .max = GF_NFS3_WTMAX, + .default_value = TOSTRING(GF_NFS3_WTPREF), + .description = "Size in which the client should issue write requests " + "to the Gluster NFSv3 server. Must be a multiple of " + "1KB (1024). Min and Max supported values are " + "4KB (4096) and 1MB(1048576) respectively. If the " + "specified value is within the supported range but " + "not a multiple of 4096, it is rounded up to the " + "nearest multiple of 4096."}, + {.key = {"nfs3.readdir-size"}, + .type = GF_OPTION_TYPE_SIZET, + .min = GF_NFS3_DTMIN, + .max = GF_NFS3_DTMAX, + .default_value = TOSTRING(GF_NFS3_DTPREF), + .description = "Size in which the client should issue directory " + "reading requests to the Gluster NFSv3 server. Must " + "be a multiple of 1KB (1024). Min and Max supported " + "values are 4KB (4096) and 1MB (1048576) respectively." + "If the specified value is within the supported range " + "but not a multiple of 4096, it is rounded up to the " + "nearest multiple of 4096."}, + {.key = {"nfs3.*.volume-access"}, + .type = GF_OPTION_TYPE_STR, + .value = {"read-only", "read-write"}, + .default_value = "read-write", + .description = "Type of access desired for this subvolume: " + " read-only, read-write(default)"}, + {.key = {"nfs3.*.trusted-write"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "On an UNSTABLE write from client, return STABLE flag" + " to force client to not send a COMMIT request. In " + "some environments, combined with a replicated " + "GlusterFS setup, this option can improve write " + "performance. This flag allows user to trust Gluster" + " replication logic to sync data to the disks and " + "recover when required. COMMIT requests if received " + "will be handled in a default manner by fsyncing." + " STABLE writes are still handled in a sync manner. " + "Off by default." + + }, + {.key = {"nfs3.*.trusted-sync"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "All writes and COMMIT requests are treated as async." + " This implies that no write requests are guaranteed" + " to be on server disks when the write reply is " + "received at the NFS client. Trusted sync includes " + " trusted-write behaviour. Off by default." + + }, + {.key = {"nfs3.*.export-dir"}, + .type = GF_OPTION_TYPE_PATH, + .default_value = "", + .description = "By default, all subvolumes of nfs are exported as " + "individual exports. There are cases where a " + "subdirectory or subdirectories in the volume need to " + "be exported separately. This option can also be used " + "in conjunction with nfs3.export-volumes option to " + "restrict exports only to the subdirectories specified" + " through this option. Must be an absolute path. Along" + " with path allowed list of IPs/hostname can be " + "associated with each subdirectory. If provided " + "connection will allowed only from these IPs. By " + "default connections from all IPs are allowed. " + "Format: <dir>[(hostspec[|hostspec|...])][,...]. Where" + " hostspec can be an IP address, hostname or an IP " + "range in CIDR notation. " + "e.g. /foo(192.168.1.0/24|host1|10.1.1.8),/host2." + " NOTE: Care must be taken while configuring this " + "option as invalid entries and/or unreachable DNS " + "servers can introduce unwanted delay in all the mount" + " calls."}, + {.key = {"nfs3.export-dirs"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "By default, all subvolumes of nfs are exported as " + "individual exports. There are cases where a " + "subdirectory or subdirectories in the volume need to " + "be exported separately. Enabling this option allows " + "any directory on a volumes to be exported separately." + "Directory exports are enabled by default."}, + {.key = {"nfs3.export-volumes"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "Enable or disable exporting whole volumes, instead " + "if used in conjunction with nfs3.export-dir, can " + "allow setting up only subdirectories as exports. On " + "by default."}, + {.key = {"rpc-auth.auth-unix"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "Disable or enable the AUTH_UNIX authentication type." + "Must always be enabled for better interoperability. " + "However, can be disabled if needed. Enabled by " + "default"}, + {.key = {"rpc-auth.auth-null"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "Disable or enable the AUTH_NULL authentication type." + "Must always be enabled. This option is here only to" + " avoid unrecognized option warnings"}, + {.key = {"rpc-auth.auth-unix.*"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "Disable or enable the AUTH_UNIX authentication type " + "for a particular exported volume overriding defaults" + " and general setting for AUTH_UNIX scheme. Must " + "always be enabled for better interoperability. " + "However, can be disabled if needed. Enabled by " + "default."}, + {.key = {"rpc-auth.auth-unix.*.allow"}, + .type = GF_OPTION_TYPE_STR, + .default_value = "on", + .description = "Disable or enable the AUTH_UNIX authentication type " + "for a particular exported volume overriding defaults" + " and general setting for AUTH_UNIX scheme. Must " + "always be enabled for better interoperability. " + "However, can be disabled if needed. Enabled by " + "default."}, + {.key = {"rpc-auth.auth-null.*"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "Disable or enable the AUTH_NULL authentication type " + "for a particular exported volume overriding defaults" + " and general setting for AUTH_NULL. Must always be " + "enabled. This option is here only to avoid " + "unrecognized option warnings."}, + {.key = {"rpc-auth.addr.allow"}, + .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR, + .default_value = "all", + .description = "Allow a comma separated list of addresses and/or" + " hostnames to connect to the server. By default, all" + " connections are allowed. This allows users to " + "define a general rule for all exported volumes."}, + {.key = {"rpc-auth.addr.reject"}, + .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR, + .default_value = "none", + .description = "Reject a comma separated list of addresses and/or" + " hostnames from connecting to the server. By default," + " all connections are allowed. This allows users to " + "define a general rule for all exported volumes."}, + {.key = {"rpc-auth.addr.*.allow"}, + .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR, + .default_value = "all", + .description = "Allow a comma separated list of addresses and/or" + " hostnames to connect to the server. By default, all" + " connections are allowed. This allows users to " + "define a rule for a specific exported volume."}, + {.key = {"rpc-auth.addr.*.reject"}, + .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR, + .default_value = "none", + .description = "Reject a comma separated list of addresses and/or" + " hostnames from connecting to the server. By default," + " all connections are allowed. This allows users to " + "define a rule for a specific exported volume."}, + {.key = {"rpc-auth.ports.insecure"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "Allow client connections from unprivileged ports. By " + "default only privileged ports are allowed. This is a" + "global setting in case insecure ports are to be " + "enabled for all exports using a single option."}, + {.key = {"rpc-auth.ports.*.insecure"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "Allow client connections from unprivileged ports. By " + "default only privileged ports are allowed. Use this" + " option to enable or disable insecure ports for " + "a specific subvolume and to override the global " + "setting set by the previous option."}, + {.key = {"rpc-auth.addr.namelookup"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "Users have the option of turning on name lookup for" + " incoming client connections using this option. Use this " + "option to turn on name lookups during address-based " + "authentication. Turning this on will enable you to" + " use hostnames in nfs.rpc-auth-* filters. In some " + "setups, the name server can take too long to reply to DNS " + "queries resulting in timeouts of mount requests. By " + "default, name lookup is off"}, + {.key = {"nfs.dynamic-volumes"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "Internal option set to tell gnfs to use a different" + " scheme for encoding file handles when DVM is being" + " used."}, + {.key = {"nfs3.*.volume-id"}, + .type = GF_OPTION_TYPE_STR, + .default_value = "", + .description = "When nfs.dynamic-volumes is set, gnfs expects every " + "subvolume to have this option set for it, so that " + "gnfs can use this option to identify the volume. " + "If all subvolumes do not have this option set, an " + "error is reported."}, + {.key = {"nfs.enable-ino32"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "no", + .description = "For nfs clients or apps that do not support 64-bit " + "inode numbers, use this option to make NFS return " + "32-bit inode numbers instead. Disabled by default, so" + " NFS returns 64-bit inode numbers."}, + {.key = {"rpc.register-with-portmap"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "For systems that need to run multiple nfs servers, " + "only one registration is possible with " + "portmap service. Use this option to turn off portmap " + "registration for Gluster NFS. On by default"}, + {.key = {"rpc.outstanding-rpc-limit"}, + .type = GF_OPTION_TYPE_INT, + .min = RPCSVC_MIN_OUTSTANDING_RPC_LIMIT, + .max = RPCSVC_MAX_OUTSTANDING_RPC_LIMIT, + .default_value = TOSTRING(RPCSVC_DEF_NFS_OUTSTANDING_RPC_LIMIT), + .description = "Parameter to throttle the number of incoming RPC " + "requests from a client. 0 means no limit (can " + "potentially run out of memory)"}, + {.key = {"nfs.port"}, + .type = GF_OPTION_TYPE_INT, + .min = 1, + .max = 0xffff, + .default_value = TOSTRING(GF_NFS3_PORT), + .description = "Use this option on systems that need Gluster NFS to " + "be associated with a non-default port number."}, + {.key = {"nfs.mem-factor"}, + .type = GF_OPTION_TYPE_INT, + .min = 1, + .max = 1024, + .default_value = TOSTRING(GF_NFS_DEFAULT_MEMFACTOR), + .description = "Use this option to make NFS be faster on systems by " + "using more memory. This option specifies a multiple " + "that determines the total amount of memory used. " + "Default value is 15. Increase to use more memory in " + "order to improve performance for certain use cases." + "Please consult gluster-users list before using this " + "option."}, + {.key = {"nfs.*.disable"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "false", + .description = "This option is used to start or stop the NFS server " + "for individual volumes."}, + {.key = {"nfs.nlm"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "This option, if set to 'off', disables NLM server " + "by not registering the service with the portmapper." + " Set it to 'on' to re-enable it. Default value: 'on'"}, + + {.key = {"nfs.mount-udp"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "set the option to 'on' to enable mountd on UDP. " + "Required for some Solaris and AIX NFS clients. " + "The need for enabling this option often depends " + "on the usage of NLM."}, + {.key = {"nfs.mount-rmtab"}, + .type = GF_OPTION_TYPE_PATH, + .default_value = NFS_DATADIR "/rmtab", + .description = "Set the location of the cache file that is used to " + "list all the NFS-clients that have connected " + "through the MOUNT protocol. If this is on shared " + "storage, all GlusterFS servers will update and " + "output (with 'showmount') the same list. Set to " + "\"/-\" to disable."}, + {.key = {OPT_SERVER_RPC_STATD}, + .type = GF_OPTION_TYPE_PATH, + .default_value = GF_RPC_STATD_PROG, + .description = "The executable of RPC statd utility. " + "Defaults to " GF_RPC_STATD_PROG}, + {.key = {OPT_SERVER_RPC_STATD_PIDFILE}, + .type = GF_OPTION_TYPE_PATH, + .default_value = GF_RPC_STATD_PIDFILE, + .description = "The pid file of RPC statd utility. " + "Defaults to " GF_RPC_STATD_PIDFILE}, + {.key = {OPT_SERVER_AUX_GIDS}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "Let the server look up which groups a user belongs " + "to, overwriting the list passed from the client. " + "This enables support for group lists longer than " + "can be passed through the NFS protocol, but is not " + "secure unless users and groups are well synchronized " + "between clients and servers."}, + {.key = {OPT_SERVER_GID_CACHE_TIMEOUT}, + .type = GF_OPTION_TYPE_INT, + .min = 0, + .max = 3600, + .default_value = "300", + .description = "Number of seconds to cache auxiliary-GID data, " + "when " OPT_SERVER_AUX_GIDS " is set."}, + {.key = {"nfs.acl"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "This option is used to control ACL support for NFS."}, + {.key = {"nfs.drc"}, + .type = GF_OPTION_TYPE_STR, + .default_value = "off", + .description = "Enable Duplicate Request Cache in gNFS server to " + "improve correctness of non-idempotent operations like " + "write, delete, link, et al"}, + {.key = {"nfs.drc-size"}, + .type = GF_OPTION_TYPE_INT, + .default_value = "0x20000", + .description = "Sets the number of non-idempotent " + "requests to cache in drc"}, + {.key = {"nfs.exports-auth-enable"}, + .type = GF_OPTION_TYPE_BOOL, + .description = "Set the option to 'on' to enable exports/netgroup " + "authentication in the NFS server and mount daemon."}, + + {.key = {"nfs.auth-refresh-interval-sec"}, + .type = GF_OPTION_TYPE_INT, + .description = "Frequency in seconds that the daemon should check for" + " changes in the exports/netgroups file."}, + + {.key = {"nfs.auth-cache-ttl-sec"}, + .type = GF_OPTION_TYPE_INT, + .description = "Sets the TTL of an entry in the auth cache. Value is " + "in seconds."}, + {.key = {"nfs.rdirplus"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "When this option is set to off NFS falls back to " + "standard readdir instead of readdirp"}, + { + .key = {"nfs.event-threads"}, + .type = GF_OPTION_TYPE_SIZET, + .min = 1, + .max = 32, + .default_value = "2", + .description = "Specifies the number of event threads to execute in" + "in parallel. Larger values would help process" + " responses faster, depending on available processing" + " power. Range 1-32 threads.", + .op_version = {GD_OP_VERSION_4_0_0}, + .flags = OPT_FLAG_SETTABLE, + }, + {.key = {NULL}}, +}; + +xlator_api_t xlator_api = { + .init = init, + .fini = fini, + .notify = notify, + .reconfigure = reconfigure, + .mem_acct_init = mem_acct_init, + .op_version = {1}, + .dumpops = &dumpops, + .fops = &fops, + .cbks = &cbks, + .options = options, + .identifier = "gnfs", + .category = GF_MAINTAINED, +}; diff --git a/xlators/nfs/server/src/nfs.h b/xlators/nfs/server/src/nfs.h new file mode 100644 index 00000000000..e3daaed17a6 --- /dev/null +++ b/xlators/nfs/server/src/nfs.h @@ -0,0 +1,154 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef __NFS_H__ +#define __NFS_H__ + +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include <glusterfs/lkowner.h> +#include <glusterfs/gidcache.h> + +#define GF_NFS "nfs" + +#define GF_NFS_CONCURRENT_OPS_MULT 15 + +#define GF_NFS_INODE_LRU_MULT 6000 + +#define GF_RPC_MIN_THREADS 1 +#define GF_RPC_MAX_THREADS 16 + +#define GF_NFS_DEFAULT_MEMFACTOR 15 +#define GF_NFS_MIN_MEMFACTOR 1 +#define GF_NFS_MAX_MEMFACTOR 30 + +#define GF_NFS_DVM_ON 1 +#define GF_NFS_DVM_OFF 0 + +/* Disable using the exports file by default */ +#define GF_NFS_DEFAULT_EXPORT_AUTH 0 + +#define GF_NFS_DEFAULT_AUTH_REFRESH_INTERVAL_SEC 2 +#define GF_NFS_DEFAULT_AUTH_CACHE_TTL_SEC 300 /* 5 min */ + +/* This corresponds to the max 16 number of group IDs that are sent through an + * RPC request. Since NFS is the only one going to set this, we can be safe + * in keeping this size hardcoded. + */ +#define GF_REQUEST_MAXGROUPS 16 + +/* Callback into a version-specific NFS protocol. + * The return type is used by the nfs.c code to register the protocol. + * with the RPC service. + */ +typedef rpcsvc_program_t *(*nfs_version_initer_t)(xlator_t *nfsx); + +/* List of version-specific protocol initiators */ +struct nfs_initer_list { + struct list_head list; + nfs_version_initer_t init; + rpcsvc_program_t *program; + gf_boolean_t required; +}; + +struct nfs_state { + rpcsvc_t *rpcsvc; + struct list_head versions; + struct mount3_state *mstate; + struct nfs3_state *nfs3state; + struct nlm4_state *nlm4state; + struct mem_pool *foppool; + unsigned int memfactor; + xlator_list_t *subvols; + + gf_lock_t svinitlock; + int allsubvols; + int upsubvols; + xlator_t **initedxl; + int subvols_started; + int dynamicvolumes; + int enable_ino32; + unsigned int override_portnum; + int allow_insecure; + int enable_nlm; + int enable_acl; + int mount_udp; + + /* Enable exports auth model */ + int exports_auth; + /* Refresh auth params from disk periodically */ + int refresh_auth; + + unsigned int auth_refresh_time_secs; + unsigned int auth_cache_ttl_sec; + + char *rmtab; + struct rpc_clnt *rpc_clnt; + gf_boolean_t server_aux_gids; + uint32_t server_aux_gids_max_age; + gid_cache_t gid_cache; + uint32_t generation; + gf_boolean_t register_portmap; + char *rpc_statd; + char *rpc_statd_pid_file; + gf_boolean_t rdirplus; + uint32_t event_threads; +}; + +struct nfs_inode_ctx { + struct list_head shares; + uint32_t generation; +}; + +#define gf_nfs_dvm_on(nfsstt) \ + (((struct nfs_state *)nfsstt)->dynamicvolumes == GF_NFS_DVM_ON) +#define gf_nfs_dvm_off(nfsstt) \ + (((struct nfs_state *)nfsstt)->dynamicvolumes == GF_NFS_DVM_OFF) +#define __gf_nfs_enable_ino32(nfsstt) \ + (((struct nfs_state *)nfsstt)->enable_ino32) +#define gf_nfs_this_private ((struct nfs_state *)((xlator_t *)THIS)->private) +#define gf_nfs_enable_ino32() (__gf_nfs_enable_ino32(gf_nfs_this_private)) + +/* We have one gid more than the glusterfs maximum since we pass the primary + * gid as the first element of the array. + */ +#define NFS_NGROUPS (GF_REQUEST_MAXGROUPS + 1) + +/* Index of the primary gid */ +#define NFS_PRIMGID_IDX 0 + +typedef struct nfs_user_info { + uid_t uid; + gid_t gids[NFS_NGROUPS]; + int ngrps; + gf_lkowner_t lk_owner; + char identifier[UNIX_PATH_MAX]; /* ip of user */ +} nfs_user_t; + +extern int +nfs_user_root_create(nfs_user_t *newnfu); + +extern int +nfs_user_create(nfs_user_t *newnfu, uid_t uid, gid_t gid, + rpc_transport_t *trans, gid_t *auxgids, int auxcount); + +extern void +nfs_request_user_init(nfs_user_t *nfu, rpcsvc_request_t *req); + +extern void +nfs_request_primary_user_init(nfs_user_t *nfu, rpcsvc_request_t *req, uid_t uid, + gid_t gid); +extern int +nfs_subvolume_started(struct nfs_state *nfs, xlator_t *xl); + +extern void +nfs_fix_groups(xlator_t *this, call_stack_t *root); +#endif diff --git a/xlators/nfs/server/src/nfs3-fh.c b/xlators/nfs/server/src/nfs3-fh.c new file mode 100644 index 00000000000..caa3cfa6995 --- /dev/null +++ b/xlators/nfs/server/src/nfs3-fh.c @@ -0,0 +1,186 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include "xdr-nfs3.h" +#include "msg-nfs3.h" +#include <glusterfs/iobuf.h> +#include "nfs3-fh.h" +#include "nfs-common.h" +#include <glusterfs/iatt.h> +#include <glusterfs/common-utils.h> +#include "nfs-messages.h" + +int +nfs3_fh_validate(struct nfs3_fh *fh) +{ + if (!fh) + return 0; + + if (fh->ident[0] != GF_NFSFH_IDENT0) + return 0; + + if (fh->ident[1] != GF_NFSFH_IDENT1) + return 0; + + if (fh->ident[2] != GF_NFSFH_IDENT2) + return 0; + + if (fh->ident[3] != GF_NFSFH_IDENT3) + return 0; + + return 1; +} + +void +nfs3_fh_init(struct nfs3_fh *fh, struct iatt *buf) +{ + if ((!fh) || (!buf)) + return; + + fh->ident[0] = GF_NFSFH_IDENT0; + fh->ident[1] = GF_NFSFH_IDENT1; + fh->ident[2] = GF_NFSFH_IDENT2; + fh->ident[3] = GF_NFSFH_IDENT3; + + gf_uuid_copy(fh->gfid, buf->ia_gfid); +} + +struct nfs3_fh +nfs3_fh_build_indexed_root_fh(xlator_list_t *cl, xlator_t *xl) +{ + struct nfs3_fh fh = { + {0}, + }; + struct iatt buf = { + 0, + }; + static uuid_t root = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + if ((!cl) || (!xl)) + return fh; + + gf_uuid_copy(buf.ia_gfid, root); + nfs3_fh_init(&fh, &buf); + fh.exportid[15] = nfs_xlator_to_xlid(cl, xl); + + return fh; +} + +struct nfs3_fh +nfs3_fh_build_uuid_root_fh(uuid_t volumeid, uuid_t mountid) +{ + struct nfs3_fh fh = { + {0}, + }; + struct iatt buf = { + 0, + }; + static uuid_t root = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + gf_uuid_copy(buf.ia_gfid, root); + nfs3_fh_init(&fh, &buf); + gf_uuid_copy(fh.exportid, volumeid); + gf_uuid_copy(fh.mountid, mountid); + + return fh; +} + +int +nfs3_fh_is_root_fh(struct nfs3_fh *fh) +{ + static uuid_t rootgfid = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + if (!fh) + return 0; + + if (gf_uuid_compare(fh->gfid, rootgfid) == 0) + return 1; + + return 0; +} + +void +nfs3_fh_to_str(struct nfs3_fh *fh, char *str, size_t len) +{ + char gfid[GF_UUID_BUF_SIZE]; + char exportid[GF_UUID_BUF_SIZE]; + char mountid[GF_UUID_BUF_SIZE]; + + if ((!fh) || (!str)) + return; + + snprintf(str, len, "FH: exportid %s, gfid %s, mountid %s", + uuid_utoa_r(fh->exportid, exportid), uuid_utoa_r(fh->gfid, gfid), + uuid_utoa_r(fh->mountid, mountid)); +} + +void +nfs3_log_fh(struct nfs3_fh *fh) +{ + char gfidstr[512]; + char exportidstr[512]; + + if (!fh) + return; + + gf_msg_trace("nfs3-fh", 0, "filehandle: exportid 0x%s, gfid 0x%s", + uuid_utoa_r(fh->exportid, exportidstr), + uuid_utoa_r(fh->gfid, gfidstr)); +} + +int +nfs3_fh_build_parent_fh(struct nfs3_fh *child, struct iatt *newstat, + struct nfs3_fh *newfh) +{ + if ((!child) || (!newstat) || (!newfh)) + return -1; + + nfs3_fh_init(newfh, newstat); + gf_uuid_copy(newfh->exportid, child->exportid); + return 0; +} + +int +nfs3_build_fh(inode_t *inode, uuid_t exportid, struct nfs3_fh *newfh) +{ + if (!newfh || !inode) + return -1; + + newfh->ident[0] = GF_NFSFH_IDENT0; + newfh->ident[1] = GF_NFSFH_IDENT1; + newfh->ident[2] = GF_NFSFH_IDENT2; + newfh->ident[3] = GF_NFSFH_IDENT3; + gf_uuid_copy(newfh->gfid, inode->gfid); + gf_uuid_copy(newfh->exportid, exportid); + /*gf_uuid_copy (newfh->mountid, mountid);*/ + return 0; +} + +int +nfs3_fh_build_child_fh(struct nfs3_fh *parent, struct iatt *newstat, + struct nfs3_fh *newfh) +{ + if ((!parent) || (!newstat) || (!newfh)) + return -1; + + nfs3_fh_init(newfh, newstat); + gf_uuid_copy(newfh->exportid, parent->exportid); + gf_uuid_copy(newfh->mountid, parent->mountid); + return 0; +} + +uint32_t +nfs3_fh_compute_size() +{ + return GF_NFSFH_STATIC_SIZE; +} diff --git a/xlators/nfs/server/src/nfs3-fh.h b/xlators/nfs/server/src/nfs3-fh.h new file mode 100644 index 00000000000..cd7916b892d --- /dev/null +++ b/xlators/nfs/server/src/nfs3-fh.h @@ -0,0 +1,101 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS_FH_H_ +#define _NFS_FH_H_ + +#include <glusterfs/xlator.h> +#include "xdr-nfs3.h" +#include <glusterfs/iatt.h> +#include <sys/types.h> +#include <glusterfs/compat-uuid.h> + +/* BIG FAT WARNING: The file handle code is tightly coupled to NFSv3 file + * handles for now. This will change if and when we need v4. */ +#define GF_NFSFH_IDENT0 ':' +#define GF_NFSFH_IDENT1 'O' +#define GF_NFSFH_IDENT2 'G' +#define GF_NFSFH_IDENT3 'L' +#define GF_NFSFH_IDENT_SIZE (sizeof(char) * 4) +#define GF_NFSFH_STATIC_SIZE (GF_NFSFH_IDENT_SIZE + (3 * sizeof(uuid_t))) + +#define nfs3_fh_exportid_to_index(exprtid) ((uint16_t)exprtid[15]) +/* ATTENTION: Change in size of the structure below should be reflected in the + * GF_NFSFH_STATIC_SIZE. + */ +struct nfs3_fh { + /* Used to ensure that a bunch of bytes are actually a GlusterFS NFS + * file handle. Should contain ":OGL" + */ + char ident[4]; + + /* UUID that identifies an export. The value stored in exportid + * depends on the usage of gluster nfs. If the DVM is enabled using + * the nfs.dynamic-volumes option then exportid will contain the UUID + * of the volume so that gnfs is able to identify volumes uniquely + * through volume additions,deletions,migrations, etc. + * + * When not using dvm, exportid contains the index of the volume + * based on the position of the volume in the list of subvolumes + * for gnfs. + */ + uuid_t exportid; + + /* File/dir gfid. */ + uuid_t gfid; + uuid_t mountid; + /* This structure must be exactly NFS3_FHSIZE (64) bytes long. + Having the structure shorter results in buffer overflows + during XDR decoding. + */ + unsigned char padding[NFS3_FHSIZE - GF_NFSFH_STATIC_SIZE]; +} __attribute__((__packed__)); + +#define GF_NFS3FH_STATIC_INITIALIZER \ + { \ + {0}, \ + } + +extern uint32_t +nfs3_fh_compute_size(); + +extern uint16_t +nfs3_fh_hash_entry(uuid_t gfid); + +extern int +nfs3_fh_validate(struct nfs3_fh *fh); + +extern struct nfs3_fh +nfs3_fh_build_indexed_root_fh(xlator_list_t *cl, xlator_t *xl); + +extern int +nfs3_fh_is_root_fh(struct nfs3_fh *fh); + +extern int +nfs3_fh_build_child_fh(struct nfs3_fh *parent, struct iatt *newstat, + struct nfs3_fh *newfh); + +extern void +nfs3_log_fh(struct nfs3_fh *fh); + +extern void +nfs3_fh_to_str(struct nfs3_fh *fh, char *str, size_t len); + +extern int +nfs3_fh_build_parent_fh(struct nfs3_fh *child, struct iatt *newstat, + struct nfs3_fh *newfh); + +extern struct nfs3_fh +nfs3_fh_build_uuid_root_fh(uuid_t volumeid, uuid_t mountid); + +extern int +nfs3_build_fh(inode_t *inode, uuid_t exportid, struct nfs3_fh *newfh); + +#endif diff --git a/xlators/nfs/server/src/nfs3-helpers.c b/xlators/nfs/server/src/nfs3-helpers.c new file mode 100644 index 00000000000..897fb42b071 --- /dev/null +++ b/xlators/nfs/server/src/nfs3-helpers.c @@ -0,0 +1,3917 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include <inttypes.h> + +#include <glusterfs/xlator.h> +#include "nfs3.h" +#include "nfs3-fh.h" +#include "msg-nfs3.h" +#include <glusterfs/rbthash.h> +#include "nfs-fops.h" +#include "nfs-inodes.h" +#include "nfs-generics.h" +#include "nfs3-helpers.h" +#include "nfs-mem-types.h" +#include <glusterfs/iatt.h> +#include <glusterfs/common-utils.h> +#include "nfs-messages.h" +#include "mount3.h" +#include <string.h> + +extern int +nfs3_set_root_looked_up(struct nfs3_state *nfs3, struct nfs3_fh *rootfh); + +extern int +nfs3_is_root_looked_up(struct nfs3_state *nfs3, struct nfs3_fh *rootfh); + +#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, "Is 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"}, + {NFS3ERR_END_OF_LIST, "IO Error"}, + +}; + +uint64_t +nfs3_iatt_gfid_to_ino(struct iatt *buf) +{ + uint64_t ino = 0; + + if (!buf) + return 0; + + if (gf_nfs_enable_ino32()) { + ino = (uint32_t)nfs_hash_gfid(buf->ia_gfid); + goto hashout; + } + + /* from posix its guaranteed to send unique ino */ + ino = buf->ia_ino; + +hashout: + return ino; +} + +void +nfs3_map_deviceid_to_statdev(struct iatt *ia, uint64_t deviceid) +{ + if (!ia) + return; + + ia->ia_dev = deviceid; +} + +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 ENOTSUP: + case ENOSYS: + stat = NFS3ERR_NOTSUPP; + break; + + case EBADF: + stat = NFS3ERR_BADTYPE; + break; + + case ESTALE: + stat = NFS3ERR_STALE; + break; + + case ENOTCONN: + stat = NFS3ERR_IO; + break; + + case EDQUOT: + stat = NFS3ERR_DQUOT; + break; + + default: + stat = NFS3ERR_SERVERFAULT; + break; + } + + return stat; +} + +/* + * Special case: If op_ret is -1, it's very unusual op_errno being + * 0 which means something came wrong from upper layer(s). If it + * happens by any means, then set NFS3 status to NFS3ERR_SERVERFAULT. + */ +nfsstat3 +nfs3_cbk_errno_status(int32_t op_ret, int32_t op_errno) +{ + if ((op_ret == -1) && (op_errno == 0)) { + return NFS3ERR_SERVERFAULT; + } + + return nfs3_errno_to_nfsstat3(op_errno); +} + +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; + } +} + +void +nfs3_stat_to_fattr3(struct iatt *buf, fattr3 *fa) +{ + if (buf == NULL || fa == NULL) { + errno = EINVAL; + return; + } + + 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 = nfs3_iatt_gfid_to_ino(buf); + + fa->atime.seconds = buf->ia_atime; + fa->atime.nseconds = buf->ia_atime_nsec; + + fa->ctime.seconds = buf->ia_ctime; + fa->ctime.nseconds = buf->ia_ctime_nsec; + + fa->mtime.seconds = buf->ia_mtime; + fa->mtime.nseconds = buf->ia_mtime_nsec; +} + +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 (gf_is_zero_filled_stat(buf)) + goto out; + + nfs3_stat_to_fattr3(buf, &(attr.post_op_attr_u.attributes)); + 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 (gf_is_zero_filled_stat(pre)) + goto out; + + poa.attributes_follow = TRUE; + poa.pre_op_attr_u.attributes.size = pre->ia_size; + poa.pre_op_attr_u.attributes.mtime.seconds = pre->ia_mtime; + poa.pre_op_attr_u.attributes.mtime.nseconds = pre->ia_mtime_nsec; + poa.pre_op_attr_u.attributes.ctime.seconds = pre->ia_ctime; + poa.pre_op_attr_u.attributes.ctime.nseconds = pre->ia_ctime_nsec; + +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(); + res->lookup3res_u.resok.object.data.data_len = fhlen; + } + + obj.attributes_follow = FALSE; + dir.attributes_follow = FALSE; + obj = nfs3_stat_to_post_op_attr(buf); + 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, + uint64_t deviceid) +{ + memset(res, 0, sizeof(*res)); + nfs3_map_deviceid_to_statdev(buf, deviceid); + nfs3_map_deviceid_to_statdev(postparent, deviceid); + 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, + uint64_t deviceid) +{ + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(buf, deviceid); + nfs3_stat_to_fattr3(buf, &(res->getattr3res_u.resok.obj_attributes)); +} + +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, uint64_t deviceid) +{ + fsinfo3resok resok = { + {0}, + }; + nfstime3 tdelta = GF_NFS3_TIMEDELTA_SECS; + + memset(res, 0, sizeof(*res)); + res->status = status; + if (status != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(fsroot, deviceid); + 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_MAXFILESIZE; + 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 != NFS3ERR_END_OF_LIST; 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; +} + +#define POSIX_READ 4 +#define POSIX_WRITE 2 +#define POSIX_EXEC 1 + +uint32_t +nfs3_accessbits(int32_t accbits) +{ + uint32_t accresult = 0; + + if (accbits & POSIX_READ) + accresult |= ACCESS3_READ; + + if (accbits & POSIX_WRITE) + accresult |= (ACCESS3_MODIFY | ACCESS3_EXTEND | ACCESS3_DELETE); + + /* lookup on directory allowed only in case of execute permission */ + if (accbits & POSIX_EXEC) + accresult |= (ACCESS3_EXECUTE | ACCESS3_LOOKUP); + + return accresult; +} + +uint32_t +nfs3_request_to_accessbits(int32_t accbits) +{ + uint32_t acc_request = 0; + + if (accbits & ACCESS3_READ) + acc_request |= POSIX_READ; + + if (accbits & (ACCESS3_MODIFY | ACCESS3_EXTEND | ACCESS3_DELETE)) + acc_request |= POSIX_WRITE; + + /* For lookup on directory check for execute permission */ + if (accbits & (ACCESS3_EXECUTE | ACCESS3_LOOKUP)) + acc_request |= POSIX_EXEC; + + return acc_request; +} +void +nfs3_fill_access3res(access3res *res, nfsstat3 status, int32_t accbits, + int32_t reqaccbits) +{ + uint32_t accres = 0; + + memset(res, 0, sizeof(*res)); + res->status = status; + if (status != NFS3_OK) + return; + + accres = nfs3_accessbits(accbits); + + /* do not answer what was not asked */ + res->access3res_u.resok.access = accres & reqaccbits; +} + +void +nfs3_prep_readdir3args(readdir3args *ra, struct nfs3_fh *fh) +{ + memset(ra, 0, sizeof(*ra)); + ra->dir.data.data_val = (void *)fh; +} + +int +nfs3_is_dot_entry(char *entry) +{ + int ret = 0; + + if (!entry) + return 0; + + if (strcmp(entry, ".") == 0) + ret = 1; + + return ret; +} + +int +nfs3_is_parentdir_entry(char *entry) +{ + int ret = 0; + + if (!entry) + return 0; + + if (strcmp(entry, "..") == 0) + ret = 1; + + return ret; +} + +void +nfs3_funge_root_dotdot_dirent(gf_dirent_t *ent, struct nfs3_fh *dfh) +{ + if ((!ent) || (!dfh)) + return; + + if (nfs3_fh_is_root_fh(dfh) && nfs3_is_parentdir_entry(ent->d_name)) { + ent->d_ino = 1; + ent->d_stat.ia_ino = 1; + } + + if (nfs3_fh_is_root_fh(dfh) && nfs3_is_dot_entry(ent->d_name)) { + ent->d_ino = 1; + ent->d_stat.ia_ino = 1; + } +} + +entry3 * +nfs3_fill_entry3(gf_dirent_t *entry, struct nfs3_fh *dfh) +{ + entry3 *ent = NULL; + int name_len = 0; + if ((!entry) || (!dfh)) + return NULL; + + ent = GF_CALLOC(1, sizeof(*ent), gf_nfs_mt_entry3); + if (!ent) + return NULL; + + gf_msg_trace(GF_NFS3, 0, "Entry: %s", entry->d_name); + + /* If the entry is . or .., we need to replace the physical ino and gen + * with 1 and 0 respectively if the directory is root. This funging is + * needed because there is no parent directory of the root. In that + * sense the behavior we provide is similar to the output of the + * command: "stat /.." + */ + entry->d_ino = nfs3_iatt_gfid_to_ino(&entry->d_stat); + nfs3_funge_root_dotdot_dirent(entry, dfh); + ent->fileid = entry->d_ino; + ent->cookie = entry->d_off; + name_len = strlen(entry->d_name); + ent->name = GF_MALLOC(name_len + 1, gf_nfs_mt_char); + if (!ent->name) { + GF_FREE(ent); + ent = NULL; + goto err; + } + strcpy(ent->name, entry->d_name); + ent->name[name_len] = '\0'; + +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(); + 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 = GF_MALLOC(sizeof(*fh), gf_nfs_mt_char); + 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, uint64_t devid) +{ + entryp3 *ent = NULL; + struct nfs3_fh newfh = { + {0}, + }; + int name_len = 0; + + if ((!entry) || (!dirfh)) + return NULL; + + /* If the entry is . or .., we need to replace the physical ino and gen + * with 1 and 0 respectively if the directory is root. This funging is + * needed because there is no parent directory of the root. In that + * sense the behavior we provide is similar to the output of the + * command: "stat /.." + */ + entry->d_ino = nfs3_iatt_gfid_to_ino(&entry->d_stat); + nfs3_funge_root_dotdot_dirent(entry, dirfh); + gf_msg_trace(GF_NFS3, 0, "Entry: %s, ino: %" PRIu64, entry->d_name, + entry->d_ino); + ent = GF_CALLOC(1, sizeof(*ent), gf_nfs_mt_entryp3); + if (!ent) + return NULL; + + ent->fileid = entry->d_ino; + ent->cookie = entry->d_off; + name_len = strlen(entry->d_name); + ent->name = GF_MALLOC(name_len + 1, gf_nfs_mt_char); + if (!ent->name) { + GF_FREE(ent); + ent = NULL; + goto err; + } + strcpy(ent->name, entry->d_name); + ent->name[name_len] = '\0'; + + nfs3_fh_build_child_fh(dirfh, &entry->d_stat, &newfh); + nfs3_map_deviceid_to_statdev(&entry->d_stat, devid); + /* * + * In tier volume, the readdirp send only to cold subvol + * which will populate in the 'T' file entries in the result. + * For such files an explicit stat call is required, by setting + * following argument client will perform the same. + * + * The inode value for 'T' files and directory is NULL, so just + * skip the check if it is directory. + */ + if (!(IA_ISDIR(entry->d_stat.ia_type)) && (entry->inode == NULL)) + ent->name_attributes.attributes_follow = FALSE; + else + 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, struct nfs3_fh *dirfh, + uint64_t cverf, struct iatt *dirstat, + gf_dirent_t *entries, count3 count, int is_eof, + uint64_t deviceid) +{ + 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_deviceid_to_statdev(dirstat, deviceid); + 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, dirfh); + 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, uint64_t deviceid) +{ + 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_deviceid_to_statdev(dirstat, deviceid); + 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, deviceid); + 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; + GF_FREE(ent->name); + GF_FREE(ent->name_handle.post_op_fh3_u.handle.data.data_val); + GF_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; + GF_FREE(ent->name); + GF_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, uint64_t deviceid) +{ + post_op_attr poa; + fsstat3resok resok; + + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(postbuf, deviceid); + poa = nfs3_stat_to_post_op_attr(postbuf); + resok.tbytes = (size3)(fsbuf->f_frsize * fsbuf->f_blocks); + resok.fbytes = (size3)(fsbuf->f_frsize * fsbuf->f_bfree); + resok.abytes = (size3)(fsbuf->f_frsize * 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 = gf_time(); + } + + 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 = gf_time(); + } + + 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, uint64_t deviceid) +{ + 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_deviceid_to_statdev(newbuf, deviceid); + poa = nfs3_stat_to_post_op_attr(newbuf); + res->create3res_u.resok.obj_attributes = poa; + nfs3_map_deviceid_to_statdev(preparent, deviceid); + nfs3_map_deviceid_to_statdev(postparent, deviceid); + 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, uint64_t deviceid) +{ + wcc_data wcc; + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(preop, deviceid); + nfs3_map_deviceid_to_statdev(postop, deviceid); + 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, uint64_t deviceid) +{ + 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_deviceid_to_statdev(buf, deviceid); + poa = nfs3_stat_to_post_op_attr(buf); + nfs3_map_deviceid_to_statdev(preparent, deviceid); + nfs3_map_deviceid_to_statdev(postparent, deviceid); + 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, uint64_t deviceid) +{ + 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_deviceid_to_statdev(buf, deviceid); + poa = nfs3_stat_to_post_op_attr(buf); + nfs3_map_deviceid_to_statdev(postparent, deviceid); + nfs3_map_deviceid_to_statdev(preparent, deviceid); + 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, uint64_t deviceid) +{ + post_op_attr poa; + + memset(res, 0, sizeof(*res)); + res->status = stat; + + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(buf, deviceid); + 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, uint64_t deviceid) +{ + 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_deviceid_to_statdev(buf, deviceid); + poa = nfs3_stat_to_post_op_attr(buf); + nfs3_map_deviceid_to_statdev(preparent, deviceid); + nfs3_map_deviceid_to_statdev(postparent, deviceid); + 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, uint64_t deviceid) +{ + wcc_data dirwcc; + + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(preparent, deviceid); + nfs3_map_deviceid_to_statdev(postparent, deviceid); + 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, uint64_t deviceid) +{ + wcc_data dirwcc; + memset(res, 0, sizeof(*res)); + res->status = stat; + + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(postparent, deviceid); + nfs3_map_deviceid_to_statdev(preparent, deviceid); + 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, + uint64_t deviceid) +{ + post_op_attr poa; + wcc_data dirwcc; + + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(preparent, deviceid); + nfs3_map_deviceid_to_statdev(postparent, deviceid); + nfs3_map_deviceid_to_statdev(buf, deviceid); + 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, + uint64_t deviceid) + +{ + wcc_data dirwcc; + + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(preoldparent, deviceid); + nfs3_map_deviceid_to_statdev(postoldparent, deviceid); + nfs3_map_deviceid_to_statdev(prenewparent, deviceid); + nfs3_map_deviceid_to_statdev(postnewparent, deviceid); + nfs3_map_deviceid_to_statdev(buf, deviceid); + 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, uint64_t deviceid) +{ + write3resok resok; + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(prestat, deviceid); + nfs3_map_deviceid_to_statdev(poststat, deviceid); + 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, + uint64_t deviceid) +{ + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(poststat, deviceid); + nfs3_map_deviceid_to_statdev(prestat, deviceid); + 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, uint64_t deviceid) +{ + post_op_attr poa; + + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(poststat, deviceid); + 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, + uint64_t deviceid) +{ + pathconf3resok resok; + + memset(res, 0, sizeof(*res)); + res->status = stat; + if (stat != NFS3_OK) + return; + + nfs3_map_deviceid_to_statdev(buf, deviceid); + 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_msg_trace(GF_NFS3, 0, + "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_msg_trace(GF_NFS3, 0, "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_msg_trace(GF_NFS3, 0, "Cookie verified"); + if (stat) + *stat = NFS3_OK; + ret = 0; +err: + return ret; +} + +void +nfs3_stat_to_errstr(uint32_t xid, char *op, nfsstat3 stat, int pstat, + char *errstr, size_t len) +{ + if ((!op) || (!errstr)) + return; + + snprintf(errstr, len, "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]; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + + nfs3_fh_to_str(fh, fhstr, sizeof(fhstr)); + gf_msg_debug(GF_NFS3, 0, "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]; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + nfs3_fh_to_str(fh, fhstr, sizeof(fhstr)); + gf_msg_debug(GF_NFS3, 0, "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]; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + nfs3_fh_to_str(src, sfhstr, sizeof(sfhstr)); + nfs3_fh_to_str(dst, dfhstr, sizeof(dfhstr)); + gf_msg_debug(GF_NFS3, 0, + "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"; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + nfs3_fh_to_str(fh, fhstr, sizeof(fhstr)); + if (mode == EXCLUSIVE) + modestr = exclmode; + else if (mode == GUARDED) + modestr = guarded; + else + modestr = unchkd; + + gf_msg_debug(GF_NFS3, 0, + "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"; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + nfs3_fh_to_str(fh, fhstr, sizeof(fhstr)); + if (type == NF3CHR) + modestr = chr; + else if (type == NF3BLK) + modestr = blk; + else if (type == NF3SOCK) + modestr = sock; + else + modestr = fifo; + + gf_msg_debug(GF_NFS3, 0, + "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]; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + nfs3_fh_to_str(fh, fhstr, sizeof(fhstr)); + gf_msg_debug(GF_NFS3, 0, + "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]; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + nfs3_fh_to_str(fh, dfhstr, sizeof(dfhstr)); + nfs3_fh_to_str(tgt, tfhstr, sizeof(tfhstr)); + gf_msg_debug(GF_NFS3, 0, + "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]; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + nfs3_fh_to_str(fh, fhstr, sizeof(fhstr)); + if (stablewrite == -1) + gf_msg_debug(GF_NFS3, 0, + "XID: %x, %s: args: %s, offset:" + " %" PRIu64 ", count: %" PRIu32, + xid, op, fhstr, offt, count); + else + gf_msg_debug(GF_NFS3, 0, + "XID: %x, %s: args: %s, offset:" + " %" PRIu64 ", count: %" PRIu32 ", %s", + xid, op, fhstr, offt, count, + (stablewrite == UNSTABLE) ? "UNSTABLE" : "STABLE"); +} + +int +nfs3_getattr_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_PERM: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ACCES: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_setattr_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_lookup_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_PERM: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ACCES: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_access_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_readlink_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_read_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_write_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_create_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_mkdir_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_symlink_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_mknod_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_remove_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_rmdir_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_rename_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_link_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_readdir_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +int +nfs3_fsstat_loglevel(nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (stat) { + case NFS3ERR_PERM: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOENT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ACCES: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_EXIST: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_XDEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NODEV: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_IO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NXIO: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ISDIR: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_INVAL: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOSPC: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_ROFS: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_FBIG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_MLINK: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NAMETOOLONG: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTEMPTY: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_SERVERFAULT: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_NOTSUPP: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_BADHANDLE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_STALE: + ll = GF_LOG_WARNING; + break; + + case NFS3ERR_DQUOT: + ll = GF_LOG_WARNING; + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +struct nfs3op_str { + int op; + char str[100]; +}; + +struct nfs3op_str nfs3op_strings[] = { + {NFS3_NULL, "NULL"}, {NFS3_GETATTR, "GETATTR"}, + {NFS3_SETATTR, "SETATTR"}, {NFS3_LOOKUP, "LOOKUP"}, + {NFS3_ACCESS, "ACCESS"}, {NFS3_READLINK, "READLINK"}, + {NFS3_READ, "READ"}, {NFS3_WRITE, "WRITE"}, + {NFS3_CREATE, "CREATE"}, {NFS3_MKDIR, "MKDIR"}, + {NFS3_SYMLINK, "SYMLINK"}, {NFS3_MKNOD, "MKNOD"}, + {NFS3_REMOVE, "REMOVE"}, {NFS3_RMDIR, "RMDIR"}, + {NFS3_RENAME, "RENAME"}, {NFS3_LINK, "LINK"}, + {NFS3_READDIR, "READDIR"}, {NFS3_READDIRP, "READDIRP"}, + {NFS3_FSSTAT, "FSSTAT"}, {NFS3_FSINFO, "FSINFO"}, + {NFS3_PATHCONF, "PATHCONF"}, {NFS3_COMMIT, "COMMIT"}, +}; + +int +nfs3_loglevel(int nfs_op, nfsstat3 stat) +{ + int ll = GF_LOG_DEBUG; + + switch (nfs_op) { + case NFS3_GETATTR: + ll = nfs3_getattr_loglevel(stat); + break; + + case NFS3_SETATTR: + ll = nfs3_setattr_loglevel(stat); + break; + + case NFS3_LOOKUP: + ll = nfs3_lookup_loglevel(stat); + break; + + case NFS3_ACCESS: + ll = nfs3_access_loglevel(stat); + break; + + case NFS3_READLINK: + ll = nfs3_readlink_loglevel(stat); + break; + + case NFS3_READ: + ll = nfs3_read_loglevel(stat); + break; + + case NFS3_WRITE: + ll = nfs3_write_loglevel(stat); + break; + + case NFS3_CREATE: + ll = nfs3_create_loglevel(stat); + break; + + case NFS3_MKDIR: + ll = nfs3_mkdir_loglevel(stat); + break; + + case NFS3_SYMLINK: + ll = nfs3_symlink_loglevel(stat); + break; + + case NFS3_MKNOD: + ll = nfs3_mknod_loglevel(stat); + break; + + case NFS3_REMOVE: + ll = nfs3_remove_loglevel(stat); + break; + + case NFS3_RMDIR: + ll = nfs3_rmdir_loglevel(stat); + break; + + case NFS3_RENAME: + ll = nfs3_rename_loglevel(stat); + break; + + case NFS3_LINK: + ll = nfs3_link_loglevel(stat); + break; + + case NFS3_READDIR: + ll = nfs3_readdir_loglevel(stat); + break; + + case NFS3_READDIRP: + ll = nfs3_readdir_loglevel(stat); + break; + + case NFS3_FSSTAT: + ll = nfs3_fsstat_loglevel(stat); + break; + + case NFS3_FSINFO: + ll = nfs3_fsstat_loglevel(stat); + break; + + case NFS3_PATHCONF: + ll = nfs3_fsstat_loglevel(stat); + break; + + case NFS3_COMMIT: + ll = nfs3_write_loglevel(stat); + break; + + default: + ll = GF_LOG_DEBUG; + break; + } + + return ll; +} + +void +nfs3_log_common_res(uint32_t xid, int op, nfsstat3 stat, int pstat, + const char *path) +{ + char errstr[1024]; + int ll = nfs3_loglevel(op, stat); + + if (THIS->ctx->log.loglevel < ll) + return; + nfs3_stat_to_errstr(xid, nfs3op_strings[op].str, stat, pstat, errstr, + sizeof(errstr)); + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, "%s => (%s)", path, errstr); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, "%s => (%s)", path, + errstr); +} + +void +nfs3_log_readlink_res(uint32_t xid, nfsstat3 stat, int pstat, char *linkpath, + const char *path) +{ + char errstr[1024]; + int ll = nfs3_loglevel(NFS3_READLINK, stat); + + if (THIS->ctx->log.loglevel < ll) + return; + + nfs3_stat_to_errstr(xid, "READLINK", stat, pstat, errstr, sizeof(errstr)); + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, "%s => (%s), target: %s", path, errstr, + linkpath); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, "%s => (%s) target: %s", + path, errstr, linkpath); +} + +void +nfs3_log_read_res(uint32_t xid, nfsstat3 stat, int pstat, count3 count, + int is_eof, struct iovec *vec, int32_t veccount, + const char *path) +{ + char errstr[1024]; + int ll = GF_LOG_DEBUG; + + ll = nfs3_loglevel(NFS3_READ, stat); + if (THIS->ctx->log.loglevel < ll) + return; + nfs3_stat_to_errstr(xid, "READ", stat, pstat, errstr, sizeof(errstr)); + if (vec) + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, + "%s => (%s), count: %" PRIu32 + ", is_eof:" + " %d, vector: count: %d, len: %zd", + path, errstr, count, is_eof, veccount, vec->iov_len); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, + "%s => (%s), count: %" PRIu32 + ", is_eof:" + " %d, vector: count: %d, len: %zd", + path, errstr, count, is_eof, veccount, vec->iov_len); + else if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, + "%s => (%s), count: %" PRIu32 + ", is_eof:" + " %d", + path, errstr, count, is_eof); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, + "%s => (%s), count: %" PRIu32 + ", is_eof:" + " %d", + path, errstr, count, is_eof); +} + +void +nfs3_log_write_res(uint32_t xid, nfsstat3 stat, int pstat, count3 count, + int stable, uint64_t wverf, const char *path) +{ + char errstr[1024]; + int ll = nfs3_loglevel(NFS3_WRITE, stat); + + if (THIS->ctx->log.loglevel < ll) + return; + + nfs3_stat_to_errstr(xid, "WRITE", stat, pstat, errstr, sizeof(errstr)); + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, + "%s => (%s), count: %" PRIu32 + ", %s,wverf: " + "%" PRIu64, + path, errstr, count, + (stable == UNSTABLE) ? "UNSTABLE" : "STABLE", wverf); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, + "%s => (%s), count: %" PRIu32 ", %s,wverf: %" PRIu64, path, + errstr, count, (stable == UNSTABLE) ? "UNSTABLE" : "STABLE", + wverf); +} + +void +nfs3_log_newfh_res(uint32_t xid, int op, nfsstat3 stat, int pstat, + struct nfs3_fh *newfh, const char *path) +{ + char errstr[1024]; + char fhstr[1024]; + int ll = nfs3_loglevel(op, stat); + + if (THIS->ctx->log.loglevel < ll) + return; + nfs3_stat_to_errstr(xid, nfs3op_strings[op].str, stat, pstat, errstr, + sizeof(errstr)); + nfs3_fh_to_str(newfh, fhstr, sizeof(fhstr)); + + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, "%s => (%s), %s", path, errstr, fhstr); + else + gf_msg(GF_NFS3, nfs3_loglevel(op, stat), errno, NFS_MSG_STAT_ERROR, + "%s => (%s), %s", path, errstr, fhstr); +} + +void +nfs3_log_readdir_res(uint32_t xid, nfsstat3 stat, int pstat, uint64_t cverf, + count3 count, int is_eof, const char *path) +{ + char errstr[1024]; + int ll = nfs3_loglevel(NFS3_READDIR, stat); + + if (THIS->ctx->log.loglevel < ll) + return; + nfs3_stat_to_errstr(xid, "READDIR", stat, pstat, errstr, sizeof(errstr)); + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, + "%s => (%s), count: %" PRIu32 ", cverf: %" PRIu64 + ", is_eof: %d", + path, errstr, count, cverf, is_eof); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, + "%s => (%s), count: %" PRIu32 ", cverf: %" PRIu64 ", is_eof: %d", + path, 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, + const char *path) +{ + char errstr[1024]; + int ll = nfs3_loglevel(NFS3_READDIRP, stat); + + if (THIS->ctx->log.loglevel < ll) + return; + nfs3_stat_to_errstr(xid, "READDIRPLUS", stat, pstat, errstr, + sizeof(errstr)); + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, + "%s => (%s), dircount: %" PRIu32 ", maxcount: %" PRIu32 + ", cverf: %" PRIu64 ", is_eof: %d", + path, errstr, dircount, maxcount, cverf, is_eof); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, + "%s => (%s), dircount: %" PRIu32 ", maxcount: %" PRIu32 + ", cverf: %" PRIu64 ", is_eof: %d", + path, errstr, dircount, maxcount, cverf, is_eof); +} + +void +nfs3_log_commit_res(uint32_t xid, nfsstat3 stat, int pstat, uint64_t wverf, + const char *path) +{ + char errstr[1024]; + int ll = nfs3_loglevel(NFS3_COMMIT, stat); + + if (THIS->ctx->log.loglevel < ll) + return; + nfs3_stat_to_errstr(xid, "COMMIT", stat, pstat, errstr, sizeof(errstr)); + if (ll == GF_LOG_DEBUG) + gf_msg_debug(GF_NFS3, 0, "%s => (%s), wverf: %" PRIu64, path, errstr, + wverf); + else + gf_msg(GF_NFS3, ll, errno, NFS_MSG_STAT_ERROR, + "%s => (%s), wverf: %" PRIu64, path, errstr, wverf); +} + +void +nfs3_log_readdir_call(uint32_t xid, struct nfs3_fh *fh, count3 dircount, + count3 maxcount) +{ + char fhstr[1024]; + + if (THIS->ctx->log.loglevel < GF_LOG_DEBUG) + return; + + nfs3_fh_to_str(fh, fhstr, sizeof(fhstr)); + + if (maxcount == 0) + gf_msg_debug(GF_NFS3, 0, + "XID: %x, READDIR: args: %s," + " count: %d", + xid, fhstr, (uint32_t)dircount); + else + gf_msg_debug(GF_NFS3, 0, + "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_msg_trace(GF_NFS3, 0, "FH inode resolved"); + ret = nfs_inode_loc_fill(inode, &cs->resolvedloc, NFS_RESOLVE_EXIST); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_INODE_LOC_FILL_ERROR, + "inode loc fill failed"); + goto err; + } + + nfs3_call_resume(cs); + +err: + 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; + inode_t *linked_inode = NULL; + + cs = frame->local; + cs->resolve_ret = op_ret; + cs->resolve_errno = op_errno; + + if (op_ret == -1) { + if (op_errno == ENOENT) { + gf_msg_trace(GF_NFS3, 0, "Lookup failed: %s: %s", + cs->resolvedloc.path, strerror(op_errno)); + } else { + gf_msg(GF_NFS3, GF_LOG_ERROR, op_errno, NFS_MSG_LOOKUP_FAIL, + "Lookup failed: %s: %s", cs->resolvedloc.path, + strerror(op_errno)); + } + goto err; + } else + gf_msg_trace(GF_NFS3, 0, "Entry looked up: %s", cs->resolvedloc.path); + + memcpy(&cs->stbuf, buf, sizeof(*buf)); + memcpy(&cs->postparent, postparent, sizeof(*postparent)); + linked_inode = inode_link(inode, cs->resolvedloc.parent, + cs->resolvedloc.name, buf); + if (linked_inode) { + nfs_fix_generation(this, linked_inode); + inode_lookup(linked_inode); + inode_unref(cs->resolvedloc.inode); + cs->resolvedloc.inode = linked_inode; + } else { + /* nfs3_fh_resolve_entry_hard() use to resolve entire path if needed. + * So the ctx for inode obtained from here need to set properly, + * otherwise it may result in a crash. + */ + nfs_fix_generation(this, inode); + } +err: + nfs3_call_resume(cs); + return 0; +} + +int32_t +nfs3_fh_resolve_inode_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; + inode_t *linked_inode = NULL; + + cs = frame->local; + cs->resolve_ret = op_ret; + cs->resolve_errno = op_errno; + + if (op_ret == -1) { + if (op_errno == ENOENT) { + gf_msg_trace(GF_NFS3, 0, "Lookup failed: %s: %s", + cs->resolvedloc.path, strerror(op_errno)); + } else { + gf_msg(GF_NFS3, GF_LOG_ERROR, op_errno, NFS_MSG_LOOKUP_FAIL, + "Lookup failed: %s: %s", cs->resolvedloc.path, + strerror(op_errno)); + } + nfs3_call_resume(cs); + goto err; + } + + memcpy(&cs->stbuf, buf, sizeof(*buf)); + memcpy(&cs->postparent, buf, sizeof(*postparent)); + linked_inode = inode_link(inode, cs->resolvedloc.parent, + cs->resolvedloc.name, buf); + if (linked_inode) { + nfs_fix_generation(this, linked_inode); + inode_lookup(linked_inode); + inode_unref(cs->resolvedloc.inode); + cs->resolvedloc.inode = linked_inode; + } + + /* If it is an entry lookup and we landed in the callback for hard + * inode resolution, it means the parent inode was not available and + * had to be resolved first. Now that is done, lets head back into + * entry resolution. + */ + if (cs->resolventry) + nfs3_fh_resolve_entry_hard(cs); + else + nfs3_call_resume(cs); +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; + + gf_msg_trace(GF_NFS3, 0, "FH hard resolution for: gfid 0x%s", + uuid_utoa(cs->resolvefh.gfid)); + cs->hardresolved = 1; + nfs_loc_wipe(&cs->resolvedloc); + ret = nfs_gfid_loc_fill(cs->vol->itable, cs->resolvefh.gfid, + &cs->resolvedloc, NFS_RESOLVE_CREATE); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_INODE_LOC_FILL_ERROR, + "Failed to fill loc using gfid: " + "%s", + strerror(-ret)); + goto out; + } + + nfs_user_root_create(&nfu); + ret = nfs_lookup(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3_fh_resolve_inode_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, + }; + gf_boolean_t freshlookup = _gf_false; + + if (!cs) + return ret; + + nfs_loc_wipe(&cs->resolvedloc); + nfs_user_root_create(&nfu); + gf_msg_trace(GF_NFS3, 0, + "FH hard resolution: gfid: %s " + ", entry: %s", + uuid_utoa(cs->resolvefh.gfid), cs->resolventry); + + ret = nfs_entry_loc_fill(cs->nfsx, cs->vol->itable, cs->resolvefh.gfid, + cs->resolventry, &cs->resolvedloc, + NFS_RESOLVE_CREATE, &freshlookup); + + if (ret == -2) { + gf_msg_trace(GF_NFS3, 0, "Entry needs lookup: %s", + cs->resolvedloc.path); + /* If the NFS op is lookup, let the resume callback + * handle the sending of the lookup fop. Similarly, + * if the NFS op is create, let the create call + * go ahead in the resume callback so that an EEXIST gets + * handled at posix without an extra fop at this point. + */ + if (freshlookup && + (nfs3_lookup_op(cs) || + (nfs3_create_op(cs) && !nfs3_create_exclusive_op(cs)))) { + cs->lookuptype = GF_NFS3_FRESH; + cs->resolve_ret = 0; + cs->hardresolved = 0; + nfs3_call_resume(cs); + } else { + cs->hardresolved = 1; + nfs_lookup(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3_fh_resolve_entry_lookup_cbk, cs); + } + ret = 0; + } else if (ret == -1) { + gf_msg_trace(GF_NFS3, 0, "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; + xlator_t *this = NULL; + + if (!cs) + return ret; + + this = cs->nfsx; + gf_msg_trace(GF_NFS3, 0, "FH needs inode resolution"); + gf_uuid_copy(cs->resolvedloc.gfid, cs->resolvefh.gfid); + + inode = inode_find(cs->vol->itable, cs->resolvefh.gfid); + if (!inode || inode_ctx_get(inode, this, NULL)) + 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; + + return nfs3_fh_resolve_entry_hard(cs); +} + +int +nfs3_fh_resolve_resume(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + + if (!cs) + return ret; + + if (cs->resolve_ret < 0) + goto err_resume_call; + + if (!cs->resolventry) + ret = nfs3_fh_resolve_inode(cs); + else + ret = nfs3_fh_resolve_entry(cs); + +err_resume_call: + if (ret < 0) { + cs->resolve_ret = -1; + cs->resolve_errno = EFAULT; + nfs3_call_resume(cs); + ret = 0; + } + + return ret; +} + +int32_t +nfs3_fh_resolve_root_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_msg(GF_NFS3, GF_LOG_ERROR, op_errno, NFS_MSG_LOOKUP_ROOT_FAIL, + "Root lookup failed: %s", strerror(op_errno)); + goto err; + } else + gf_msg_trace(GF_NFS3, 0, "Root looked up: %s", cs->resolvedloc.path); + + nfs3_set_root_looked_up(cs->nfs3state, &cs->resolvefh); +err: + nfs3_fh_resolve_resume(cs); + return 0; +} + +int +nfs3_fh_resolve_root(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + + if (!cs) + return ret; + + if (nfs3_is_root_looked_up(cs->nfs3state, &cs->resolvefh)) { + ret = nfs3_fh_resolve_resume(cs); + goto out; + } + + nfs_user_root_create(&nfu); + gf_msg_trace(GF_NFS3, 0, "Root needs lookup"); + ret = nfs_root_loc_fill(cs->vol->itable, &cs->resolvedloc); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_LOOKUP_ROOT_FAIL, + "Failed to lookup root from itable: %s", strerror(-ret)); + goto out; + } + + ret = nfs_lookup(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3_fh_resolve_root_lookup_cbk, cs); + +out: + return ret; +} + +/** + * __nfs3_fh_auth_get_peer -- Get a peer name from the rpc request object + * + * @peer: Char * to write to + * @req : The request to get host/peer from + */ +int +__nfs3_fh_auth_get_peer(const rpcsvc_request_t *req, char *peer) +{ + struct sockaddr_storage sastorage = { + 0, + }; + rpc_transport_t *trans = NULL; + int ret = 0; + + /* Why do we pass in the peer here and then + * store it rather than malloc() and return a char * ? We want to avoid + * heap allocations in the IO path as much as possible for speed + * so we try to keep all allocations on the stack. + */ + trans = rpcsvc_request_transport(req); + ret = rpcsvc_transport_peeraddr(trans, peer, RPCSVC_PEER_STRLEN, &sastorage, + sizeof(sastorage)); + if (ret != 0) { + gf_msg(GF_NFS3, GF_LOG_WARNING, 0, NFS_MSG_GET_PEER_ADDR_FAIL, + "Failed to get peer addr: %s", gai_strerror(ret)); + } + return ret; +} + +/* + * nfs3_fh_auth_nfsop () -- Checks if an nfsop is authorized. + * + * @cs: The NFS call state containing all the relevant information + * + * @return: 0 if authorized + * -EACCES for completely unauthorized fop + * -EROFS for unauthorized write operations (rm, mkdir, write) + */ +int +nfs3_fh_auth_nfsop(nfs3_call_state_t *cs, gf_boolean_t is_write_op) +{ + struct nfs_state *nfs = NULL; + struct mount3_state *ms = NULL; + + nfs = (struct nfs_state *)cs->nfsx->private; + ms = (struct mount3_state *)nfs->mstate; + return mnt3_authenticate_request(ms, cs->req, &cs->resolvefh, NULL, NULL, + NULL, NULL, is_write_op); +} + +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; + + /* Check if the resolution is: + * a. fh resolution + * + * or + * + * b. (fh, basename) resolution + */ + if (entry) { /* b */ + cs->resolventry = gf_strdup(entry); + if (!cs->resolventry) + goto err; + } + + ret = nfs3_fh_resolve_root(cs); +err: + return ret; +} diff --git a/xlators/nfs/server/src/nfs3-helpers.h b/xlators/nfs/server/src/nfs3-helpers.h new file mode 100644 index 00000000000..aead636c831 --- /dev/null +++ b/xlators/nfs/server/src/nfs3-helpers.h @@ -0,0 +1,339 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS3_HELPER_H_ +#define _NFS3_HELPER_H_ + +#include <glusterfs/xlator.h> +#include "nfs3.h" +#include "nfs3-fh.h" +#include "msg-nfs3.h" +#include "xdr-nfs3.h" + +#include <sys/statvfs.h> + +#define GF_NFS3_FD_CACHED 0xcaced + +extern struct nfs3_fh +nfs3_extract_lookup_fh(lookup3args *args); + +extern char * +nfs3_extract_lookup_name(lookup3args *args); + +extern nfsstat3 +nfs3_errno_to_nfsstat3(int errnum); + +extern nfsstat3 nfs3_cbk_errno_status(int32_t, int32_t); + +extern void +nfs3_fill_lookup3res(lookup3res *res, nfsstat3 stat, struct nfs3_fh *newfh, + struct iatt *stbuf, struct iatt *postparent, + uint64_t deviceid); + +extern post_op_attr +nfs3_stat_to_post_op_attr(struct iatt *buf); + +extern struct nfs3_fh +nfs3_extract_getattr_fh(getattr3args *args); + +extern void +nfs3_fill_getattr3res(getattr3res *res, nfsstat3 stat, struct iatt *buf, + uint64_t deviceid); + +extern struct nfs3_fh +nfs3_extract_fsinfo_fh(fsinfo3args *args); + +extern void +nfs3_fill_fsinfo3res(struct nfs3_state *nfs3, fsinfo3res *res, nfsstat3 status, + struct iatt *fsroot, uint64_t deviceid); + +/* Functions containing _prep_ are used specifically to work around + * the memory allocations that happen inside Sun RPC library. + * In that library, there are numerous places where every NFS request + * can result in really tiny malloc calls. I fear the memory fragmentation + * that will follow. After studying the points at and the way in which malloc + * is called in Sun RPC, I've come up with this work-around. It is based on + * the idea that if the user/caller of the xdr_to_XXXXargs functions can provide + * already allocated memory or provide references to memory areas on its stack + * just for the short-term purpose of decoding the message from XDR format, we + * can avoid the memory allocations in Sun RPC. This is based on the fact + * that Sun RPC first checks whether structure members which require memory + * are NULL or not and only then calls malloc. In this case, if the caller + * provided references are non-NULL, then the if-branches containing malloc + * in Sun RPC will be avoided. + * PS: You're not expected to understand this unless you've spent some time + * looking through the glibc/sunrpc sources. + */ +extern void +nfs3_prep_lookup3args(lookup3args *args, struct nfs3_fh *fh, char *name); + +extern void +nfs3_prep_getattr3args(getattr3args *args, struct nfs3_fh *fh); + +extern void +nfs3_prep_fsinfo3args(fsinfo3args *args, struct nfs3_fh *root); + +extern char * +nfsstat3_strerror(int stat); + +extern void +nfs3_prep_access3args(access3args *args, struct nfs3_fh *fh); + +extern void +nfs3_fill_access3res(access3res *res, nfsstat3 status, int32_t accbits, + int32_t reqaccbits); + +extern char * +nfs3_fhcache_getpath(struct nfs3_state *nfs3, struct nfs3_fh *fh); + +extern int +nfs3_fhcache_putpath(struct nfs3_state *nfs3, struct nfs3_fh *fh, char *path); + +extern void +nfs3_prep_readdir3args(readdir3args *ra, struct nfs3_fh *fh); + +extern void +nfs3_fill_readdir3res(readdir3res *res, nfsstat3 stat, struct nfs3_fh *dfh, + uint64_t cverf, struct iatt *dirstat, + gf_dirent_t *entries, count3 count, int is_eof, + uint64_t deviceid); + +extern void +nfs3_prep_readdirp3args(readdirp3args *ra, struct nfs3_fh *fh); + +extern 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, uint64_t deviceid); + +extern void +nfs3_free_readdirp3res(readdirp3res *res); + +extern void +nfs3_free_readdir3res(readdir3res *res); + +extern void +nfs3_prep_fsstat3args(fsstat3args *args, struct nfs3_fh *fh); + +extern void +nfs3_fill_fsstat3res(fsstat3res *res, nfsstat3 stat, struct statvfs *fsbuf, + struct iatt *postbuf, uint64_t deviceid); + +extern int32_t +nfs3_sattr3_to_setattr_valid(sattr3 *sattr, struct iatt *buf, mode_t *omode); +extern void +nfs3_fill_create3res(create3res *res, nfsstat3 stat, struct nfs3_fh *newfh, + struct iatt *newbuf, struct iatt *preparent, + struct iatt *postparent, uint64_t deviceid); + +extern void +nfs3_prep_create3args(create3args *args, struct nfs3_fh *fh, char *name); + +extern void +nfs3_prep_setattr3args(setattr3args *args, struct nfs3_fh *fh); + +extern void +nfs3_fill_setattr3res(setattr3res *res, nfsstat3 stat, struct iatt *preop, + struct iatt *postop, uint64_t deviceid); + +extern void +nfs3_prep_mkdir3args(mkdir3args *args, struct nfs3_fh *dirfh, char *name); + +extern void +nfs3_fill_mkdir3res(mkdir3res *res, nfsstat3 stat, struct nfs3_fh *fh, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, uint64_t deviceid); + +extern void +nfs3_prep_symlink3args(symlink3args *args, struct nfs3_fh *dirfh, char *name, + char *target); + +extern void +nfs3_fill_symlink3res(symlink3res *res, nfsstat3 stat, struct nfs3_fh *fh, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, uint64_t deviceid); + +extern void +nfs3_prep_readlink3args(readlink3args *args, struct nfs3_fh *fh); + +extern void +nfs3_fill_readlink3res(readlink3res *res, nfsstat3 stat, char *path, + struct iatt *buf, uint64_t deviceid); + +extern void +nfs3_prep_mknod3args(mknod3args *args, struct nfs3_fh *fh, char *name); + +extern void +nfs3_fill_mknod3res(mknod3res *res, nfsstat3 stat, struct nfs3_fh *fh, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, uint64_t deviceid); + +extern void +nfs3_fill_remove3res(remove3res *res, nfsstat3 stat, struct iatt *preparent, + struct iatt *postparent, uint64_t deviceid); +extern void +nfs3_prep_remove3args(remove3args *args, struct nfs3_fh *fh, char *name); + +extern void +nfs3_fill_rmdir3res(rmdir3res *res, nfsstat3 stat, struct iatt *preparent, + struct iatt *postparent, uint64_t deviceid); + +extern void +nfs3_prep_rmdir3args(rmdir3args *args, struct nfs3_fh *fh, char *name); + +extern void +nfs3_fill_link3res(link3res *res, nfsstat3 stat, struct iatt *buf, + struct iatt *preparent, struct iatt *postparent, + uint64_t deviceid); + +extern void +nfs3_prep_link3args(link3args *args, struct nfs3_fh *target, + struct nfs3_fh *dirfh, char *name); + +extern void +nfs3_prep_rename3args(rename3args *args, struct nfs3_fh *olddirfh, + char *oldname, struct nfs3_fh *newdirfh, char *newname); + +extern void +nfs3_fill_rename3res(rename3res *res, nfsstat3 stat, struct iatt *buf, + struct iatt *preoldparent, struct iatt *postoldparent, + struct iatt *prenewparent, struct iatt *postnewparent, + uint64_t deviceid); + +extern void +nfs3_prep_write3args(write3args *args, struct nfs3_fh *fh); + +extern void +nfs3_fill_write3res(write3res *res, nfsstat3 stat, count3 count, + stable_how stable, uint64_t wverf, struct iatt *prestat, + struct iatt *poststat, uint64_t deviceid); + +extern void +nfs3_prep_commit3args(commit3args *args, struct nfs3_fh *fh); + +extern void +nfs3_fill_commit3res(commit3res *res, nfsstat3 stat, uint64_t wverf, + struct iatt *prestat, struct iatt *poststat, + uint64_t deviceid); + +extern void +nfs3_fill_read3res(read3res *res, nfsstat3 stat, count3 count, + struct iatt *poststat, int is_eof, uint64_t deviceid); + +extern void +nfs3_prep_read3args(read3args *args, struct nfs3_fh *fh); + +extern void +nfs3_prep_pathconf3args(pathconf3args *args, struct nfs3_fh *fh); + +extern void +nfs3_fill_pathconf3res(pathconf3res *res, nfsstat3 stat, struct iatt *buf, + uint64_t deviceid); + +extern int +nfs3_cached_inode_opened(xlator_t *nfsxl, inode_t *inode); + +extern void +nfs3_log_common_res(uint32_t xid, int op, nfsstat3 stat, int pstat, + const char *path); + +extern void +nfs3_log_readlink_res(uint32_t xid, nfsstat3 stat, int pstat, char *linkpath, + const char *path); + +extern void +nfs3_log_read_res(uint32_t xid, nfsstat3 stat, int pstat, count3 count, + int is_eof, struct iovec *vec, int32_t vcount, + const char *path); + +extern void +nfs3_log_write_res(uint32_t xid, nfsstat3 stat, int pstat, count3 count, + int stable, uint64_t wverf, const char *path); + +extern void +nfs3_log_newfh_res(uint32_t xid, int op, nfsstat3 stat, int pstat, + struct nfs3_fh *newfh, const char *path); + +extern void +nfs3_log_readdir_res(uint32_t xid, nfsstat3 stat, int pstat, uint64_t cverf, + count3 count, int is_eof, const char *path); + +extern void +nfs3_log_readdirp_res(uint32_t xid, nfsstat3 stat, int pstat, uint64_t cverf, + count3 dircount, count3 maxcount, int is_eof, + const char *path); + +extern void +nfs3_log_commit_res(uint32_t xid, nfsstat3 stat, int pstat, uint64_t wverf, + const char *path); + +extern void +nfs3_log_common_call(uint32_t xid, char *op, struct nfs3_fh *fh); + +extern void +nfs3_log_fh_entry_call(uint32_t xid, char *op, struct nfs3_fh *fh, char *name); + +extern void +nfs3_log_rw_call(uint32_t xid, char *op, struct nfs3_fh *fh, offset3 offt, + count3 count, int stablewrite); + +extern void +nfs3_log_create_call(uint32_t xid, struct nfs3_fh *fh, char *name, + createmode3 mode); + +extern void +nfs3_log_symlink_call(uint32_t xid, struct nfs3_fh *fh, char *name, char *tgt); + +extern void +nfs3_log_mknod_call(uint32_t xid, struct nfs3_fh *fh, char *name, int type); + +extern void +nfs3_log_rename_call(uint32_t xid, struct nfs3_fh *src, char *sname, + struct nfs3_fh *dst, char *dname); + +extern void +nfs3_log_link_call(uint32_t xid, struct nfs3_fh *fh, char *name, + struct nfs3_fh *tgt); + +extern void +nfs3_log_readdir_call(uint32_t xid, struct nfs3_fh *fh, count3 dircount, + count3 maxcount); + +extern int +nfs3_fh_resolve_entry_hard(nfs3_call_state_t *cs); + +extern int +nfs3_fh_resolve_inode(nfs3_call_state_t *cs); + +extern int +nfs3_fh_resolve_entry(nfs3_call_state_t *cs); + +extern int +nfs3_fh_resolve_and_resume(nfs3_call_state_t *cs, struct nfs3_fh *fh, + char *entry, nfs3_resume_fn_t resum_fn); + +extern int +nfs3_verify_dircookie(struct nfs3_state *nfs3, fd_t *dirfd, cookie3 cookie, + uint64_t cverf, nfsstat3 *stat); + +extern int +nfs3_is_parentdir_entry(char *entry); + +uint32_t +nfs3_request_to_accessbits(int32_t accbits); + +extern int +nfs3_fh_auth_nfsop(nfs3_call_state_t *cs, gf_boolean_t is_write_op); + +void +nfs3_map_deviceid_to_statdev(struct iatt *ia, uint64_t deviceid); + +#endif diff --git a/xlators/nfs/server/src/nfs3.c b/xlators/nfs/server/src/nfs3.c new file mode 100644 index 00000000000..f9042bc3b3f --- /dev/null +++ b/xlators/nfs/server/src/nfs3.c @@ -0,0 +1,5738 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include "mount3.h" +#include "xdr-nfs3.h" +#include "msg-nfs3.h" +#include <glusterfs/iobuf.h> +#include "nfs3.h" +#include <glusterfs/mem-pool.h> +#include <glusterfs/logging.h> +#include "nfs-common.h" +#include "nfs-fops.h" +#include "nfs-inodes.h" +#include "nfs-generics.h" +#include "nfs3-helpers.h" +#include "nfs-mem-types.h" +#include "nfs.h" +#include "xdr-rpc.h" +#include "xdr-generic.h" +#include "nfs-messages.h" +#include "glfs-internal.h" + +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/statvfs.h> +#include <time.h> + +#define nfs3_validate_strlen_or_goto(str, len, label, status, retval) \ + do { \ + if ((str)) { \ + if (strlen((str)) > (len)) { \ + gf_msg(GF_NFS3, GF_LOG_ERROR, ENAMETOOLONG, \ + NFS_MSG_STR_TOO_LONG, "strlen too long"); \ + status = NFS3ERR_NAMETOOLONG; \ + retval = -ENAMETOOLONG; \ + goto label; \ + } \ + } \ + } while (0); + +#define nfs3_validate_nfs3_state(request, state, status, label, retval) \ + do { \ + state = rpcsvc_request_program_private(request); \ + if (!state) { \ + gf_msg(GF_NFS3, GF_LOG_ERROR, EFAULT, NFS_MSG_STATE_MISSING, \ + "NFSv3 state " \ + "missing from RPC request"); \ + status = NFS3ERR_SERVERFAULT; \ + ret = -EFAULT; \ + goto label; \ + } \ + } while (0); + +struct nfs3_export * +__nfs3_get_export_by_index(struct nfs3_state *nfs3, uuid_t exportid) +{ + struct nfs3_export *exp = NULL; + int index = 0; + int searchindex = 0; + + searchindex = nfs3_fh_exportid_to_index(exportid); + list_for_each_entry(exp, &nfs3->exports, explist) + { + if (searchindex == index) + goto found; + + ++index; + } + + exp = NULL; + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_INDEX_NOT_FOUND, + "searchindex=%d not found", searchindex); +found: + return exp; +} + +struct nfs3_export * +__nfs3_get_export_by_volumeid(struct nfs3_state *nfs3, uuid_t exportid) +{ + struct nfs3_export *exp = NULL; + + list_for_each_entry(exp, &nfs3->exports, explist) + { + if (!gf_uuid_compare(exportid, exp->volumeid)) + goto found; + } + + exp = NULL; +found: + return exp; +} + +struct nfs3_export * +__nfs3_get_export_by_exportid(struct nfs3_state *nfs3, uuid_t exportid) +{ + struct nfs3_export *exp = NULL; + + if (!nfs3) + return exp; + + if (gf_nfs_dvm_off(nfs_state(nfs3->nfsx))) + exp = __nfs3_get_export_by_index(nfs3, exportid); + else + exp = __nfs3_get_export_by_volumeid(nfs3, exportid); + + return exp; +} + +int +nfs3_export_access(struct nfs3_state *nfs3, uuid_t exportid) +{ + int ret = GF_NFS3_VOLACCESS_RO; + struct nfs3_export *exp = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, nfs3, err); + + exp = __nfs3_get_export_by_exportid(nfs3, exportid); + + if (!exp) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_EXPORT_ID_FAIL, + "Failed to get export by ID"); + goto err; + } + + ret = exp->access; + +err: + return ret; +} + +#define nfs3_check_rw_volaccess(nfs3state, exid, status, label) \ + do { \ + if (nfs3_export_access(nfs3state, exid) != GF_NFS3_VOLACCESS_RW) { \ + gf_msg(GF_NFS3, GF_LOG_ERROR, EACCES, NFS_MSG_NO_RW_ACCESS, \ + "No read-write access"); \ + status = NFS3ERR_ROFS; \ + goto label; \ + } \ + } while (0) + +xlator_t * +nfs3_fh_to_xlator(struct nfs3_state *nfs3, struct nfs3_fh *fh) +{ + xlator_t *vol = NULL; + struct nfs3_export *exp = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, nfs3, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, fh, out); + + exp = __nfs3_get_export_by_exportid(nfs3, fh->exportid); + if (!exp) + goto out; + + vol = exp->subvol; +out: + return vol; +} + +int +nfs3_is_root_looked_up(struct nfs3_state *nfs3, struct nfs3_fh *rootfh) +{ + struct nfs3_export *exp = NULL; + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_NFS3, nfs3, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, rootfh, out); + + exp = __nfs3_get_export_by_exportid(nfs3, rootfh->exportid); + if (!exp) + goto out; + + ret = exp->rootlookedup; +out: + return ret; +} + +int +nfs3_set_root_looked_up(struct nfs3_state *nfs3, struct nfs3_fh *rootfh) +{ + struct nfs3_export *exp = NULL; + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_NFS3, nfs3, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, rootfh, out); + + exp = __nfs3_get_export_by_exportid(nfs3, rootfh->exportid); + if (!exp) + goto out; + + exp->rootlookedup = 1; +out: + return ret; +} + +#define nfs3_map_fh_to_volume(nfs3state, handle, req, volume, status, label) \ + do { \ + char exportid[256], gfid[256]; \ + rpc_transport_t *trans = NULL; \ + volume = nfs3_fh_to_xlator((nfs3state), handle); \ + if (!volume) { \ + gf_uuid_unparse(handle->exportid, exportid); \ + gf_uuid_unparse(handle->gfid, gfid); \ + trans = rpcsvc_request_transport(req); \ + GF_LOG_OCCASIONALLY(nfs3state->occ_logger, GF_NFS3, GF_LOG_ERROR, \ + "Failed to map " \ + "FH to vol: client=%s, exportid=%s, " \ + "gfid=%s", \ + trans->peerinfo.identifier, exportid, gfid); \ + GF_LOG_OCCASIONALLY(nfs3state->occ_logger, GF_NFS3, GF_LOG_ERROR, \ + "Stale nfs " \ + "client %s must be trying to connect to" \ + " a deleted volume, please unmount it.", \ + trans->peerinfo.identifier); \ + status = NFS3ERR_STALE; \ + goto label; \ + } else { \ + gf_msg_trace(GF_NFS3, 0, \ + "FH to Volume:" \ + "%s", \ + volume->name); \ + rpcsvc_request_set_private(req, volume); \ + } \ + } while (0); + +#define nfs3_validate_gluster_fh(handle, status, errlabel) \ + do { \ + if (!nfs3_fh_validate(handle)) { \ + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_BAD_HANDLE, \ + "Bad Handle"); \ + status = NFS3ERR_BADHANDLE; \ + goto errlabel; \ + } \ + } while (0) + +#define nfs3_check_fh_auth_status(cst, nfstat, is_write_op, erlabl) \ + do { \ + int auth_ret = 0; \ + int auth_errno = 0; \ + xlator_t *xlatorp = NULL; \ + char buf[256], gfid[GF_UUID_BUF_SIZE]; \ + rpc_transport_t *trans = NULL; \ + \ + auth_ret = auth_errno = nfs3_fh_auth_nfsop(cst, is_write_op); \ + if (auth_ret < 0) { \ + trans = rpcsvc_request_transport(cst->req); \ + xlatorp = nfs3_fh_to_xlator(cst->nfs3state, &cst->resolvefh); \ + gf_uuid_unparse(cst->resolvefh.gfid, gfid); \ + sprintf(buf, "(%s) %s : %s", trans->peerinfo.identifier, \ + xlatorp ? xlatorp->name : "ERR", gfid); \ + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_RESOLVE_FH_FAIL, \ + "Unable to " \ + "resolve FH: %s", \ + buf); \ + nfstat = nfs3_errno_to_nfsstat3(-auth_errno); \ + goto erlabl; \ + } \ + } while (0) + +#define nfs3_check_fh_resolve_status(cst, nfstat, erlabl) \ + do { \ + xlator_t *xlatorp = NULL; \ + char buf[256], gfid[GF_UUID_BUF_SIZE]; \ + rpc_transport_t *trans = NULL; \ + if ((cst)->resolve_ret < 0) { \ + trans = rpcsvc_request_transport(cst->req); \ + xlatorp = nfs3_fh_to_xlator(cst->nfs3state, &cst->resolvefh); \ + gf_uuid_unparse(cst->resolvefh.gfid, gfid); \ + snprintf(buf, sizeof(buf), "(%s) %s : %s", \ + trans->peerinfo.identifier, \ + xlatorp ? xlatorp->name : "ERR", gfid); \ + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_RESOLVE_STAT, "%s: %s", \ + strerror(cst->resolve_errno), buf); \ + nfstat = nfs3_errno_to_nfsstat3(cst->resolve_errno); \ + goto erlabl; \ + } \ + } while (0) + +#define nfs3_check_new_fh_resolve_status(cst, nfstat, erlabl) \ + do { \ + xlator_t *xlatorp = NULL; \ + char buf[256], gfid[GF_UUID_BUF_SIZE]; \ + rpc_transport_t *trans = NULL; \ + if (((cst)->resolve_ret < 0) && ((cst)->resolve_errno != ENOENT)) { \ + trans = rpcsvc_request_transport(cst->req); \ + xlatorp = nfs3_fh_to_xlator(cst->nfs3state, &cst->resolvefh); \ + gf_uuid_unparse(cst->resolvefh.gfid, gfid); \ + snprintf(buf, sizeof(buf), "(%s) %s : %s", \ + trans->peerinfo.identifier, \ + xlatorp ? xlatorp->name : "ERR", gfid); \ + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_RESOLVE_STAT, "%s: %s", \ + strerror(cst->resolve_errno), buf); \ + nfstat = nfs3_errno_to_nfsstat3(cs->resolve_errno); \ + goto erlabl; \ + } \ + } while (0) + +int +__nfs3_get_volume_id(struct nfs3_state *nfs3, xlator_t *xl, uuid_t volumeid) +{ + int ret = -1; + struct nfs3_export *exp = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, nfs3, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, xl, out); + + list_for_each_entry(exp, &nfs3->exports, explist) + { + if (exp->subvol == xl) { + gf_uuid_copy(volumeid, exp->volumeid); + ret = 0; + goto out; + } + } + +out: + return ret; +} + +static int +nfs3_funge_webnfs_zerolen_fh(rpcsvc_request_t *req, struct nfs3_state *nfs3st, + struct nfs3_fh *fhd, char *name) +{ + xlator_t *fungexl = NULL; + struct nfs_state *nfs = NULL; + glfs_t *fs = NULL; + loc_t loc = { + 0, + }; + int ret = -1; + char *subdir = NULL; + char volname[NAME_MAX] = { + 0, + }; + + fungexl = nfs_mntpath_to_xlator(nfs3st->exportslist, name); + if (!fungexl) { + gf_msg_trace(GF_NFS3, 0, "failed to find xlator for volume"); + ret = -ENOENT; + goto out; + } + /* fungexl is valid, set for nfs3_request_xlator_deviceid() */ + rpcsvc_request_set_private(req, fungexl); + + /* Permission checks are done through mnt3_parse_dir_exports(). The + * "nfs.export-dir" option gets checked as well. */ + nfs = nfs_state(nfs3st->nfsx); + ret = mnt3_parse_dir_exports(req, nfs->mstate, name, _gf_false); + if (ret) { + gf_msg_trace(GF_NFS3, -ret, "mounting not possible"); + goto out; + } + + /* glfs_resolve_at copied from UDP MNT support */ + fs = glfs_new_from_ctx(fungexl->ctx); + if (!fs) { + gf_msg_trace(GF_NFS3, 0, "failed to create glfs instance"); + ret = -ENOENT; + goto out; + } + + /* split name "volname/sub/dir/s" into pieces */ + subdir = mnt3_get_volume_subdir(name, (char **)&volname); + + ret = glfs_resolve_at(fs, fungexl, NULL, subdir, &loc, NULL, 1, 0); + if (ret != 0) { + gf_msg_trace(GF_NFS3, 0, "failed to resolve %s", subdir); + ret = -ENOENT; + goto out; + } + + /* resolved subdir, copy gfid for the fh */ + gf_uuid_copy(fhd->gfid, loc.gfid); + loc_wipe(&loc); + + if (gf_nfs_dvm_off(nfs_state(nfs3st->nfsx))) + fhd->exportid[15] = nfs_xlator_to_xlid(nfs3st->exportslist, fungexl); + else { + if (__nfs3_get_volume_id(nfs3st, fungexl, fhd->exportid) < 0) { + ret = -ESTALE; + goto out; + } + } + + ret = 0; +out: + if (fs) + glfs_free_from_ctx(fs); + + return ret; +} + +/* + * This macro checks if the volume is started or not. + * If it is not started, it closes the client connection & logs it. + * + * Why do we do this? + * + * There is a "race condition" where gNFSd may start listening for RPC requests + * prior to the volume being started. Presumably, that is why this macro exists + * in the first place. In the NFS kernel client (specifically Linux's NFS + * kernel client), they establish a TCP connection to our endpoint and + * (re-)send requests. If we ignore the request, and return nothing back, + * the NFS kernel client waits forever for our response. If for some reason, + * the TCP connection were to die, and re-establish, the requests are + * retransmitted and everything begins working as expected + * + * Now, this is clearly bad behavior on the client side, + * but in order to make every user's life easier, + * gNFSd should simply disconnect the TCP connection if it sees requests + * before it is ready to accept them. + * + */ + +#define nfs3_volume_started_check(nf3stt, vlm, rtval, erlbl) \ + do { \ + if ((!nfs_subvolume_started(nfs_state(nf3stt->nfsx), vlm))) { \ + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_VOL_DISABLE, \ + "Volume is disabled: %s", vlm->name); \ + nfs3_disconnect_transport(req->trans); \ + rtval = RPCSVC_ACTOR_IGNORE; \ + goto erlbl; \ + } \ + } while (0) + +void +nfs3_disconnect_transport(rpc_transport_t *transport) +{ + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_NFS3, transport, out); + + ret = rpc_transport_disconnect(transport, _gf_false); + if (ret != 0) { + gf_log(GF_NFS3, GF_LOG_WARNING, + "Unable to close client connection to %s.", + transport->peerinfo.identifier); + } else { + gf_log(GF_NFS3, GF_LOG_WARNING, "Closed client connection to %s.", + transport->peerinfo.identifier); + } +out: + return; +} + +int +nfs3_export_sync_trusted(struct nfs3_state *nfs3, uuid_t exportid) +{ + struct nfs3_export *exp = NULL; + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_NFS3, nfs3, err); + + exp = __nfs3_get_export_by_exportid(nfs3, exportid); + if (!exp) + goto err; + + ret = exp->trusted_sync; +err: + return ret; +} + +int +nfs3_export_write_trusted(struct nfs3_state *nfs3, uuid_t exportid) +{ + struct nfs3_export *exp = NULL; + int ret = 0; + + GF_VALIDATE_OR_GOTO(GF_NFS3, nfs3, err); + + exp = __nfs3_get_export_by_exportid(nfs3, exportid); + if (!exp) + goto err; + + ret = exp->trusted_write; +err: + return ret; +} + +int +nfs3_solaris_zerolen_fh(struct nfs3_fh *fh, int fhlen) +{ + if (!fh) + return 0; + + if (nfs3_fh_validate(fh)) + return 0; + + if (fhlen == 0) { + gf_msg_trace(GF_NFS3, 0, "received WebNFS request"); + return 1; + } + + return 0; +} + +/* Function pointer that represents the generic prototypes of functions used + * to serialize NFS3 message structures into the XDR format. + * For usage, see the nfs3svc_XXX_cbk functions. + */ +typedef ssize_t (*nfs3_serializer)(struct iovec outmsg, void *args); + +static void +__nfs3_call_state_wipe(nfs3_call_state_t *cs) +{ + if (cs->fd) { + gf_msg_trace(GF_NFS3, 0, "fd 0x%lx ref: %" PRId64, (long)cs->fd, + GF_ATOMIC_GET(cs->fd->refcount)); + fd_unref(cs->fd); + } + + GF_FREE(cs->resolventry); + + GF_FREE(cs->pathname); + + if (!list_empty(&cs->entries.list)) + gf_dirent_free(&cs->entries); + + nfs_loc_wipe(&cs->oploc); + nfs_loc_wipe(&cs->resolvedloc); + if (cs->iob) + iobuf_unref(cs->iob); + if (cs->iobref) + iobref_unref(cs->iobref); + if (cs->trans) + rpc_transport_unref(cs->trans); + memset(cs, 0, sizeof(*cs)); + mem_put(cs); + /* Already refd by fd_lookup, so no need to ref again. */ +} + +nfs3_call_state_t * +nfs3_call_state_init(struct nfs3_state *s, rpcsvc_request_t *req, xlator_t *v) +{ + nfs3_call_state_t *cs = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, s, err); + GF_VALIDATE_OR_GOTO(GF_NFS3, req, err); + /* GF_VALIDATE_OR_GOTO (GF_NFS3, v, err); NLM sets this later */ + + cs = (nfs3_call_state_t *)mem_get(s->localpool); + if (!cs) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "out of memory"); + return NULL; + } + + memset(cs, 0, sizeof(*cs)); + GF_REF_INIT(cs, __nfs3_call_state_wipe); + INIT_LIST_HEAD(&cs->entries.list); + INIT_LIST_HEAD(&cs->openwait_q); + cs->operrno = EINVAL; + cs->req = req; + cs->vol = v; + cs->nfsx = s->nfsx; + cs->nfs3state = s; +err: + return cs; +} + +void +nfs3_call_state_wipe(nfs3_call_state_t *cs) +{ + if (!cs) { + gf_log_callingfn("nfs", GF_LOG_WARNING, "nfs calling state NULL"); + return; + } + GF_REF_PUT(cs); +} + +#define nfs3_handle_call_state_init(nfs3state, calls, rq, vl, opstat, \ + errlabel) \ + do { \ + calls = nfs3_call_state_init((nfs3state), (rq), (vl)); \ + if (!calls) { \ + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_INIT_CALL_STAT_FAIL, \ + "Failed to" \ + " init call state"); \ + opstat = NFS3ERR_SERVERFAULT; \ + goto errlabel; \ + } \ + } while (0) + +struct iobuf * +nfs3_serialize_reply(rpcsvc_request_t *req, void *arg, nfs3_serializer sfunc, + struct iovec *outmsg) +{ + struct nfs3_state *nfs3 = NULL; + struct iobuf *iob = NULL; + ssize_t retlen = -1; + + nfs3 = (struct nfs3_state *)rpcsvc_request_program_private(req); + if (!nfs3) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_STATE_MISSING, + "NFSv3 state not found in RPC request"); + goto ret; + } + + /* First, get the io buffer into which the reply in arg will + * be serialized. + */ + /* TODO: get rid of 'sfunc' and use 'xdrproc_t' so we + can have 'xdr_sizeof' */ + iob = iobuf_get(nfs3->iobpool); + if (!iob) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobuf"); + goto ret; + } + + iobuf_to_iovec(iob, outmsg); + /* Use the given serializer to translate the give C structure in arg + * to XDR format which will be written into the buffer in outmsg. + */ + /* retlen is used to received the error since size_t is unsigned and we + * need -1 for error notification during encoding. + */ + retlen = sfunc(*outmsg, arg); + if (retlen == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ENCODE_FAIL, + "Failed to encode message"); + goto ret; + } + + outmsg->iov_len = retlen; +ret: + if (retlen == -1) { + iobuf_unref(iob); + iob = NULL; + } + + return iob; +} + +/* Generic reply function for NFSv3 specific replies. */ +int +nfs3svc_submit_reply(rpcsvc_request_t *req, void *arg, nfs3_serializer sfunc) +{ + struct iovec outmsg = { + 0, + }; + struct iobuf *iob = NULL; + int ret = -1; + struct iobref *iobref = NULL; + + if (!req) + return -1; + + iob = nfs3_serialize_reply(req, arg, sfunc, &outmsg); + if (!iob) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SERIALIZE_REPLY_FAIL, + "Failed to serialize reply"); + goto ret; + } + + iobref = iobref_new(); + if (!iobref) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "failed on iobref_new()"); + goto ret; + } + + ret = iobref_add(iobref, iob); + if (ret) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to add iob to iobref"); + goto ret; + } + + /* Then, submit the message for transmission. */ + ret = rpcsvc_submit_message(req, &outmsg, 1, NULL, 0, iobref); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SUBMIT_REPLY_FAIL, + "Reply submission failed"); + goto ret; + } + + ret = 0; +ret: + /* Now that we've done our job of handing the message to the RPC layer + * we can safely unref the iob in the hope that RPC layer must have + * ref'ed the iob on receiving into the txlist. + */ + if (NULL != iob) + iobuf_unref(iob); + if (NULL != iobref) + iobref_unref(iobref); + return ret; +} + +int +nfs3svc_submit_vector_reply(rpcsvc_request_t *req, void *arg, + nfs3_serializer sfunc, struct iovec *payload, + int vcount, struct iobref *iobref) +{ + struct iovec outmsg = { + 0, + }; + struct iobuf *iob = NULL; + int ret = -1; + int new_iobref = 0; + + if (!req) + return -1; + + iob = nfs3_serialize_reply(req, arg, sfunc, &outmsg); + if (!iob) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SERIALIZE_REPLY_FAIL, + "Failed to serialize reply"); + goto ret; + } + if (iobref == NULL) { + iobref = iobref_new(); + if (!iobref) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "failed on iobref_new"); + goto ret; + } + new_iobref = 1; + } + + ret = iobref_add(iobref, iob); + if (ret) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to add iob to iobref"); + goto ret; + } + + /* Then, submit the message for transmission. */ + ret = rpcsvc_submit_message(req, &outmsg, 1, payload, vcount, iobref); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SUBMIT_REPLY_FAIL, + "Reply submission failed"); + goto ret; + } + + ret = 0; +ret: + /* Now that we've done our job of handing the message to the RPC layer + * we can safely unref the iob in the hope that RPC layer must have + * ref'ed the iob on receiving into the txlist. + */ + if (NULL != iob) + iobuf_unref(iob); + if (new_iobref) + iobref_unref(iobref); + return ret; +} + +uint64_t +nfs3_request_xlator_deviceid(rpcsvc_request_t *rq) +{ + struct nfs3_state *nfs3 = NULL; + xlator_t *xl = NULL; + uint64_t devid = 0; + uuid_t volumeid = { + 0, + }; + + if (!rq) + return 0; + + xl = rpcsvc_request_private(rq); + nfs3 = rpcsvc_request_program_private(rq); + if (nfs3 && (gf_nfs_dvm_off(nfs_state(nfs3->nfsx)))) + devid = (uint64_t)nfs_xlator_to_xlid(nfs3->exportslist, xl); + else { + __nfs3_get_volume_id(nfs3, xl, volumeid); + memcpy(&devid, &volumeid[8], sizeof(devid)); + } + + return devid; +} + +int +nfs3svc_null(rpcsvc_request_t *req) +{ + struct iovec dummyvec = { + 0, + }; + if (!req) + return RPCSVC_ACTOR_ERROR; + rpcsvc_submit_generic(req, &dummyvec, 1, NULL, 0, NULL); + return RPCSVC_ACTOR_SUCCESS; +} + +int +nfs3_getattr_reply(rpcsvc_request_t *req, nfsstat3 status, struct iatt *buf) +{ + getattr3res res; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_getattr3res(&res, status, buf, deviceid); + nfs3svc_submit_reply(req, &res, (nfs3_serializer)xdr_serialize_getattr3res); + + return 0; +} + +int32_t +nfs3svc_getattr_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) +{ + nfsstat3 status = NFS3_OK; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + + /* + * Somewhat counter-intuitively, we don't need to look for sh-failed + * here. Failing this getattr will generate a new lookup from the + * client, and nfs_fop_lookup_cbk will detect any self-heal failures. + */ + + if (op_ret == -1) { + status = nfs3_cbk_errno_status(op_ret, op_errno); + } else { + nfs_fix_generation(this, inode); + } + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_GETATTR, status, + op_errno, cs->resolvedloc.path); + + nfs3_getattr_reply(cs->req, status, buf); + nfs3_call_state_wipe(cs); + + return 0; +} + +int32_t +nfs3svc_getattr_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + nfsstat3 status = NFS3_OK; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + + if (op_ret == -1) { + /* Prevent crashes for the case where this call fails + * and buf is left in a NULL state, yet the op_errno == 0. + */ + if (!buf && op_errno == 0) { + op_errno = EIO; + } + status = nfs3_cbk_errno_status(op_ret, op_errno); + } + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_GETATTR, status, + op_errno, cs->resolvedloc.path); + + nfs3_getattr_reply(cs->req, status, buf); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_getattr_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + uint64_t raw_ctx = 0; + struct nfs_inode_ctx *ictx = NULL; + struct nfs_state *priv = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status(cs, stat, _gf_false, nfs3err); + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + /* If inode which is to be getattr'd is the root, we need to do a + * lookup instead because after a server reboot, it is not necessary + * for the root to have been looked up when the getattr on the root is + * sent. AND, this causes a problem for stat-prefetch in that it + * expects even the root inode to have been looked up. + + if (__is_root_gfid (cs->resolvedloc.inode->gfid)) + ret = nfs_lookup (cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_getattr_lookup_cbk, cs); + else + ret = nfs_stat (cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + */ + + if (cs->hardresolved) { + ret = -EFAULT; + stat = NFS3_OK; + goto nfs3err; + } + + /* + * If brick state changed, we need to force a proper lookup cycle (as + * would happen in native protocol) to do self-heal checks. We detect + * this by comparing the generation number for the last successful + * creation/lookup on the inode to the current number, so inodes that + * haven't been validated since the state change are affected. + */ + if (inode_ctx_get(cs->resolvedloc.inode, cs->nfsx, &raw_ctx) == 0) { + ictx = (struct nfs_inode_ctx *)(uintptr_t)raw_ctx; + priv = cs->nfsx->private; + if (ictx->generation != priv->generation) { + ret = nfs_lookup(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_getattr_lookup_cbk, cs); + goto check_err; + } + } + + ret = nfs_stat(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_getattr_stat_cbk, cs); + +check_err: + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_STAT_FOP_FAIL, + "Stat fop failed: %s: %s", cs->oploc.path, strerror(-ret)); + stat = nfs3_errno_to_nfsstat3(-ret); + } + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_GETATTR, stat, + -ret, cs->resolvedloc.path); + nfs3_getattr_reply(cs->req, stat, &cs->stbuf); + nfs3_call_state_wipe(cs); + ret = 0; + } + + return ret; +} + +int +nfs3_getattr(rpcsvc_request_t *req, struct nfs3_fh *fh) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cstate = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, req, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, fh, out); + + nfs3_log_common_call(rpcsvc_request_xid(req), "GETATTR", fh); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cstate, req, vol, stat, nfs3err); + + ret = nfs3_fh_resolve_and_resume(cstate, fh, NULL, nfs3_getattr_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_GETATTR, stat, -ret, + NULL); + nfs3_getattr_reply(req, stat, NULL); + ret = 0; + nfs3_call_state_wipe(cstate); + } +out: + return ret; +} + +int +nfs3svc_getattr(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + getattr3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + + nfs3_prep_getattr3args(&args, &fh); + if (xdr_to_getattr3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_getattr(req, &fh); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_GETATTR_FAIL, + "GETATTR procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_setattr_reply(rpcsvc_request_t *req, nfsstat3 stat, struct iatt *preop, + struct iatt *postop) +{ + setattr3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_setattr3res(&res, stat, preop, postop, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_setattr3res); + return 0; +} + +int32_t +nfs3svc_truncate_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + struct iatt *prestat = NULL; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + /* If the first stat was got from the guarded setattr callback, or + * from an earlier setattr call then we'll need to use that stat + * instead of the preop returned here. + */ + if (cs->preparent.ia_ino != 0) + prestat = &cs->preparent; + else + prestat = prebuf; + + stat = NFS3_OK; +nfs3err: + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_SETATTR, stat, + op_errno, cs->resolvedloc.path); + nfs3_setattr_reply(cs->req, stat, prestat, postbuf); + nfs3_call_state_wipe(cs); + + return 0; +} + +int32_t +nfs3svc_setattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preop, + struct iatt *postop, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -1; + struct iatt *prebuf = NULL; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + prebuf = preop; + /* Store the current preop in case we need to send a truncate, + * in which case the preop to be returned will be this one. + */ + cs->preparent = *preop; + + /* Only truncate if the size is not already same as the requested + * truncation and also only if this is not a directory. + */ + if ((gf_attr_size_set(cs->setattr_valid)) && (!IA_ISDIR(postop->ia_type)) && + (preop->ia_size != cs->attr_in.ia_size)) { + nfs_request_user_init(&nfu, cs->req); + ret = nfs_truncate(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + cs->attr_in.ia_size, nfs3svc_truncate_cbk, cs); + + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + } else { + ret = -1; /* Force a reply in the branch below. */ + stat = NFS3_OK; + } + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_SETATTR, stat, + op_errno, cs->resolvedloc.path); + nfs3_setattr_reply(cs->req, stat, prebuf, postop); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int32_t +nfs3svc_setattr_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + int ret = -EFAULT; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + if (buf->ia_ctime != cs->timestamp.seconds) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_TIMESTAMP_NO_SYNC, + "Timestamps not in sync"); + stat = NFS3ERR_NOT_SYNC; + goto nfs3err; + } + + /* Not a clean way but no motivation to add a new member to local. */ + cs->preparent = *buf; + nfs_request_user_init(&nfu, cs->req); + ret = nfs_setattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, &cs->stbuf, + cs->setattr_valid, nfs3svc_setattr_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_SETATTR, stat, + op_errno, cs->resolvedloc.path); + nfs3_setattr_reply(cs->req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int +nfs3_setattr_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + ret = nfs_setattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, &cs->attr_in, + cs->setattr_valid, nfs3svc_setattr_cbk, cs); + + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_SETATTR, stat, + -ret, cs->resolvedloc.path); + nfs3_setattr_reply(cs->req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_setattr(rpcsvc_request_t *req, struct nfs3_fh *fh, sattr3 *sattr, + sattrguard3 *guard) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, req, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, fh, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, sattr, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, guard, out); + + nfs3_log_common_call(rpcsvc_request_xid(req), "SETATTR", fh); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, fh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->setattr_valid = nfs3_sattr3_to_setattr_valid(sattr, &cs->attr_in, NULL); + if (guard->check) { + gf_msg_trace(GF_NFS3, 0, "Guard check required"); + cs->timestamp = guard->sattrguard3_u.obj_ctime; + cs->sattrguardcheck = 1; + } else { + gf_msg_trace(GF_NFS3, 0, "Guard check not required"); + cs->sattrguardcheck = 0; + } + + if (!cs->setattr_valid) { + ret = -EINVAL; /* Force a reply */ + stat = NFS3_OK; + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_SETATTR_INVALID, + "cs->setattr_valid is invalid"); + goto nfs3err; + } + + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_setattr_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_SETATTR, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_setattr_reply(req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_setattr(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + setattr3args args; + int ret = RPCSVC_ACTOR_ERROR; + + GF_VALIDATE_OR_GOTO(GF_NFS3, req, rpcerr); + + nfs3_prep_setattr3args(&args, &fh); + if (xdr_to_setattr3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_setattr(req, &fh, &args.new_attributes, &args.guard); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_SETATTR_FAIL, + "SETATTR procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_lookup_reply(rpcsvc_request_t *req, nfsstat3 stat, struct nfs3_fh *newfh, + struct iatt *stbuf, struct iatt *postparent) +{ + lookup3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_lookup3res(&res, stat, newfh, stbuf, postparent, deviceid); + return nfs3svc_submit_reply(req, &res, + (nfs3_serializer)xdr_serialize_lookup3res); +} + +int +nfs3_lookup_resume(void *carg); + +int +nfs3_fresh_lookup(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + char *oldresolventry = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, cs, err); + gf_msg_debug(GF_NFS3, 0, "inode needs fresh lookup"); + inode_unlink(cs->resolvedloc.inode, cs->resolvedloc.parent, + cs->resolventry); + nfs_loc_wipe(&cs->resolvedloc); + + /* Store pointer to currently allocated resolventry because it gets over + * written in fh_resolve_and_resume. + */ + oldresolventry = cs->resolventry; + cs->lookuptype = GF_NFS3_FRESH; + ret = nfs3_fh_resolve_and_resume(cs, &cs->resolvefh, cs->resolventry, + nfs3_lookup_resume); + /* Allocated in the previous call to fh_resolve_and_resume using the + * same call_state. + */ + GF_FREE(oldresolventry); +err: + return ret; +} + +int +nfs3svc_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) +{ + struct nfs3_fh newfh = { + {0}, + }; + nfsstat3 status = NFS3_OK; + nfs3_call_state_t *cs = NULL; + inode_t *oldinode = NULL; + + cs = frame->local; + if (op_ret == -1) { + status = nfs3_cbk_errno_status(op_ret, op_errno); + goto xmit_res; + } + + nfs3_fh_build_child_fh(&cs->parent, buf, &newfh); + oldinode = inode_link(inode, cs->resolvedloc.parent, cs->resolvedloc.name, + buf); +xmit_res: + /* Only send fresh lookup if it was a revalidate that failed. */ + if ((op_ret == -1) && (nfs3_is_revalidate_lookup(cs))) { + op_ret = nfs3_fresh_lookup(cs); + goto out; + } + + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_LOOKUP, status, + op_errno, &newfh, cs->resolvedloc.path); + nfs3_lookup_reply(cs->req, status, &newfh, buf, postparent); + nfs3_call_state_wipe(cs); +out: + if (oldinode) { + inode_lookup(oldinode); + inode_unref(oldinode); + } + return 0; +} + +int +nfs3svc_lookup_parentdir_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) +{ + struct nfs3_fh newfh = { + {0}, + }; + nfsstat3 status = NFS3_OK; + nfs3_call_state_t *cs = NULL; + uuid_t volumeid = { + 0, + }; + uuid_t mountid = { + 1, + }; + struct nfs3_state *nfs3 = NULL; + + cs = frame->local; + if (op_ret == -1) { + status = nfs3_cbk_errno_status(op_ret, op_errno); + goto xmit_res; + } + + nfs3 = cs->nfs3state; + /* If the buf inode shows that this is a root dir's buf, then the file + * handle needs to be specially crafted, in all other cases, we'll just + * create the handle normally using the buffer of the parent dir. + */ + if (buf->ia_ino != 1) { + nfs3_fh_build_parent_fh(&cs->fh, buf, &newfh); + goto xmit_res; + } + + if (gf_nfs_dvm_off(nfs_state(nfs3->nfsx))) + newfh = nfs3_fh_build_indexed_root_fh(nfs3->exportslist, cs->vol); + else { + __nfs3_get_volume_id(nfs3, cs->vol, volumeid); + newfh = nfs3_fh_build_uuid_root_fh(volumeid, mountid); + } + +xmit_res: + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_LOOKUP, status, + op_errno, &newfh, cs->resolvedloc.path); + nfs3_lookup_reply(cs->req, status, &newfh, buf, postparent); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_lookup_parentdir_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + inode_t *parent = NULL; + + if (!carg) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid argument, carg value NULL"); + return EINVAL; + } + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status(cs, stat, _gf_false, nfs3err); + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + + /* At this point now, the loc in cs is for the directory file handle + * sent by the client. This loc needs to be transformed into a loc that + * represents the parent dir of cs->resolvedloc.inode. + * + * EXCEPT in the case where the .. is a parent of the root directory. + * In this case we'll be returning the file handle and attributes of the + * root itself. + */ + nfs_request_user_init(&nfu, cs->req); + + /* Save the file handle from the LOOKUP request. We'll use this to + * build the file handle of the parent directory in case the parent is + * not root dir. + */ + cs->fh = cs->resolvefh; + + /* If fh is that of the root, the resolvedloc will already contain + * the loc for root. After that, we'll send lookup for the root dir + * itself since we cannot send the lookup on the parent of root. + * + * For all other cases, we'll send the lookup on the parent of the + * given directory file handle. + */ + if (!nfs3_fh_is_root_fh(&cs->fh)) { + parent = inode_ref(cs->resolvedloc.parent); + nfs_loc_wipe(&cs->resolvedloc); + ret = nfs_inode_loc_fill(parent, &cs->resolvedloc, NFS_RESOLVE_CREATE); + + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_INODE_LOC_FILL_ERROR, + "nfs_inode_loc_fill" + " error"); + goto errtostat; + } + } + + ret = nfs_lookup(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_lookup_parentdir_cbk, cs); +errtostat: + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_LOOKUP, stat, + -ret, cs->resolvedloc.path); + nfs3_lookup_reply(cs->req, stat, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + if (parent) + inode_unref(parent); + + return ret; +} + +int +nfs3_lookup_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + struct nfs3_fh newfh = { + {0}, + }; + + if (!carg) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid argument, carg value NULL"); + return EINVAL; + } + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status(cs, stat, _gf_false, nfs3err); + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + cs->parent = cs->resolvefh; + + if (cs->hardresolved) { + stat = NFS3_OK; + nfs3_fh_build_child_fh(&cs->parent, &cs->stbuf, &newfh); + goto nfs3err; + } + + nfs_request_user_init(&nfu, cs->req); + ret = nfs_lookup(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_lookup_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_LOOKUP, stat, + -ret, cs->resolvedloc.path); + nfs3_lookup_reply(cs->req, stat, &newfh, &cs->stbuf, &cs->postparent); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_lookup(rpcsvc_request_t *req, struct nfs3_fh *fh, int fhlen, char *name) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS3, req, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, fh, out); + GF_VALIDATE_OR_GOTO(GF_NFS3, name, out); + + nfs3_log_fh_entry_call(rpcsvc_request_xid(req), "LOOKUP", fh, name); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + if (nfs3_solaris_zerolen_fh(fh, fhlen)) { + ret = nfs3_funge_webnfs_zerolen_fh(req, nfs3, fh, name); + if (ret < 0) + goto nfs3err; + + /* this fh means we're doing a mount, name is no more useful */ + name = NULL; + } else + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_strlen_or_goto(name, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->lookuptype = GF_NFS3_REVALIDATE; + ret = nfs3_fh_resolve_and_resume(cs, fh, name, nfs3_lookup_resume); + + if (ret < 0) { + gf_msg(GF_NFS, GF_LOG_ERROR, -ret, NFS_MSG_HARD_RESOLVE_FAIL, + "failed to start hard resolve"); + } + +nfs3err: + if (ret < 0) { + stat = nfs3_errno_to_nfsstat3(-ret); + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_LOOKUP, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_lookup_reply(req, stat, NULL, NULL, NULL); + if (cs) + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_lookup(rpcsvc_request_t *req) +{ + char name[NFS_PATH_MAX]; + struct nfs3_fh fh = { + {0}, + }; + lookup3args args; + int ret = RPCSVC_ACTOR_ERROR; + + GF_VALIDATE_OR_GOTO(GF_NFS, req, rpcerr); + + nfs3_prep_lookup3args(&args, &fh, name); + if (xdr_to_lookup3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_lookup(req, &fh, args.what.dir.data.data_len, name); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_LOOKUP_PROC_FAIL, + "LOOKUP procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_access_reply(rpcsvc_request_t *req, nfsstat3 status, int32_t accbits, + int32_t reqaccbits) +{ + access3res res; + + nfs3_fill_access3res(&res, status, accbits, reqaccbits); + nfs3svc_submit_reply(req, &res, (nfs3_serializer)xdr_serialize_access3res); + return 0; +} + +int32_t +nfs3svc_access_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) +{ + nfsstat3 status = NFS3_OK; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + + if (op_ret == -1) { + status = nfs3_cbk_errno_status(op_ret, op_errno); + } + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_ACCESS, status, + op_errno, cs->resolvedloc.path); + nfs3_access_reply(cs->req, status, op_errno, cs->accessbits); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_access_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + if (!carg) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Invalid argument, carg value NULL"); + return EINVAL; + } + + cs = (nfs3_call_state_t *)carg; + + /* Additional checks on the NFS file handle + * go here. The path for an NFS ACCESS call + * goes like this: + * nfs3_access -> nfs3_fh_resolve_and_resume -> nfs3_resolve_resume -> + * nfs3_access_resume -> <macro/function performs check on FH> -> + * <continue or return from function based on check.> ('goto nfs3err' + * terminates this function and writes the appropriate response to the + * client). It is important that you do NOT stick any sort of check + * on the file handle outside of the nfs3_##OP_resume functions. + */ + nfs3_check_fh_auth_status(cs, stat, _gf_false, nfs3err); + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + cs->fh = cs->resolvefh; + nfs_request_user_init(&nfu, cs->req); + ret = nfs_access(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, cs->accessbits, + nfs3svc_access_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_ACCESS, stat, + -ret, cs->resolvedloc.path); + nfs3_access_reply(cs->req, stat, 0, 0); + nfs3_call_state_wipe(cs); + ret = 0; + } + + return ret; +} + +int +nfs3_access(rpcsvc_request_t *req, struct nfs3_fh *fh, uint32_t accbits) +{ + xlator_t *vol = NULL; + struct nfs3_state *nfs3 = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + + GF_VALIDATE_OR_GOTO(GF_NFS, req, out); + GF_VALIDATE_OR_GOTO(GF_NFS, fh, out); + nfs3_log_common_call(rpcsvc_request_xid(req), "ACCESS", fh); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + cs->accessbits = accbits; + + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_access_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_ACCESS, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_access_reply(req, stat, 0, 0); + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_access(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + access3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + + nfs3_prep_access3args(&args, &fh); + if (xdr_to_access3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_access(req, &fh, args.access); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_ACCESS_PROC_FAIL, + "ACCESS procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_readlink_reply(rpcsvc_request_t *req, nfsstat3 stat, char *path, + struct iatt *buf) +{ + readlink3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_readlink3res(&res, stat, path, buf, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_readlink3res); + + return 0; +} + +int32_t +nfs3svc_readlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, const char *path, + struct iatt *buf, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + stat = NFS3_OK; + +nfs3err: + nfs3_log_readlink_res(rpcsvc_request_xid(cs->req), stat, op_errno, + (char *)path, cs->resolvedloc.path); + nfs3_readlink_reply(cs->req, stat, (char *)path, buf); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_readlink_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status(cs, stat, _gf_false, nfs3err); + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + ret = nfs_readlink(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_readlink_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READLINK, stat, + -ret, cs->resolvedloc.path); + nfs3_readlink_reply(cs->req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_readlink(rpcsvc_request_t *req, struct nfs3_fh *fh) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_common_call(rpcsvc_request_xid(req), "READLINK", fh); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_readlink_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_READLINK, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_readlink_reply(req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_readlink(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + readlink3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + + nfs3_prep_readlink3args(&args, &fh); + if (xdr_to_readlink3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_readlink(req, &fh); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_READLINK_PROC_FAIL, + "READLINK procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_read_reply(rpcsvc_request_t *req, nfsstat3 stat, count3 count, + struct iovec *vec, int vcount, struct iobref *iobref, + struct iatt *poststat, int is_eof) +{ + read3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_read3res(&res, stat, count, poststat, is_eof, deviceid); + if (stat == NFS3_OK) { + xdr_vector_round_up(vec, vcount, count); + /* iob can be zero if the file size was zero. If so, op_ret + * would be 0 and count = 0. + */ + + if (count != 0) { + nfs3svc_submit_vector_reply( + req, (void *)&res, + (nfs3_serializer)xdr_serialize_read3res_nocopy, vec, vcount, + iobref); + } else + + nfs3svc_submit_reply( + req, (void *)&res, + (nfs3_serializer)xdr_serialize_read3res_nocopy); + } else + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_read3res_nocopy); + + return 0; +} + +int32_t +nfs3svc_read_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iovec *vector, + int32_t count, struct iatt *stbuf, struct iobref *iobref, + dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int is_eof = 0; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto err; + } else + stat = NFS3_OK; + + if (op_errno == ENOENT) + is_eof = 1; + +err: + nfs3_log_read_res(rpcsvc_request_xid(cs->req), stat, op_errno, op_ret, + is_eof, vector, count, cs->resolvedloc.path); + nfs3_read_reply(cs->req, stat, op_ret, vector, count, iobref, stbuf, + is_eof); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_read_fd_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + ret = nfs_read(cs->nfsx, cs->vol, &nfu, cs->fd, cs->datacount, + cs->dataoffset, nfs3svc_read_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READ, stat, -ret, + cs->resolvedloc.path); + nfs3_read_reply(cs->req, stat, 0, NULL, 0, NULL, NULL, 0); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_read_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + fd_t *fd = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status(cs, stat, _gf_false, nfs3err); + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + fd = fd_anonymous(cs->resolvedloc.inode); + if (!fd) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ANONYMOUS_FD_FAIL, + "Failed to create anonymous fd"); + goto nfs3err; + } + + cs->fd = fd; + nfs3_read_fd_resume(cs); + ret = 0; +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READ, stat, -ret, + cs->resolvedloc.path); + nfs3_read_reply(cs->req, stat, 0, NULL, 0, NULL, NULL, 0); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_read(rpcsvc_request_t *req, struct nfs3_fh *fh, offset3 offset, + count3 count) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_rw_call(rpcsvc_request_xid(req), "READ", fh, offset, count, -1); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->datacount = count; + cs->dataoffset = offset; + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_read_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_READ, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_read_reply(req, stat, 0, NULL, 0, NULL, NULL, 0); + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_read(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + read3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + + nfs3_prep_read3args(&args, &fh); + if (xdr_to_read3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_read(req, &fh, args.offset, args.count); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_READ_FAIL, + "READ procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_write_reply(rpcsvc_request_t *req, nfsstat3 stat, count3 count, + stable_how stable, uint64_t wverf, struct iatt *prestat, + struct iatt *poststat) +{ + write3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_write3res(&res, stat, count, stable, wverf, prestat, poststat, + deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_write3res); + + return 0; +} + +int32_t +nfs3svc_write_fsync_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + struct nfs3_state *nfs3 = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + nfs3 = rpcsvc_request_program_private(cs->req); + + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + } else + stat = NFS3_OK; + + nfs3_log_write_res(rpcsvc_request_xid(cs->req), stat, op_errno, + cs->maxcount, cs->writetype, nfs3->serverstart, + cs->resolvedloc.path); + nfs3_write_reply(cs->req, stat, cs->maxcount, cs->writetype, + nfs3->serverstart, &cs->stbuf, postbuf); + nfs3_call_state_wipe(cs); + return 0; +} + +/* + * Before going into the write reply logic, here is a matrix that shows the + * requirements for a write reply as given by RFC1813. + * + * Requested Write Type || Possible Returns + * ============================================== + * FILE_SYNC || FILE_SYNC + * DATA_SYNC || DATA_SYNC or FILE_SYNC + * UNSTABLE || DATA_SYNC or FILE_SYNC or UNSTABLE + * + * Write types other than UNSTABLE are together called STABLE. + * RS - Return Stable + * RU - Return Unstable + * WS - Write Stable + * WU - Write Unstable + * + *+============================================+ + *| Vol Opts -> || trusted-write| trusted-sync | + *| Write Type || | | + *|-------------||--------------|--------------| + *| STABLE || WS | WU | + *| || RS | RS | + *|-------------||--------------|--------------| + *| UNSTABLE || WU | WU | + *| || RS | RS | + *|-------------||--------------|--------------| + *| COMMIT || fsync | getattr | + *+============================================+ + * + * + */ +int32_t +nfs3svc_write_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + struct nfs3_state *nfs3 = NULL; + + cs = frame->local; + nfs3 = rpcsvc_request_program_private(cs->req); + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto err; + } + + stat = NFS3_OK; + cs->maxcount = op_ret; + +err: + nfs3_log_write_res(rpcsvc_request_xid(cs->req), stat, op_errno, + cs->maxcount, cs->writetype, nfs3->serverstart, + cs->resolvedloc.path); + nfs3_write_reply(cs->req, stat, cs->maxcount, cs->writetype, + nfs3->serverstart, prebuf, postbuf); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +__nfs3_write_resume(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + + if (!cs) + return ret; + + nfs_request_user_init(&nfu, cs->req); + /* It is possible that the RPC record contains more bytes than + * than the size of write requested in this request. This means, + * that in the RPC message buffer, there could be more bytes + * beyind the @count bytes. Since @payload is referring to the write + * data directly inside the RPC request buffer(..since we performed a + * no-copy deXDRing..), we might end up writing more data than + * requested, because till now payload.iov_len accounts for all the + * bytes not just the write request bytes. These extra bytes are present + * as a requirement of the XDR encoding to round up the all string and + * opaque data buffers to multiples of 4 bytes. + */ + cs->datavec.iov_len = cs->datacount; + ret = nfs_write(cs->nfsx, cs->vol, &nfu, cs->fd, cs->iobref, &cs->datavec, + 1, cs->dataoffset, nfs3svc_write_cbk, cs); + + return ret; +} + +int +nfs3_write_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + fd_t *fd = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status(cs, stat, _gf_true, nfs3err); + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + fd = fd_anonymous(cs->resolvedloc.inode); + if (!fd) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ANONYMOUS_FD_FAIL, + "Failed to create anonymous fd"); + goto nfs3err; + } + + cs->fd = fd; /* Gets unrefd when the call state is wiped. */ + + ret = __nfs3_write_resume(cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_WRITE, stat, -ret, + cs->resolvedloc.path); + nfs3_write_reply(cs->req, stat, 0, cs->writetype, 0, NULL, NULL); + nfs3_call_state_wipe(cs); + } + return ret; +} + +int +nfs3_write(rpcsvc_request_t *req, struct nfs3_fh *fh, offset3 offset, + count3 count, stable_how stable, struct iovec payload, + struct iobref *iobref) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh) || (!payload.iov_base)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_rw_call(rpcsvc_request_xid(req), "WRITE", fh, offset, count, + stable); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, fh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + cs->datacount = count; + cs->dataoffset = offset; + cs->writetype = stable; + cs->iobref = iobref; + cs->datavec = payload; + + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_write_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_WRITE, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_write_reply(req, stat, 0, stable, 0, NULL, NULL); + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +#define NFS3_VECWRITE_READFHLEN 1 +#define NFS3_VECWRITE_READFH 2 +#define NFS3_VECWRITE_READREST 3 + +#define NFS3_WRITE_POSTFH_SIZE 20 + +int +nfs3svc_write_vecsizer(int state, ssize_t *readsize, char *base_addr, + char *curr_addr) +{ + int ret = 0; + uint32_t fhlen = 0; + uint32_t fhlen_n = 0; + + if (state == 0) { + ret = NFS3_VECWRITE_READFHLEN; + *readsize = 4; + } else if (state == NFS3_VECWRITE_READFHLEN) { + fhlen_n = *(uint32_t *)(curr_addr - 4); + fhlen = ntohl(fhlen_n); + *readsize = xdr_length_round_up(fhlen, NFS3_FHSIZE); + ret = NFS3_VECWRITE_READFH; + } else if (state == NFS3_VECWRITE_READFH) { + *readsize = NFS3_WRITE_POSTFH_SIZE; + ret = NFS3_VECWRITE_READREST; + } else if (state == NFS3_VECWRITE_READREST) { + ret = 0; + *readsize = 0; + } else + gf_msg("nfs", GF_LOG_ERROR, 0, NFS_MSG_STATE_WRONG, "state wrong"); + + return ret; +} + +int +nfs3svc_write(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + write3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_write3args(&args, &fh); + if (xdr_to_write3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + /* To ensure that the iobuf for the current record does not + * get returned to the iobpool, we need to keep a reference for + * ourselves because the RPC call handler who called us will unref its + * own ref of the record's iobuf when it is done handling the request. + */ + + ret = nfs3_write(req, &fh, args.offset, args.count, args.stable, + req->msg[1], rpcsvc_request_iobref_ref(req)); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_WRITE_FAIL, + "WRITE procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_create_reply(rpcsvc_request_t *req, nfsstat3 stat, struct nfs3_fh *newfh, + struct iatt *newbuf, struct iatt *preparent, + struct iatt *postparent) +{ + create3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_create3res(&res, stat, newfh, newbuf, preparent, postparent, + deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_create3res); + return 0; +} + +int32_t +nfs3svc_create_setattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preop, + struct iatt *postop, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + stat = NFS3_OK; +nfs3err: + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_CREATE, stat, op_errno, + &cs->fh, cs->resolvedloc.path); + nfs3_create_reply(cs->req, stat, &cs->fh, postop, &cs->preparent, + &cs->postparent); + nfs3_call_state_wipe(cs); + + return 0; +} + +int32_t +nfs3svc_create_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + inode_t *oldinode = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + nfs3_fh_build_child_fh(&cs->parent, buf, &cs->fh); + oldinode = inode_link(inode, cs->resolvedloc.parent, cs->resolvedloc.name, + buf); + + /* Means no attributes were required to be set. */ + if (!cs->setattr_valid) { + stat = NFS3_OK; + ret = -1; + goto nfs3err; + } + + cs->preparent = *preparent; + cs->postparent = *postparent; + nfs_request_user_init(&nfu, cs->req); + gf_uuid_copy(cs->resolvedloc.gfid, oldinode->gfid); + ret = nfs_setattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, &cs->stbuf, + cs->setattr_valid, nfs3svc_create_setattr_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (oldinode) { + inode_lookup(oldinode); + inode_unref(oldinode); + } + + if (ret < 0) { + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_CREATE, stat, + op_errno, &cs->fh, cs->resolvedloc.path); + nfs3_create_reply(cs->req, stat, &cs->fh, buf, preparent, postparent); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int +nfs3_create_common(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + int flags = 0; + nfs_user_t nfu = { + 0, + }; + uid_t uid = 0; + gid_t gid = 0; + + if (!cs) + return ret; + + if (cs->createmode == GUARDED) + flags = (O_RDWR | O_EXCL); + else + flags = O_RDWR; + + if (gf_attr_uid_set(cs->setattr_valid)) { + uid = cs->stbuf.ia_uid; + cs->setattr_valid &= ~GF_SET_ATTR_UID; + } else + uid = rpcsvc_request_uid(cs->req); + + if (gf_attr_gid_set(cs->setattr_valid)) { + gid = cs->stbuf.ia_gid; + cs->setattr_valid &= ~GF_SET_ATTR_GID; + } else + gid = rpcsvc_request_gid(cs->req); + + nfs_request_primary_user_init(&nfu, cs->req, uid, gid); + /* We can avoid sending the setattr call later if only the mode is + * required to be set. This is possible because the create fop allows + * us to specify a mode arg. + */ + if (cs->setattr_valid & GF_SET_ATTR_MODE) { + cs->setattr_valid &= ~GF_SET_ATTR_MODE; + ret = nfs_create(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, flags, + cs->mode, nfs3svc_create_cbk, cs); + } else + ret = nfs_create(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, flags, + NFS_DEFAULT_CREATE_MODE, nfs3svc_create_cbk, cs); + + return ret; +} + +int32_t +nfs3svc_create_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + int ret = -EFAULT; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + nfs_request_user_init(&nfu, cs->req); + if (op_ret == -1) { + ret = -op_errno; + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + if ((cs->stbuf.ia_mtime == buf->ia_mtime) && + (cs->stbuf.ia_atime == buf->ia_atime)) { + gf_msg_debug(GF_NFS3, 0, + "Create req retransmitted verf %" PRId64 " %" PRId64, + cs->stbuf.ia_mtime, cs->stbuf.ia_atime); + stat = NFS3_OK; + nfs3_fh_build_child_fh(&cs->parent, buf, &cs->fh); + } else { + gf_msg_debug(GF_NFS3, 0, + "File already exist new_verf %" PRId64 " %" PRId64 + "old_verf %" PRId64 " %" PRId64, + cs->stbuf.ia_mtime, cs->stbuf.ia_atime, buf->ia_mtime, + buf->ia_atime); + stat = NFS3ERR_EXIST; + } + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_CREATE, stat, + op_errno, cs->resolvedloc.path); + nfs3_create_reply(cs->req, stat, &cs->fh, buf, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int +nfs3_create_exclusive(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + + if (!cs) + return ret; + + /* Storing verifier as a mtime and atime attribute, to store it + * in stable storage */ + memcpy(&cs->stbuf.ia_atime, &cs->cookieverf, sizeof(cs->stbuf.ia_atime)); + memcpy(&cs->stbuf.ia_mtime, + ((char *)&cs->cookieverf) + sizeof(cs->stbuf.ia_atime), + sizeof(cs->stbuf.ia_mtime)); + cs->setattr_valid |= GF_SET_ATTR_ATIME; + cs->setattr_valid |= GF_SET_ATTR_MTIME; + nfs_request_user_init(&nfu, cs->req); + + /* If the file already existed we need to get that attributes so we can + * compare and check whether a previous create operation was + * interrupted due to server failure or dropped packets. + */ + if ((cs->resolve_ret == 0) || + ((cs->resolve_ret == -1) && (cs->resolve_errno != ENOENT))) { + ret = nfs_stat(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_create_stat_cbk, cs); + goto nfs3err; + } + + ret = nfs3_create_common(cs); +nfs3err: + return ret; +} + +int +nfs3_create_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status(cs, stat, _gf_true, nfs3err); + nfs3_check_new_fh_resolve_status(cs, stat, nfs3err); + if (cs->createmode == EXCLUSIVE) + ret = nfs3_create_exclusive(cs); + else + ret = nfs3_create_common(cs); + + /* Handle a failure return from either of the create functions above. */ + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_CREATE, stat, + -ret, cs->resolvedloc.path); + nfs3_create_reply(cs->req, stat, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_create(rpcsvc_request_t *req, struct nfs3_fh *dirfh, char *name, + createmode3 mode, sattr3 *sattr, uint64_t cverf) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!dirfh) || (!name) || (!sattr)) + return -1; + + nfs3_log_create_call(rpcsvc_request_xid(req), dirfh, name, mode); + nfs3_validate_gluster_fh(dirfh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(name, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, dirfh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, dirfh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->cookieverf = cverf; + /*In Exclusive create client is supposed to send cverf instead of + * sattr*/ + if (mode != EXCLUSIVE) + cs->setattr_valid = nfs3_sattr3_to_setattr_valid(sattr, &cs->stbuf, + &cs->mode); + cs->createmode = mode; + cs->parent = *dirfh; + + ret = nfs3_fh_resolve_and_resume(cs, dirfh, name, nfs3_create_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_CREATE, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_create_reply(req, stat, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_create(rpcsvc_request_t *req) +{ + char name[NFS_PATH_MAX]; + struct nfs3_fh dirfh = { + {0}, + }; + create3args args; + int ret = RPCSVC_ACTOR_ERROR; + uint64_t cverf = 0; + uint64_t *cval; + + if (!req) + return ret; + + nfs3_prep_create3args(&args, &dirfh, name); + if (xdr_to_create3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + cval = (uint64_t *)args.how.createhow3_u.verf; + cverf = *cval; + + ret = nfs3_create(req, &dirfh, name, args.how.mode, + &args.how.createhow3_u.obj_attributes, cverf); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_CREATE_FAIL, + "CREATE procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_mkdir_reply(rpcsvc_request_t *req, nfsstat3 stat, struct nfs3_fh *fh, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent) +{ + mkdir3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_mkdir3res(&res, stat, fh, buf, preparent, postparent, deviceid); + nfs3svc_submit_reply(req, &res, (nfs3_serializer)xdr_serialize_mkdir3res); + return 0; +} + +int32_t +nfs3svc_mkdir_setattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preop, + struct iatt *postop, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + stat = NFS3_OK; +nfs3err: + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_MKDIR, stat, op_errno, + &cs->fh, cs->resolvedloc.path); + nfs3_mkdir_reply(cs->req, stat, &cs->fh, postop, &cs->preparent, + &cs->postparent); + nfs3_call_state_wipe(cs); + + return 0; +} + +int32_t +nfs3svc_mkdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + nfs3_fh_build_child_fh(&cs->parent, buf, &cs->fh); + + /* Means no attributes were required to be set. */ + if (!cs->setattr_valid) { + stat = NFS3_OK; + goto nfs3err; + } + + cs->preparent = *preparent; + cs->postparent = *postparent; + nfs_request_user_init(&nfu, cs->req); + ret = nfs_setattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, &cs->stbuf, + cs->setattr_valid, nfs3svc_mkdir_setattr_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_MKDIR, stat, + op_errno, &cs->fh, cs->resolvedloc.path); + nfs3_mkdir_reply(cs->req, stat, &cs->fh, buf, preparent, postparent); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int +nfs3_mkdir_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_new_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + + if (gf_attr_mode_set(cs->setattr_valid)) { + cs->setattr_valid &= ~GF_SET_ATTR_MODE; + ret = nfs_mkdir(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, cs->mode, + nfs3svc_mkdir_cbk, cs); + } else + ret = nfs_mkdir(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, cs->mode, + nfs3svc_mkdir_cbk, cs); + + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_MKDIR, stat, -ret, + cs->resolvedloc.path); + nfs3_mkdir_reply(cs->req, stat, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int +nfs3_mkdir(rpcsvc_request_t *req, struct nfs3_fh *dirfh, char *name, + sattr3 *sattr) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!dirfh) || (!name) || (!sattr)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_fh_entry_call(rpcsvc_request_xid(req), "MKDIR", dirfh, name); + nfs3_validate_gluster_fh(dirfh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(name, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, dirfh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, dirfh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->parent = *dirfh; + cs->setattr_valid = nfs3_sattr3_to_setattr_valid(sattr, &cs->stbuf, + &cs->mode); + ret = nfs3_fh_resolve_and_resume(cs, dirfh, name, nfs3_mkdir_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_MKDIR, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_mkdir_reply(req, stat, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_mkdir(rpcsvc_request_t *req) +{ + char name[NFS_PATH_MAX]; + struct nfs3_fh dirfh = { + {0}, + }; + mkdir3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_mkdir3args(&args, &dirfh, name); + if (xdr_to_mkdir3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_mkdir(req, &dirfh, name, &args.attributes); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_DIR_OP_FAIL, + "MKDIR procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_symlink_reply(rpcsvc_request_t *req, nfsstat3 stat, struct nfs3_fh *fh, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent) +{ + symlink3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_symlink3res(&res, stat, fh, buf, preparent, postparent, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_symlink3res); + + return 0; +} + +int32_t +nfs3svc_symlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + nfs3_fh_build_child_fh(&cs->parent, buf, &cs->fh); + stat = NFS3_OK; + +nfs3err: + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_SYMLINK, stat, + op_errno, &cs->fh, cs->resolvedloc.path); + nfs3_symlink_reply(cs->req, stat, &cs->fh, buf, preparent, postparent); + nfs3_call_state_wipe(cs); + return 0; +} + +int +nfs3_symlink_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_new_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + ret = nfs_symlink(cs->nfsx, cs->vol, &nfu, cs->pathname, &cs->resolvedloc, + nfs3svc_symlink_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_SYMLINK, stat, + -ret, cs->resolvedloc.path); + nfs3_symlink_reply(cs->req, stat, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_symlink(rpcsvc_request_t *req, struct nfs3_fh *dirfh, char *name, + char *target, sattr3 *sattr) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!dirfh) || (!name) || (!target) || (!sattr)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_symlink_call(rpcsvc_request_xid(req), dirfh, name, target); + nfs3_validate_gluster_fh(dirfh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(name, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, dirfh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, dirfh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->parent = *dirfh; + cs->pathname = gf_strdup(target); + if (!cs->pathname) { + ret = -1; + stat = NFS3ERR_SERVERFAULT; + goto nfs3err; + } + + ret = nfs3_fh_resolve_and_resume(cs, dirfh, name, nfs3_symlink_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_SYMLINK, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_symlink_reply(req, stat, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_symlink(rpcsvc_request_t *req) +{ + char name[NFS_PATH_MAX]; + struct nfs3_fh dirfh = { + {0}, + }; + char target[NFS_PATH_MAX]; + symlink3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_symlink3args(&args, &dirfh, name, target); + if (xdr_to_symlink3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_symlink(req, &dirfh, name, target, + &args.symlink.symlink_attributes); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EXDEV, NFS_MSG_SYMLINK_FAIL, + "SYMLINK procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +static int +nfs3_mknod_reply(rpcsvc_request_t *req, nfsstat3 stat, struct nfs3_fh *fh, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent) +{ + mknod3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_mknod3res(&res, stat, fh, buf, preparent, postparent, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_mknod3res); + + return 0; +} + +int32_t +nfs3svc_mknod_setattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preop, + struct iatt *postop, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + stat = NFS3_OK; +nfs3err: + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_MKNOD, stat, op_errno, + &cs->fh, cs->resolvedloc.path); + nfs3_mknod_reply(cs->req, stat, &cs->fh, postop, &cs->preparent, + &cs->postparent); + nfs3_call_state_wipe(cs); + return 0; +} + +int32_t +nfs3svc_mknod_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -1; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + nfs3_fh_build_child_fh(&cs->parent, buf, &cs->fh); + + /* Means no attributes were required to be set. */ + if (!cs->setattr_valid) { + stat = NFS3_OK; + ret = -1; + goto nfs3err; + } + + cs->preparent = *preparent; + cs->postparent = *postparent; + nfs_request_user_init(&nfu, cs->req); + ret = nfs_setattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, &cs->stbuf, + cs->setattr_valid, nfs3svc_mknod_setattr_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); +nfs3err: + if (ret < 0) { + nfs3_log_newfh_res(rpcsvc_request_xid(cs->req), NFS3_MKNOD, stat, + op_errno, &cs->fh, cs->resolvedloc.path); + nfs3_mknod_reply(cs->req, stat, &cs->fh, buf, preparent, postparent); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +static int +nfs3_mknod_device(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + dev_t devnum = 0; + mode_t mode = 0; + nfs_user_t nfu = { + 0, + }; + + if (!cs) + return ret; + + devnum = makedev(cs->devnums.specdata1, cs->devnums.specdata2); + if (cs->mknodtype == NF3CHR) + mode = S_IFCHR; + else + mode = S_IFBLK; + + nfs_request_user_init(&nfu, cs->req); + if (gf_attr_mode_set(cs->setattr_valid)) { + cs->setattr_valid &= ~GF_SET_ATTR_MODE; + mode |= cs->mode; + ret = nfs_mknod(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, mode, devnum, + nfs3svc_mknod_cbk, cs); + } else + ret = nfs_mknod(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, mode, devnum, + nfs3svc_mknod_cbk, cs); + + return ret; +} + +static int +nfs3_mknod_fifo(nfs3_call_state_t *cs, mode_t mode) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + + if (!cs) + return ret; + + nfs_request_user_init(&nfu, cs->req); + if (gf_attr_mode_set(cs->setattr_valid)) { + cs->setattr_valid &= ~GF_SET_ATTR_MODE; + mode |= cs->mode; + ret = nfs_mknod(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, mode, 0, + nfs3svc_mknod_cbk, cs); + } else + ret = nfs_mknod(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, mode, 0, + nfs3svc_mknod_cbk, cs); + + return ret; +} + +static int +nfs3_mknod_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_new_fh_resolve_status(cs, stat, nfs3err); + switch (cs->mknodtype) { + case NF3CHR: + case NF3BLK: + ret = nfs3_mknod_device(cs); + break; + case NF3SOCK: + ret = nfs3_mknod_fifo(cs, S_IFSOCK); + break; + case NF3FIFO: + ret = nfs3_mknod_fifo(cs, S_IFIFO); + break; + default: + ret = -EBADF; + break; + } + + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_MKNOD, stat, -ret, + cs->resolvedloc.path); + nfs3_mknod_reply(cs->req, stat, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_mknod(rpcsvc_request_t *req, struct nfs3_fh *fh, char *name, + mknoddata3 *nodedata) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + sattr3 *sattr = NULL; + + if ((!req) || (!fh) || (!name) || (!nodedata)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_mknod_call(rpcsvc_request_xid(req), fh, name, nodedata->type); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(name, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, fh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->mknodtype = nodedata->type; + switch (nodedata->type) { + case NF3CHR: + case NF3BLK: + cs->devnums = nodedata->mknoddata3_u.device.spec; + sattr = &nodedata->mknoddata3_u.device.dev_attributes; + cs->setattr_valid = nfs3_sattr3_to_setattr_valid(sattr, &cs->stbuf, + &cs->mode); + break; + case NF3SOCK: + case NF3FIFO: + sattr = &nodedata->mknoddata3_u.pipe_attributes; + cs->setattr_valid = nfs3_sattr3_to_setattr_valid(sattr, &cs->stbuf, + &cs->mode); + break; + default: + ret = -EBADF; + goto nfs3err; + } + + cs->parent = *fh; + ret = nfs3_fh_resolve_and_resume(cs, fh, name, nfs3_mknod_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_MKNOD, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_mknod_reply(req, stat, NULL, NULL, NULL, NULL); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_mknod(rpcsvc_request_t *req) +{ + char name[NFS_PATH_MAX]; + struct nfs3_fh fh = { + {0}, + }; + mknod3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_mknod3args(&args, &fh, name); + if (xdr_to_mknod3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_mknod(req, &fh, name, &args.what); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_MKNOD_FAIL, + "MKNOD procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_remove_reply(rpcsvc_request_t *req, nfsstat3 stat, struct iatt *preparent, + struct iatt *postparent) +{ + remove3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_remove3res(&res, stat, preparent, postparent, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_remove3res); + return 0; +} + +int32_t +nfs3svc_remove_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + } + + if (op_ret == 0) + stat = NFS3_OK; + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_REMOVE, stat, + op_errno, cs->resolvedloc.path); + nfs3_remove_reply(cs->req, stat, preparent, postparent); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +__nfs3_remove(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + ia_type_t type = 0; + + if (!cs) + return ret; + type = cs->resolvedloc.inode->ia_type; + nfs_request_user_init(&nfu, cs->req); + if (IA_ISDIR(type)) + ret = nfs_rmdir(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_remove_cbk, cs); + else + ret = nfs_unlink(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_remove_cbk, cs); + + return ret; +} + +int +nfs3_remove_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + ret = __nfs3_remove(cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_REMOVE, stat, + -ret, cs->resolvedloc.path); + nfs3_remove_reply(cs->req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_remove(rpcsvc_request_t *req, struct nfs3_fh *fh, char *name) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh) || (!name)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_fh_entry_call(rpcsvc_request_xid(req), "REMOVE", fh, name); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(name, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, fh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + ret = nfs3_fh_resolve_and_resume(cs, fh, name, nfs3_remove_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_REMOVE, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_remove_reply(req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_remove(rpcsvc_request_t *req) +{ + char name[NFS_PATH_MAX]; + struct nfs3_fh fh = { + {0}, + }; + remove3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_remove3args(&args, &fh, name); + if (xdr_to_remove3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_remove(req, &fh, name); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_REMOVE_FAIL, + "REMOVE procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_rmdir_reply(rpcsvc_request_t *req, nfsstat3 stat, struct iatt *preparent, + struct iatt *postparent) +{ + rmdir3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_rmdir3res(&res, stat, preparent, postparent, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_rmdir3res); + return 0; +} + +int32_t +nfs3svc_rmdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + gf_msg(GF_NFS, GF_LOG_WARNING, op_errno, NFS_MSG_RMDIR_CBK, + "%x: %s => -1 (%s)", rpcsvc_request_xid(cs->req), + cs->resolvedloc.path, strerror(op_errno)); + stat = nfs3_cbk_errno_status(op_ret, op_errno); + } else { + stat = NFS3_OK; + } + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_RMDIR, stat, op_errno, + cs->resolvedloc.path); + nfs3_rmdir_reply(cs->req, stat, preparent, postparent); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_rmdir_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + ret = nfs_rmdir(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_rmdir_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_RMDIR, stat, -ret, + cs->resolvedloc.path); + nfs3_rmdir_reply(cs->req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_rmdir(rpcsvc_request_t *req, struct nfs3_fh *fh, char *name) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh) || (!name)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_fh_entry_call(rpcsvc_request_xid(req), "RMDIR", fh, name); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(name, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, fh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + ret = nfs3_fh_resolve_and_resume(cs, fh, name, nfs3_rmdir_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_RMDIR, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_rmdir_reply(req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_rmdir(rpcsvc_request_t *req) +{ + char name[NFS_PATH_MAX]; + struct nfs3_fh fh = { + {0}, + }; + rmdir3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_rmdir3args(&args, &fh, name); + if (xdr_to_rmdir3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_rmdir(req, &fh, name); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_DIR_OP_FAIL, + "RMDIR procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_rename_reply(rpcsvc_request_t *req, nfsstat3 stat, struct iatt *buf, + struct iatt *preoldparent, struct iatt *postoldparent, + struct iatt *prenewparent, struct iatt *postnewparent) +{ + rename3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_rename3res(&res, stat, buf, preoldparent, postoldparent, + prenewparent, postnewparent, deviceid); + + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_rename3res); + + return 0; +} + +int32_t +nfs3svc_rename_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + struct iatt *preoldparent, struct iatt *postoldparent, + struct iatt *prenewparent, struct iatt *postnewparent, + dict_t *xdata) +{ + int ret = -EFAULT; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + stat = NFS3_OK; +nfs3err: + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_RENAME, stat, -ret, + cs->resolvedloc.path); + nfs3_rename_reply(cs->req, stat, buf, preoldparent, postoldparent, + prenewparent, postnewparent); + nfs3_call_state_wipe(cs); + return 0; +} + +int +nfs3_rename_resume_dst(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_new_fh_resolve_status(cs, stat, nfs3err); + cs->parent = cs->resolvefh; + nfs_request_user_init(&nfu, cs->req); + ret = nfs_rename(cs->nfsx, cs->vol, &nfu, &cs->oploc, &cs->resolvedloc, + nfs3svc_rename_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_RENAME, stat, + -ret, cs->resolvedloc.path); + nfs3_rename_reply(cs->req, stat, NULL, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_rename_resume_src(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + /* Copy the resolved loc for the source file into another loc + * for safekeeping till we resolve the dest loc. + */ + nfs_loc_copy(&cs->oploc, &cs->resolvedloc); + nfs_loc_wipe(&cs->resolvedloc); + GF_FREE(cs->resolventry); + + ret = nfs3_fh_resolve_and_resume(cs, &cs->fh, cs->pathname, + nfs3_rename_resume_dst); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_RENAME, stat, + -ret, cs->resolvedloc.path); + nfs3_rename_reply(cs->req, stat, NULL, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_rename(rpcsvc_request_t *req, struct nfs3_fh *olddirfh, char *oldname, + struct nfs3_fh *newdirfh, char *newname) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!olddirfh) || (!oldname) || (!newdirfh) || (!newname)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_rename_call(rpcsvc_request_xid(req), olddirfh, oldname, newdirfh, + newname); + nfs3_validate_gluster_fh(olddirfh, stat, nfs3err); + nfs3_validate_gluster_fh(newdirfh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(oldname, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_validate_strlen_or_goto(newname, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, olddirfh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, olddirfh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + /* While we resolve the source (fh, name) pair, we need to keep a copy + * of the dest (fh,name) pair. + */ + cs->fh = *newdirfh; + cs->pathname = gf_strdup(newname); + if (!cs->pathname) { + stat = NFS3ERR_SERVERFAULT; + ret = -1; + goto nfs3err; + } + + ret = nfs3_fh_resolve_and_resume(cs, olddirfh, oldname, + nfs3_rename_resume_src); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_RENAME, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_rename_reply(req, stat, NULL, NULL, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_rename(rpcsvc_request_t *req) +{ + char newname[NFS_PATH_MAX]; + char oldname[NFS_PATH_MAX]; + struct nfs3_fh olddirfh = { + {0}, + }; + struct nfs3_fh newdirfh = { + {0}, + }; + rename3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_rename3args(&args, &olddirfh, oldname, &newdirfh, newname); + if (xdr_to_rename3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_rename(req, &olddirfh, oldname, &newdirfh, newname); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_RENAME_FAIL, + "RENAME procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_link_reply(rpcsvc_request_t *req, nfsstat3 stat, struct iatt *buf, + struct iatt *preparent, struct iatt *postparent) +{ + link3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_link3res(&res, stat, buf, preparent, postparent, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_link3res); + + return 0; +} + +int32_t +nfs3svc_link_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + } else + stat = NFS3_OK; + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_LINK, stat, op_errno, + cs->resolvedloc.path); + nfs3_link_reply(cs->req, stat, buf, preparent, postparent); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_link_resume_lnk(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_new_fh_resolve_status(cs, stat, nfs3err); + + nfs_request_user_init(&nfu, cs->req); + ret = nfs_link(cs->nfsx, cs->vol, &nfu, &cs->oploc, &cs->resolvedloc, + nfs3svc_link_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_LINK, stat, -ret, + cs->resolvedloc.path); + nfs3_link_reply(cs->req, stat, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + return ret; +} + +int +nfs3_link_resume_tgt(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_loc_copy(&cs->oploc, &cs->resolvedloc); + nfs_loc_wipe(&cs->resolvedloc); + + ret = nfs3_fh_resolve_and_resume(cs, &cs->fh, cs->pathname, + nfs3_link_resume_lnk); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_LINK, stat, -ret, + cs->resolvedloc.path); + nfs3_link_reply(cs->req, stat, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_link(rpcsvc_request_t *req, struct nfs3_fh *targetfh, + struct nfs3_fh *dirfh, char *newname) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!targetfh) || (!dirfh) || (!newname)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_validate_gluster_fh(dirfh, stat, nfs3err); + nfs3_validate_gluster_fh(targetfh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_validate_strlen_or_goto(newname, NFS_NAME_MAX, nfs3err, stat, ret); + nfs3_map_fh_to_volume(nfs3, dirfh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, dirfh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->fh = *dirfh; + cs->pathname = gf_strdup(newname); + if (!cs->pathname) { + stat = NFS3ERR_SERVERFAULT; + ret = -1; + goto nfs3err; + } + + ret = nfs3_fh_resolve_and_resume(cs, targetfh, NULL, nfs3_link_resume_tgt); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_LINK, stat, -ret, + cs ? cs->pathname : NULL); + nfs3_link_reply(req, stat, NULL, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_link(rpcsvc_request_t *req) +{ + char newpath[NFS_PATH_MAX]; + struct nfs3_fh dirfh = { + {0}, + }; + struct nfs3_fh targetfh = { + {0}, + }; + link3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_link3args(&args, &targetfh, &dirfh, newpath); + if (xdr_to_link3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_link(req, &targetfh, &dirfh, newpath); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EXDEV, NFS_MSG_LINK_FAIL, + "LINK procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_readdirp_reply(rpcsvc_request_t *req, nfsstat3 stat, struct nfs3_fh *dirfh, + uint64_t cverf, struct iatt *dirstat, gf_dirent_t *entries, + count3 dircount, count3 maxcount, int is_eof) +{ + readdirp3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_readdirp3res(&res, stat, dirfh, cverf, dirstat, entries, dircount, + maxcount, is_eof, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_readdirp3res); + nfs3_free_readdirp3res(&res); + + return 0; +} + +int +nfs3_readdir_reply(rpcsvc_request_t *req, nfsstat3 stat, struct nfs3_fh *dirfh, + uint64_t cverf, struct iatt *dirstat, gf_dirent_t *entries, + count3 count, int is_eof) +{ + readdir3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_readdir3res(&res, stat, dirfh, cverf, dirstat, entries, count, + is_eof, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_readdir3res); + nfs3_free_readdir3res(&res); + + return 0; +} + +int32_t +nfs3svc_readdir_fstat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int is_eof = 0; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto nfs3err; + } + + /* Check whether we encountered a end of directory stream while + * readdir'ing. + */ + if (cs->operrno == ENOENT) { + gf_msg_trace(GF_NFS3, 0, "Reached end-of-directory"); + is_eof = 1; + } + + stat = NFS3_OK; + + /* do inode linking here */ + gf_link_inodes_from_dirent(this, cs->fd->inode, &cs->entries); + +nfs3err: + if (cs->maxcount == 0) { + nfs3_log_readdir_res(rpcsvc_request_xid(cs->req), stat, op_errno, + (uintptr_t)cs->fd, cs->dircount, is_eof, + cs->resolvedloc.path); + nfs3_readdir_reply(cs->req, stat, &cs->parent, (uintptr_t)cs->fd, buf, + &cs->entries, cs->dircount, is_eof); + } else { + nfs3_log_readdirp_res(rpcsvc_request_xid(cs->req), stat, op_errno, + (uintptr_t)cs->fd, cs->dircount, cs->maxcount, + is_eof, cs->resolvedloc.path); + nfs3_readdirp_reply(cs->req, stat, &cs->parent, (uintptr_t)cs->fd, buf, + &cs->entries, cs->dircount, cs->maxcount, is_eof); + } + + if (is_eof) { + /* do nothing */ + } + + nfs3_call_state_wipe(cs); + return 0; +} + +int32_t +nfs3svc_readdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, gf_dirent_t *entries, + dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto err; + } + + cs->operrno = op_errno; + list_splice_init(&entries->list, &cs->entries.list); + nfs_request_user_init(&nfu, cs->req); + ret = nfs_fstat(cs->nfsx, cs->vol, &nfu, cs->fd, nfs3svc_readdir_fstat_cbk, + cs); + if (ret < 0) { + op_ret = -1; + stat = nfs3_errno_to_nfsstat3(-ret); + op_errno = -ret; + } + +err: + if (op_ret >= 0) + goto ret; + + if (cs->maxcount == 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READDIR, stat, + op_errno, cs->resolvedloc.path); + nfs3_readdir_reply(cs->req, stat, NULL, 0, NULL, NULL, 0, 0); + } else { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READDIRP, stat, + op_errno, cs->resolvedloc.path); + nfs3_readdirp_reply(cs->req, stat, NULL, 0, NULL, NULL, 0, 0, 0); + } + + /* For directories, we force a purge from the fd cache on close + * so that next time the dir is read, we'll get any changed directory + * entries. + */ + nfs3_call_state_wipe(cs); +ret: + return 0; +} + +int +nfs3_readdir_process(nfs3_call_state_t *cs) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + + if (!cs) + return ret; + + nfs_request_user_init(&nfu, cs->req); + ret = nfs_readdirp(cs->nfsx, cs->vol, &nfu, cs->fd, cs->dircount, + cs->cookie, nfs3svc_readdir_cbk, cs); + return ret; +} + +int +nfs3_readdir_read_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + struct nfs3_state *nfs3 = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs3 = rpcsvc_request_program_private(cs->req); + ret = nfs3_verify_dircookie(nfs3, cs->fd, cs->cookie, cs->cookieverf, + &stat); + if (ret < 0) /* Stat already set by verifier function above. */ + goto nfs3err; + + ret = nfs3_readdir_process(cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); +nfs3err: + if (ret < 0) { + if (cs->maxcount == 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READDIR, stat, + -ret, cs->resolvedloc.path); + nfs3_readdir_reply(cs->req, stat, NULL, 0, NULL, NULL, 0, 0); + } else { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READDIRP, + stat, -ret, cs->resolvedloc.path); + nfs3_readdirp_reply(cs->req, stat, NULL, 0, NULL, NULL, 0, 0, 0); + } + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int32_t +nfs3svc_readdir_opendir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, + dict_t *xdata) +{ + /* + * We don't really need this, it's just an artifact of forcing the + * opendir to happen. + */ + if (fd) { + fd_unref(fd); + } + + return 0; +} + +int +nfs3_readdir_open_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + cs->fd = fd_anonymous(cs->resolvedloc.inode); + if (!cs->fd) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ANONYMOUS_FD_FAIL, + "Fail to create anonymous fd"); + goto nfs3err; + } + + /* + * NFS client will usually send us a readdirp without an opendir, + * which would cause us to skip our usual self-heal checks which occur + * in opendir for native protocol. To make sure those checks do happen, + * our most reliable option is to do our own opendir for any readdirp + * at the beginning of the directory. + */ + if (cs->cookie == 0) { + nfs_request_user_init(&nfu, cs->req); + ret = nfs_opendir(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_readdir_opendir_cbk, cs); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_DIR_OP_FAIL, + "auto-opendir failed"); + } + } + + ret = nfs3_readdir_read_resume(cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + if (cs->maxcount == 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READDIR, stat, + -ret, cs->resolvedloc.path); + nfs3_readdir_reply(cs->req, stat, NULL, 0, NULL, NULL, 0, 0); + } else { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_READDIRP, + stat, -ret, cs->resolvedloc.path); + nfs3_readdirp_reply(cs->req, stat, NULL, 0, NULL, NULL, 0, 0, 0); + } + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_readdir(rpcsvc_request_t *req, struct nfs3_fh *fh, cookie3 cookie, + uint64_t cverf, count3 dircount, count3 maxcount) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + struct nfs_state *nfs = NULL; + gf_boolean_t is_readdirp = !!maxcount; + + if ((!req) || (!fh)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_readdir_call(rpcsvc_request_xid(req), fh, dircount, maxcount); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + nfs = nfs_state(nfs3->nfsx); + + if (is_readdirp && !nfs->rdirplus) { + ret = -ENOTSUP; + stat = nfs3_errno_to_nfsstat3(-ret); + goto nfs3err; + } + + cs->cookieverf = cverf; + cs->dircount = dircount; + cs->maxcount = maxcount; + cs->cookie = cookie; + cs->parent = *fh; + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_readdir_open_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + if (!is_readdirp) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_READDIR, stat, + -ret, cs ? cs->resolvedloc.path : NULL); + nfs3_readdir_reply(req, stat, NULL, 0, NULL, NULL, 0, 0); + } else { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_READDIRP, stat, + -ret, cs ? cs->resolvedloc.path : NULL); + nfs3_readdirp_reply(req, stat, NULL, 0, NULL, NULL, 0, 0, 0); + } + /* Ret must be NULL after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + nfs3_call_state_wipe(cs); + } +out: + return ret; +} + +int +nfs3svc_readdir(rpcsvc_request_t *req) +{ + readdir3args ra; + struct nfs3_fh fh = { + {0}, + }; + int ret = RPCSVC_ACTOR_ERROR; + uint64_t verf = 0; + uint64_t *cval; + + if (!req) + return ret; + nfs3_prep_readdir3args(&ra, &fh); + if (xdr_to_readdir3args(req->msg[0], &ra) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + cval = (uint64_t *)ra.cookieverf; + verf = *cval; + + ret = nfs3_readdir(req, &fh, ra.cookie, verf, ra.count, 0); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_READDIR_FAIL, + "READDIR procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3svc_readdirp(rpcsvc_request_t *req) +{ + readdirp3args ra; + struct nfs3_fh fh = { + {0}, + }; + int ret = RPCSVC_ACTOR_ERROR; + uint64_t cverf = 0; + uint64_t *cval; + + if (!req) + return ret; + nfs3_prep_readdirp3args(&ra, &fh); + if (xdr_to_readdirp3args(req->msg[0], &ra) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + cval = (uint64_t *)ra.cookieverf; + cverf = *cval; + + ret = nfs3_readdir(req, &fh, ra.cookie, cverf, ra.dircount, ra.maxcount); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_READDIRP_FAIL, + "READDIRP procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_fsstat_reply(rpcsvc_request_t *req, nfsstat3 stat, struct statvfs *fsbuf, + struct iatt *postbuf) +{ + fsstat3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_fsstat3res(&res, stat, fsbuf, postbuf, deviceid); + return nfs3svc_submit_reply(req, &res, + (nfs3_serializer)xdr_serialize_fsstat3res); +} + +int32_t +nfs3_fsstat_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + } else + stat = NFS3_OK; + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_FSSTAT, stat, + op_errno, cs->resolvedloc.path); + nfs3_fsstat_reply(cs->req, stat, &cs->fsstat, buf); + nfs3_call_state_wipe(cs); + return 0; +} + +int32_t +nfs3_fsstat_statfs_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct statvfs *buf, + dict_t *xdata) +{ + nfs_user_t nfu = { + 0, + }; + int ret = -EFAULT; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + ret = -op_errno; + stat = nfs3_cbk_errno_status(op_ret, op_errno); + goto err; + } + + /* Then get the stat for the fs root in order to fill in the + * post_op_attr. + */ + cs->fsstat = *buf; + nfs_request_user_init(&nfu, cs->req); + ret = nfs_stat(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3_fsstat_stat_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_FSSTAT, stat, + -ret, cs->resolvedloc.path); + nfs3_fsstat_reply(cs->req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int +nfs3_fsstat_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nfs_user_t nfu = { + 0, + }; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + /* First, we need to get the statvfs for the subvol */ + ret = nfs_statfs(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3_fsstat_statfs_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_FSSTAT, stat, + -ret, cs->resolvedloc.path); + nfs3_fsstat_reply(cs->req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_fsstat(rpcsvc_request_t *req, struct nfs3_fh *fh) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_common_call(rpcsvc_request_xid(req), "FSSTAT", fh); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_fsstat_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_FSSTAT, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_fsstat_reply(req, stat, NULL, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_fsstat(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + fsstat3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_fsstat3args(&args, &fh); + if (xdr_to_fsstat3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_fsstat(req, &fh); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_FSTAT_FAIL, + "FSTAT procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_fsinfo_reply(rpcsvc_request_t *req, nfsstat3 status, struct iatt *fsroot) +{ + fsinfo3res res; + struct nfs3_state *nfs3 = NULL; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3 = rpcsvc_request_program_private(req); + nfs3_fill_fsinfo3res(nfs3, &res, status, fsroot, deviceid); + + nfs3svc_submit_reply(req, &res, (nfs3_serializer)xdr_serialize_fsinfo3res); + return 0; +} + +int32_t +nfs3svc_fsinfo_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + nfsstat3 status = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + + if (op_ret == -1) { + status = nfs3_cbk_errno_status(op_ret, op_errno); + } else + status = NFS3_OK; + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_FSINFO, status, + op_errno, cs->resolvedloc.path); + + nfs3_fsinfo_reply(cs->req, status, buf); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_fsinfo_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + + ret = nfs_stat(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_fsinfo_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_FSINFO, stat, + -ret, cs->resolvedloc.path); + nfs3_fsinfo_reply(cs->req, stat, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_fsinfo(rpcsvc_request_t *req, struct nfs3_fh *fh) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_common_call(rpcsvc_request_xid(req), "FSINFO", fh); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_fsinfo_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_FSINFO, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_fsinfo_reply(req, stat, NULL); + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_fsinfo(rpcsvc_request_t *req) +{ + int ret = RPCSVC_ACTOR_ERROR; + fsinfo3args args; + struct nfs3_fh root = { + {0}, + }; + + if (!req) + return ret; + + nfs3_prep_fsinfo3args(&args, &root); + if (xdr_to_fsinfo3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding arguments"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_fsinfo(req, &root); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_FSINFO_FAIL, + "FSINFO procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_pathconf_reply(rpcsvc_request_t *req, nfsstat3 stat, struct iatt *buf) +{ + pathconf3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_pathconf3res(&res, stat, buf, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_pathconf3res); + return 0; +} + +int32_t +nfs3svc_pathconf_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) +{ + struct iatt *sbuf = NULL; + nfs3_call_state_t *cs = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + } else { + /* If stat fop failed, we can still send the other components + * in a pathconf reply. + */ + sbuf = buf; + stat = NFS3_OK; + } + + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_PATHCONF, stat, + op_errno, cs->resolvedloc.path); + nfs3_pathconf_reply(cs->req, stat, sbuf); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_pathconf_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + nfs_request_user_init(&nfu, cs->req); + ret = nfs_stat(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, + nfs3svc_pathconf_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_PATHCONF, stat, + -ret, cs->resolvedloc.path); + nfs3_pathconf_reply(cs->req, stat, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_pathconf(rpcsvc_request_t *req, struct nfs3_fh *fh) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_common_call(rpcsvc_request_xid(req), "PATHCONF", fh); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_pathconf_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_PATHCONF, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_pathconf_reply(req, stat, NULL); + nfs3_call_state_wipe(cs); + /* Ret must be 0 after this so that the caller does not + * also send an RPC reply. + */ + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_pathconf(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + pathconf3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_pathconf3args(&args, &fh); + if (xdr_to_pathconf3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_pathconf(req, &fh); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, -ret, NFS_MSG_PATHCONF_FAIL, + "PATHCONF procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +int +nfs3_commit_reply(rpcsvc_request_t *req, nfsstat3 stat, uint64_t wverf, + struct iatt *prestat, struct iatt *poststat) +{ + commit3res res = { + 0, + }; + uint64_t deviceid = 0; + + deviceid = nfs3_request_xlator_deviceid(req); + nfs3_fill_commit3res(&res, stat, wverf, prestat, poststat, deviceid); + nfs3svc_submit_reply(req, (void *)&res, + (nfs3_serializer)xdr_serialize_commit3res); + + return 0; +} + +int32_t +nfs3svc_commit_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + nfs3_call_state_t *cs = NULL; + struct nfs3_state *nfs3 = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nfs3_cbk_errno_status(op_ret, op_errno); + } else + stat = NFS3_OK; + + nfs3 = rpcsvc_request_program_private(cs->req); + nfs3_log_commit_res(rpcsvc_request_xid(cs->req), stat, op_errno, + nfs3->serverstart, cs->resolvedloc.path); + nfs3_commit_reply(cs->req, stat, nfs3->serverstart, NULL, NULL); + nfs3_call_state_wipe(cs); + + return 0; +} + +int +nfs3_commit_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + + if (nfs3_export_sync_trusted(cs->nfs3state, cs->resolvefh.exportid)) { + ret = -1; + stat = NFS3_OK; + goto nfs3err; + } + + nfs_request_user_init(&nfu, cs->req); + ret = nfs_flush(cs->nfsx, cs->vol, &nfu, cs->fd, nfs3svc_commit_cbk, cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_COMMIT, stat, + -ret, cs->resolvedloc.path); + nfs3_commit_reply(cs->req, stat, cs->nfs3state->serverstart, NULL, + NULL); + nfs3_call_state_wipe(cs); + ret = 0; + } + + return 0; +} + +int +nfs3_commit_open_resume(void *carg) +{ + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_resolve_status(cs, stat, nfs3err); + cs->fd = fd_anonymous(cs->resolvedloc.inode); + if (!cs->fd) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ANONYMOUS_FD_FAIL, + "Failed to create anonymous fd."); + goto nfs3err; + } + + ret = nfs3_commit_resume(cs); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(cs->req), NFS3_COMMIT, stat, + -ret, cs->resolvedloc.path); + nfs3_commit_reply(cs->req, stat, 0, NULL, NULL); + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nfs3_commit(rpcsvc_request_t *req, struct nfs3_fh *fh, offset3 offset, + count3 count) +{ + xlator_t *vol = NULL; + nfsstat3 stat = NFS3ERR_SERVERFAULT; + int ret = -EFAULT; + struct nfs3_state *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + + if ((!req) || (!fh)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Bad arguments"); + return -1; + } + + nfs3_log_rw_call(rpcsvc_request_xid(req), "COMMIT", fh, offset, count, -1); + nfs3_validate_gluster_fh(fh, stat, nfs3err); + nfs3_validate_nfs3_state(req, nfs3, stat, nfs3err, ret); + nfs3_map_fh_to_volume(nfs3, fh, req, vol, stat, nfs3err); + nfs3_volume_started_check(nfs3, vol, ret, out); + nfs3_check_rw_volaccess(nfs3, fh->exportid, stat, nfs3err); + nfs3_handle_call_state_init(nfs3, cs, req, vol, stat, nfs3err); + + cs->datacount = count; + cs->dataoffset = offset; + ret = nfs3_fh_resolve_and_resume(cs, fh, NULL, nfs3_commit_open_resume); + if (ret < 0) + stat = nfs3_errno_to_nfsstat3(-ret); + +nfs3err: + if (ret < 0) { + nfs3_log_common_res(rpcsvc_request_xid(req), NFS3_COMMIT, stat, -ret, + cs ? cs->resolvedloc.path : NULL); + nfs3_commit_reply(req, stat, 0, NULL, NULL); + nfs3_call_state_wipe(cs); + ret = 0; + } +out: + return ret; +} + +int +nfs3svc_commit(rpcsvc_request_t *req) +{ + struct nfs3_fh fh = { + {0}, + }; + commit3args args; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + nfs3_prep_commit3args(&args, &fh); + if (xdr_to_commit3args(req->msg[0], &args) <= 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + ret = nfs3_commit(req, &fh, args.offset, args.count); + if ((ret < 0) && (ret != RPCSVC_ACTOR_IGNORE)) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_COMMIT_FAIL, + "COMMIT procedure failed"); + rpcsvc_request_seterr(req, SYSTEM_ERR); + ret = RPCSVC_ACTOR_ERROR; + } + +rpcerr: + return ret; +} + +static rpcsvc_actor_t nfs3svc_actors[NFS3_PROC_COUNT] = { + {"NULL", nfs3svc_null, NULL, NFS3_NULL, DRC_IDEMPOTENT, 0}, + {"GETATTR", nfs3svc_getattr, NULL, NFS3_GETATTR, DRC_IDEMPOTENT, 0}, + {"SETATTR", nfs3svc_setattr, NULL, NFS3_SETATTR, DRC_NON_IDEMPOTENT, 0}, + {"LOOKUP", nfs3svc_lookup, NULL, NFS3_LOOKUP, DRC_IDEMPOTENT, 0}, + {"ACCESS", nfs3svc_access, NULL, NFS3_ACCESS, DRC_IDEMPOTENT, 0}, + {"READLINK", nfs3svc_readlink, NULL, NFS3_READLINK, DRC_IDEMPOTENT, 0}, + {"READ", nfs3svc_read, NULL, NFS3_READ, DRC_IDEMPOTENT, 0}, + {"WRITE", nfs3svc_write, nfs3svc_write_vecsizer, NFS3_WRITE, DRC_IDEMPOTENT, + 0}, + {"CREATE", nfs3svc_create, NULL, NFS3_CREATE, DRC_NON_IDEMPOTENT, 0}, + {"MKDIR", nfs3svc_mkdir, NULL, NFS3_MKDIR, DRC_NON_IDEMPOTENT, 0}, + {"SYMLINK", nfs3svc_symlink, NULL, NFS3_SYMLINK, DRC_NON_IDEMPOTENT, 0}, + {"MKNOD", nfs3svc_mknod, NULL, NFS3_MKNOD, DRC_NON_IDEMPOTENT, 0}, + {"REMOVE", nfs3svc_remove, NULL, NFS3_REMOVE, DRC_NON_IDEMPOTENT, 0}, + {"RMDIR", nfs3svc_rmdir, NULL, NFS3_RMDIR, DRC_NON_IDEMPOTENT, 0}, + {"RENAME", nfs3svc_rename, NULL, NFS3_RENAME, DRC_NON_IDEMPOTENT, 0}, + {"LINK", nfs3svc_link, NULL, NFS3_LINK, DRC_NON_IDEMPOTENT, 0}, + {"READDIR", nfs3svc_readdir, NULL, NFS3_READDIR, DRC_IDEMPOTENT, 0}, + {"READDIRPLUS", nfs3svc_readdirp, NULL, NFS3_READDIRP, DRC_IDEMPOTENT, 0}, + {"FSSTAT", nfs3svc_fsstat, NULL, NFS3_FSSTAT, DRC_IDEMPOTENT, 0}, + {"FSINFO", nfs3svc_fsinfo, NULL, NFS3_FSINFO, DRC_IDEMPOTENT, 0}, + {"PATHCONF", nfs3svc_pathconf, NULL, NFS3_PATHCONF, DRC_IDEMPOTENT, 0}, + {"COMMIT", nfs3svc_commit, NULL, NFS3_COMMIT, DRC_IDEMPOTENT, 0}}; + +static rpcsvc_program_t nfs3prog = { + .progname = "NFS3", + .prognum = NFS_PROGRAM, + .progver = NFS_V3, + .progport = GF_NFS3_PORT, + .actors = nfs3svc_actors, + .numactors = NFS3_PROC_COUNT, + + /* Requests like FSINFO are sent before an auth scheme + * is inited by client. See RFC 2623, Section 2.3.2. */ + .min_auth = AUTH_NULL, +}; + +/* + * This function rounds up the input value to multiple of 4096. Min and Max + * supported I/O size limits are 4KB (GF_NFS3_FILE_IO_SIZE_MIN) and + * 1MB (GF_NFS3_FILE_IO_SIZE_MAX). + */ +void +nfs3_iosize_roundup_4KB(uint64_t *ioszptr) +{ + uint64_t iosize; + uint64_t iopages; + + if (!ioszptr) + return; + + iosize = *ioszptr; + iopages = (iosize + GF_NFS3_IO_SIZE - 1) >> GF_NFS3_IO_SHIFT; + iosize = (iopages * GF_NFS3_IO_SIZE); + + /* Double check - boundary conditions */ + if (iosize < GF_NFS3_FILE_IO_SIZE_MIN) { + iosize = GF_NFS3_FILE_IO_SIZE_MIN; + } else if (iosize > GF_NFS3_FILE_IO_SIZE_MAX) { + iosize = GF_NFS3_FILE_IO_SIZE_MAX; + } + + *ioszptr = iosize; +} + +int +nfs3_init_options(struct nfs3_state *nfs3, dict_t *options) +{ + int ret = -1; + char *optstr = NULL; + uint64_t size64 = 0; + + if ((!nfs3) || (!options)) + return -1; + + /* nfs3.read-size */ + nfs3->readsize = GF_NFS3_RTPREF; + if (dict_get(options, "nfs3.read-size")) { + ret = dict_get_str(options, "nfs3.read-size", &optstr); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: nfs3.read-size"); + ret = -1; + goto err; + } + + ret = gf_string2uint64(optstr, &size64); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_FORMAT_FAIL, + "Failed to format option: nfs3.read-size"); + ret = -1; + goto err; + } + + nfs3_iosize_roundup_4KB(&size64); + nfs3->readsize = size64; + } + + /* nfs3.write-size */ + nfs3->writesize = GF_NFS3_WTPREF; + if (dict_get(options, "nfs3.write-size")) { + ret = dict_get_str(options, "nfs3.write-size", &optstr); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: nfs3.write-size"); + ret = -1; + goto err; + } + + ret = gf_string2uint64(optstr, &size64); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_FORMAT_FAIL, + "Failed to format option: nfs3.write-size"); + ret = -1; + goto err; + } + + nfs3_iosize_roundup_4KB(&size64); + nfs3->writesize = size64; + } + + /* nfs3.readdir.size */ + nfs3->readdirsize = GF_NFS3_DTPREF; + if (dict_get(options, "nfs3.readdir-size")) { + ret = dict_get_str(options, "nfs3.readdir-size", &optstr); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: nfs3.readdir-size"); + ret = -1; + goto err; + } + + ret = gf_string2uint64(optstr, &size64); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_FORMAT_FAIL, + "Failed to format option: nfs3.readdir-size"); + ret = -1; + goto err; + } + + nfs3_iosize_roundup_4KB(&size64); + nfs3->readdirsize = size64; + } + + /* We want to use the size of the biggest param for the io buffer size. + */ + nfs3->iobsize = nfs3->readsize; + if (nfs3->iobsize < nfs3->writesize) + nfs3->iobsize = nfs3->writesize; + if (nfs3->iobsize < nfs3->readdirsize) + nfs3->iobsize = nfs3->readdirsize; + + /* But this is the true size of each iobuf. We need this size to + * accommodate the NFS headers also in the same buffer. */ + nfs3->iobsize = nfs3->iobsize * 2; + + ret = 0; +err: + return ret; +} + +int +nfs3_init_subvolume_options(xlator_t *nfsx, struct nfs3_export *exp, + dict_t *options) +{ + int ret = -1; + char *optstr = NULL; + char searchkey[1024]; + char *name = NULL; + gf_boolean_t boolt = _gf_false; + uuid_t volumeid = { + 0, + }; + + if ((!nfsx) || (!exp)) + return -1; + + /* For init, fetch options from xlator but for + * reconfigure, take the parameter */ + if (!options) + options = nfsx->options; + + if (!options) + return (-1); + + gf_uuid_clear(volumeid); + if (gf_nfs_dvm_off(nfs_state(nfsx))) + goto no_dvm; + + ret = snprintf(searchkey, sizeof(searchkey), "nfs3.%s.volume-id", + exp->subvol->name); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SNPRINTF_FAIL, + "snprintf failed"); + ret = -1; + goto err; + } + + if (dict_get(options, searchkey)) { + ret = dict_get_str(options, searchkey, &optstr); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: %s", searchkey); + ret = -1; + goto err; + } + } else { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_VOLID_MISSING, + "DVM is" + " on but volume-id not given for volume: %s", + exp->subvol->name); + ret = -1; + goto err; + } + + if (optstr) { + ret = gf_uuid_parse(optstr, volumeid); + if (ret < 0) { + gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_PARSE_VOL_UUID_FAIL, + "Failed to parse volume UUID"); + ret = -1; + goto err; + } + gf_uuid_copy(exp->volumeid, volumeid); + } + +no_dvm: + /* Volume Access */ + name = exp->subvol->name; + ret = snprintf(searchkey, sizeof(searchkey), "nfs3.%s.volume-access", name); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SNPRINTF_FAIL, + "snprintf failed"); + ret = -1; + goto err; + } + + exp->access = GF_NFS3_DEFAULT_VOLACCESS; + if (dict_get(options, searchkey)) { + ret = dict_get_str(options, searchkey, &optstr); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: %s", searchkey); + ret = -1; + goto err; + } + + if (strcmp(optstr, "read-only") == 0) + exp->access = GF_NFS3_VOLACCESS_RO; + } + + ret = snprintf(searchkey, sizeof(searchkey), "rpc-auth.%s.unix", name); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SNPRINTF_FAIL, + "snprintf failed"); + ret = -1; + goto err; + } + + if (dict_get(options, searchkey)) { + ret = dict_get_str(options, searchkey, &optstr); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: %s", searchkey); + ret = -1; + goto err; + } + } + + exp->trusted_sync = 0; + ret = snprintf(searchkey, sizeof(searchkey), "nfs3.%s.trusted-sync", name); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SNPRINTF_FAIL, + "snprintf failed"); + ret = -1; + goto err; + } + + if (dict_get(options, searchkey)) { + ret = dict_get_str(options, searchkey, &optstr); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: %s", searchkey); + ret = -1; + goto err; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_STR2BOOL_FAIL, + "Failed to convert str " + "to gf_boolean_t"); + ret = -1; + goto err; + } + + if (boolt == _gf_true) + exp->trusted_sync = 1; + } + + exp->trusted_write = 0; + ret = snprintf(searchkey, sizeof(searchkey), "nfs3.%s.trusted-write", name); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SNPRINTF_FAIL, + "snprintf failed"); + ret = -1; + goto err; + } + + if (dict_get(options, searchkey)) { + ret = dict_get_str(options, searchkey, &optstr); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_READ_FAIL, + "Failed to read option: %s", searchkey); + ret = -1; + goto err; + } + + ret = gf_string2boolean(optstr, &boolt); + if (ret < 0) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_STR2BOOL_FAIL, + "Failed to convert str" + " to gf_boolean_t"); + ret = -1; + goto err; + } + + if (boolt == _gf_true) + exp->trusted_write = 1; + } + + /* If trusted-sync is on, then we also switch on trusted-write because + * tw is included in ts. In write logic, we're then only checking for + * tw. + */ + if (exp->trusted_sync) + exp->trusted_write = 1; + + gf_msg_trace( + GF_NFS3, 0, "%s: %s, %s, %s", exp->subvol->name, + (exp->access == GF_NFS3_VOLACCESS_RO) ? "read-only" : "read-write", + (exp->trusted_sync == 0) ? "no trusted_sync" : "trusted_sync", + (exp->trusted_write == 0) ? "no trusted_write" : "trusted_write"); + ret = 0; +err: + return ret; +} + +struct nfs3_export * +nfs3_init_subvolume(struct nfs3_state *nfs3, xlator_t *subvol) +{ + int ret = -1; + struct nfs3_export *exp = NULL; + + if ((!nfs3) || (!subvol)) + return NULL; + + exp = GF_CALLOC(1, sizeof(*exp), gf_nfs_mt_nfs3_export); + exp->subvol = subvol; + INIT_LIST_HEAD(&exp->explist); + gf_msg_trace(GF_NFS3, 0, "Initing state: %s", exp->subvol->name); + + ret = nfs3_init_subvolume_options(nfs3->nfsx, exp, NULL); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SUBVOL_INIT_FAIL, + "Failed to init subvol"); + goto exp_free; + } + + ret = 0; +exp_free: + if (ret < 0) { + GF_FREE(exp); + exp = NULL; + } + + return exp; +} + +int +nfs3_init_subvolumes(struct nfs3_state *nfs3) +{ + int ret = -1; + struct xlator_list *xl_list = NULL; + struct nfs3_export *exp = NULL; + + if (!nfs3) + return -1; + + xl_list = nfs3->nfsx->children; + + while (xl_list) { + exp = nfs3_init_subvolume(nfs3, xl_list->xlator); + if (!exp) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SUBVOL_INIT_FAIL, + "Failed to init " + "subvol: %s", + xl_list->xlator->name); + goto err; + } + list_add_tail(&exp->explist, &nfs3->exports); + xl_list = xl_list->next; + } + + ret = 0; +err: + return ret; +} + +struct nfs3_state * +nfs3_init_state(xlator_t *nfsx) +{ + struct nfs3_state *nfs3 = NULL; + int ret = -1; + unsigned int localpool = 0; + struct nfs_state *nfs = NULL; + + if ((!nfsx) || (!nfsx->private)) + return NULL; + + nfs3 = (struct nfs3_state *)GF_CALLOC(1, sizeof(*nfs3), + gf_nfs_mt_nfs3_state); + if (!nfs3) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Memory allocation failed"); + return NULL; + } + + nfs = nfsx->private; + ret = nfs3_init_options(nfs3, nfsx->options); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_OPT_INIT_FAIL, + "Failed to init options"); + goto ret; + } + + nfs3->iobpool = nfsx->ctx->iobuf_pool; + + localpool = nfs->memfactor * GF_NFS_CONCURRENT_OPS_MULT; + gf_msg_trace(GF_NFS3, 0, "local pool: %d", localpool); + nfs3->localpool = mem_pool_new_ctx(nfsx->ctx, nfs3_call_state_t, localpool); + if (!nfs3->localpool) { + gf_msg(GF_NFS3, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "local mempool creation failed"); + ret = -1; + goto ret; + } + + nfs3->nfsx = nfsx; + nfs3->exportslist = nfsx->children; + INIT_LIST_HEAD(&nfs3->exports); + ret = nfs3_init_subvolumes(nfs3); + if (ret == -1) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_SUBVOL_INIT_FAIL, + "Failed to init per-subvolume state"); + goto free_localpool; + } + + nfs3->serverstart = (uint64_t)gf_time(); + INIT_LIST_HEAD(&nfs3->fdlru); + LOCK_INIT(&nfs3->fdlrulock); + nfs3->fdcount = 0; + + ret = rpcsvc_create_listeners(nfs->rpcsvc, nfsx->options, nfsx->name); + if (ret == -1) { + gf_msg(GF_NFS, GF_LOG_ERROR, 0, NFS_MSG_LISTENERS_CREATE_FAIL, + "Unable to create listeners"); + goto free_localpool; + } + + nfs->nfs3state = nfs3; + ret = 0; + +free_localpool: + if (ret == -1) + mem_pool_destroy(nfs3->localpool); + +ret: + if (ret == -1) { + GF_FREE(nfs3); + nfs3 = NULL; + } + + return nfs3; +} + +rpcsvc_program_t * +nfs3svc_init(xlator_t *nfsx) +{ + struct nfs3_state *nfs3 = NULL; + + if (!nfsx) + return NULL; + + nfs3 = nfs3_init_state(nfsx); + if (!nfs3) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_STATE_INIT_FAIL, + "NFSv3 state init failed"); + return NULL; + } + + nfs3prog.private = nfs3; + + return &nfs3prog; +} + +int +nfs3_reconfigure_state(xlator_t *nfsx, dict_t *options) +{ + int ret = -1; + struct nfs3_export *exp = NULL; + struct nfs_state *nfs = NULL; + struct nfs3_state *nfs3 = NULL; + + if ((!nfsx) || (!nfsx->private) || (!options)) + goto out; + + nfs = (struct nfs_state *)nfsx->private; + nfs3 = nfs->nfs3state; + if (!nfs3) + goto out; + + ret = nfs3_init_options(nfs3, options); + if (ret) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_RECONF_FAIL, + "Failed to reconfigure options"); + goto out; + } + + list_for_each_entry(exp, &nfs3->exports, explist) + { + ret = nfs3_init_subvolume_options(nfsx, exp, options); + if (ret) { + gf_msg(GF_NFS3, GF_LOG_ERROR, 0, NFS_MSG_RECONF_SUBVOL_FAIL, + "Failed to reconfigure subvol options"); + goto out; + } + } + + ret = 0; +out: + return ret; +} diff --git a/xlators/nfs/server/src/nfs3.h b/xlators/nfs/server/src/nfs3.h new file mode 100644 index 00000000000..cdb7e03a619 --- /dev/null +++ b/xlators/nfs/server/src/nfs3.h @@ -0,0 +1,292 @@ +/* + Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NFS3_H_ +#define _NFS3_H_ + +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include <glusterfs/iobuf.h> +#include "nfs.h" +#include "nfs3-fh.h" +#include "nfs-common.h" +#include "xdr-nfs3.h" +#include <glusterfs/mem-pool.h> +#include "nlm4.h" +#include "acl3-xdr.h" +#include "acl3.h" +#include <glusterfs/refcount.h> +#include <sys/statvfs.h> + +#define GF_NFS3 GF_NFS "-nfsv3" + +#define GF_NFS3_DEFAULT_MEMFACTOR 15 +#define GF_NFS3_IOBPOOL_MULT GF_NFS_CONCURRENT_OPS_MULT +#define GF_NFS3_CLTABLE_BUCKETS_MULT 2 +#define GF_NFS3_FDTABLE_BUCKETS_MULT 2 + +/* Static values used for FSINFO + * To change the maximum rsize and wsize supported by the NFS client, adjust + * GF_NFS3_FILE_IO_SIZE_MAX. The Gluster NFS server defaults to 1MB(1048576) + * (same as kernel NFS server). For slower network, rsize/wsize can be trimmed + * to 16/32/64-KB. rsize and wsize can be tuned through nfs.read-size and + * nfs.write-size respectively. + * + * NB: For Kernel-NFS, NFS_MAX_FILE_IO_SIZE is 1048576U (1MB). + */ +#define GF_NFS3_FILE_IO_SIZE_MAX (1 * GF_UNIT_MB) /* 1048576 */ +#define GF_NFS3_FILE_IO_SIZE_MIN (4 * GF_UNIT_KB) /* 4096 */ + +#define GF_NFS3_FILE_IO_SIZE_DEF GF_NFS3_FILE_IO_SIZE_MAX + +#define GF_NFS3_RTMAX GF_NFS3_FILE_IO_SIZE_MAX +#define GF_NFS3_RTMIN GF_NFS3_FILE_IO_SIZE_MIN +#define GF_NFS3_RTPREF GF_NFS3_FILE_IO_SIZE_DEF +#define GF_NFS3_RTMULT GF_NFS3_FILE_IO_SIZE_MIN + +#define GF_NFS3_WTMAX GF_NFS3_FILE_IO_SIZE_MAX +#define GF_NFS3_WTMIN GF_NFS3_FILE_IO_SIZE_MIN +#define GF_NFS3_WTPREF GF_NFS3_FILE_IO_SIZE_DEF +#define GF_NFS3_WTMULT GF_NFS3_FILE_IO_SIZE_MIN + +/* This can be tuned through nfs.readdir-size */ +#define GF_NFS3_DTMAX GF_NFS3_FILE_IO_SIZE_MAX +#define GF_NFS3_DTMIN GF_NFS3_FILE_IO_SIZE_MIN +#define GF_NFS3_DTPREF GF_NFS3_FILE_IO_SIZE_DEF + +#define GF_NFS3_MAXFILESIZE (1 * GF_UNIT_PB) + +#define GF_NFS3_IO_SIZE 4096 /* 4-KB */ +#define GF_NFS3_IO_SHIFT 12 /* 2^12 = 4KB */ + +/* FIXME: Handle time resolutions */ +#define GF_NFS3_TIMEDELTA_SECS \ + { \ + 1, 0 \ + } +#define GF_NFS3_TIMEDELTA_NSECS \ + { \ + 0, 1 \ + } +#define GF_NFS3_TIMEDELTA_MSECS \ + { \ + 0, 1000000 \ + } + +#define GF_NFS3_FS_PROP \ + (FSF3_LINK | FSF3_SYMLINK | FSF3_HOMOGENEOUS | FSF3_CANSETTIME) + +#define GF_NFS3_DIRFD_VALID 1 +#define GF_NFS3_DIRFD_INVALID 0 + +#define GF_NFS3_VOLACCESS_RW 1 +#define GF_NFS3_VOLACCESS_RO 2 + +#define GF_NFS3_FDCACHE_SIZE 512 +/* This should probably be moved to a more generic layer so that if needed + * different versions of NFS protocol can use the same thing. + */ +struct nfs3_fd_entry { + fd_t *cachedfd; + struct list_head list; +}; + +/* Per subvolume nfs3 specific state */ +struct nfs3_export { + struct list_head explist; + xlator_t *subvol; + uuid_t volumeid; + int access; + int trusted_sync; + int trusted_write; + int rootlookedup; +}; + +#define GF_NFS3_DEFAULT_VOLACCESS (GF_NFS3_VOLACCESS_RW) + +/* The NFSv3 protocol state */ +typedef struct nfs3_state { + /* The NFS xlator pointer. The NFS xlator can be running + * multiple versions of the NFS protocol. + */ + xlator_t *nfsx; + + /* The iob pool from which memory allocations are made for receiving + * and sending network messages. + */ + struct iobuf_pool *iobpool; + + /* List of child subvolumes for the NFSv3 protocol. + * Right now, is simply referring to the list of children in nfsx above. + */ + xlator_list_t *exportslist; + + struct list_head exports; + /* Mempool for allocations of struct nfs3_local */ + struct mem_pool *localpool; + + /* Server start-up timestamp, currently used for write verifier. */ + uint64_t serverstart; + + /* NFSv3 Protocol configurables */ + uint64_t readsize; + uint64_t writesize; + uint64_t readdirsize; + + /* Size of the iobufs used, depends on the sizes of the three params + * above. + */ + uint64_t iobsize; + + struct list_head fdlru; + gf_lock_t fdlrulock; + int fdcount; + uint32_t occ_logger; +} nfs3_state_t; + +typedef enum nfs3_lookup_type { + GF_NFS3_REVALIDATE = 1, + GF_NFS3_FRESH, +} nfs3_lookup_type_t; + +typedef union args_ { + nlm4_stat nlm4_stat; + nlm4_holder nlm4_holder; + nlm4_lock nlm4_lock; + nlm4_share nlm4_share; + nlm4_testrply nlm4_testrply; + nlm4_testres nlm4_testres; + nlm4_testargs nlm4_testargs; + nlm4_res nlm4_res; + nlm4_lockargs nlm4_lockargs; + nlm4_cancargs nlm4_cancargs; + nlm4_unlockargs nlm4_unlockargs; + nlm4_shareargs nlm4_shareargs; + nlm4_shareres nlm4_shareres; + nlm4_freeallargs nlm4_freeallargs; + getaclargs getaclargs; + setaclargs setaclargs; + getaclreply getaclreply; + setaclreply setaclreply; +} args; + +typedef int (*nfs3_resume_fn_t)(void *cs); +/* Structure used to communicate state between a fop and its callback. + * Not all members are used at all times. Usage is fop and NFS request + * dependent. + * + * I wish we could have a smaller structure for communicating state + * between callers and callbacks. It could be broken into smaller parts + * but I feel that will lead to a proliferation of types/structures and then + * we'll just be tracking down which structure is used by which fop, not + * to mention that having one type allows me to used a single mem-pool. + * Imagine the chaos if we need a mem-pool for each one of those sub-structures. + */ +struct nfs3_local { + GF_REF_DECL; + + rpcsvc_request_t *req; + xlator_t *vol; + nfs3_resume_fn_t resume_fn; + xlator_t *nfsx; + struct nfs3_state *nfs3state; + + /* The list hook to attach this call state to the inode's queue till + * the opening of the fd on the inode completes. + */ + struct list_head openwait_q; + + /* Per-NFSv3 Op state */ + struct nfs3_fh parent; + struct nfs3_fh fh; + fd_t *fd; + uint32_t accessbits; + int operrno; + count3 dircount; + count3 maxcount; + struct statvfs fsstat; + gf_dirent_t entries; + struct iatt stbuf; + struct iatt preparent; + struct iatt postparent; + int32_t setattr_valid; + nfstime3 timestamp; + loc_t oploc; + int writetype; + count3 datacount; + offset3 dataoffset; + struct iobuf *iob; + struct iobref *iobref; + createmode3 createmode; + uint64_t cookieverf; + int sattrguardcheck; + char *pathname; + ftype3 mknodtype; + specdata3 devnums; + cookie3 cookie; + struct iovec datavec; + mode_t mode; + struct iatt attr_in; + + /* NFSv3 FH resolver state */ + int hardresolved; + struct nfs3_fh resolvefh; + loc_t resolvedloc; + int resolve_ret; + int resolve_errno; + int hashidx; + fd_t *resolve_dir_fd; + char *resolventry; + nfs3_lookup_type_t lookuptype; + gf_dirent_t *hashmatch; + gf_dirent_t *entrymatch; + off_t lastentryoffset; + struct flock flock; + args args; + nlm4_lkowner_t lkowner; + char cookiebytes[1024]; + struct nfs3_fh lockfh; + int monitor; + rpc_transport_t *trans; + call_frame_t *frame; + + /* ACL */ + aclentry aclentry[NFS_ACL_MAX_ENTRIES]; + aclentry daclentry[NFS_ACL_MAX_ENTRIES]; + int aclcount; + char aclxattr[NFS_ACL_MAX_ENTRIES * 8 + 4]; + int daclcount; + char daclxattr[NFS_ACL_MAX_ENTRIES * 8 + 4]; +}; + +#define nfs3_is_revalidate_lookup(cst) ((cst)->lookuptype == GF_NFS3_REVALIDATE) +#define nfs3_lookup_op(cst) (rpcsvc_request_procnum(cst->req) == NFS3_LOOKUP) +#define nfs3_create_op(cst) (rpcsvc_request_procnum(cst->req) == NFS3_CREATE) +#define nfs3_create_exclusive_op(cst) ((cst)->createmode == EXCLUSIVE) + +typedef struct nfs3_local nfs3_call_state_t; + +/* Queue of ops waiting for open fop to return. */ +struct inode_op_queue { + struct list_head opq; + pthread_mutex_t qlock; +}; + +extern rpcsvc_program_t * +nfs3svc_init(xlator_t *nfsx); + +extern int +nfs3_reconfigure_state(xlator_t *nfsx, dict_t *options); + +extern uint64_t +nfs3_request_xlator_deviceid(rpcsvc_request_t *req); + +#endif diff --git a/xlators/nfs/server/src/nfsserver.sym b/xlators/nfs/server/src/nfsserver.sym new file mode 100644 index 00000000000..dce7d964e9e --- /dev/null +++ b/xlators/nfs/server/src/nfsserver.sym @@ -0,0 +1,12 @@ +exp_file_parse +exp_file_print +exp_file_get_dir +exp_dir_get_host +exp_dir_get_netgroup +exp_file_dir_from_uuid +exp_file_deinit +ng_file_parse +ng_file_get_netgroup +ng_file_print +ng_file_deinit +xlator_api diff --git a/xlators/nfs/server/src/nlm4.c b/xlators/nfs/server/src/nlm4.c new file mode 100644 index 00000000000..577e8543966 --- /dev/null +++ b/xlators/nfs/server/src/nlm4.c @@ -0,0 +1,2786 @@ +/* + Copyright (c) 2012 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#include <glusterfs/defaults.h> +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include "nfs.h" +#include <glusterfs/mem-pool.h> +#include <glusterfs/logging.h> +#include <glusterfs/syscall.h> +#include "nfs-fops.h" +#include "mount3.h" +#include "nfs3.h" +#include "nfs-mem-types.h" +#include "nfs3-helpers.h" +#include "nfs3-fh.h" +#include "nlm4.h" +#include "nlm4-xdr.h" +#include "msg-nfs3.h" +#include "nfs-generics.h" +#include "rpc-clnt.h" +#include "nsm-xdr.h" +#include <glusterfs/run.h> +#include "nfs-messages.h" +#include <unistd.h> +#include <rpc/pmap_clnt.h> +#include <rpc/rpc.h> +#include <rpc/xdr.h> +#include <glusterfs/statedump.h> + +#define KILLALL_CMD "pkill" + +/* TODO: + * 1) 2 opens racing .. creating an fd leak. + * 2) use GF_REF_* for nlm_clnt_t + */ + +typedef ssize_t (*nlm4_serializer)(struct iovec outmsg, void *args); + +extern void +nfs3_call_state_wipe(nfs3_call_state_t *cs); + +nfs3_call_state_t * +nfs3_call_state_init(struct nfs3_state *s, rpcsvc_request_t *req, xlator_t *v); + +struct list_head nlm_client_list; +gf_lock_t nlm_client_list_lk; + +/* race on this is harmless */ +int nlm_grace_period = 50; + +static gf_boolean_t nlm4_inited = _gf_false; + +#define nlm4_validate_nfs3_state(request, state, status, label, retval) \ + do { \ + state = rpcsvc_request_program_private(request); \ + if (!state) { \ + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_STATE_MISSING, \ + "NFSv3 state " \ + "missing from RPC request"); \ + rpcsvc_request_seterr(req, SYSTEM_ERR); \ + status = nlm4_failed; \ + goto label; \ + } \ + } while (0); + +#define nlm4_handle_call_state_init(nfs3state, calls, rq, opstat, errlabel) \ + do { \ + calls = nlm4_call_state_init((nfs3state), (rq)); \ + if (!calls) { \ + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_INIT_CALL_STAT_FAIL, \ + "Failed to " \ + "init call state"); \ + opstat = nlm4_failed; \ + rpcsvc_request_seterr(req, SYSTEM_ERR); \ + goto errlabel; \ + } \ + } while (0) + +#define nlm4_validate_gluster_fh(handle, status, errlabel) \ + do { \ + if (!nfs3_fh_validate(handle)) { \ + status = nlm4_stale_fh; \ + goto errlabel; \ + } \ + } while (0) + +xlator_t * +nfs3_fh_to_xlator(struct nfs3_state *nfs3, struct nfs3_fh *fh); + +#define nlm4_map_fh_to_volume(nfs3state, handle, req, volume, status, label) \ + do { \ + char exportid[256], gfid[256]; \ + rpc_transport_t *trans = NULL; \ + volume = nfs3_fh_to_xlator((nfs3state), &handle); \ + if (!volume) { \ + gf_uuid_unparse(handle.exportid, exportid); \ + gf_uuid_unparse(handle.gfid, gfid); \ + trans = rpcsvc_request_transport(req); \ + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_FH_TO_VOL_FAIL, \ + "Failed to map " \ + "FH to vol: client=%s, exportid=%s, gfid=%s", \ + trans->peerinfo.identifier, exportid, gfid); \ + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_VOLUME_ERROR, \ + "Stale nfs client %s must be trying to " \ + "connect to a deleted volume, please " \ + "unmount it.", \ + trans->peerinfo.identifier); \ + status = nlm4_stale_fh; \ + goto label; \ + } else { \ + gf_msg_trace(GF_NLM, 0, "FH to Volume: %s", volume->name); \ + rpcsvc_request_set_private(req, volume); \ + } \ + } while (0); + +#define nlm4_volume_started_check(nfs3state, vlm, rtval, erlbl) \ + do { \ + if ((!nfs_subvolume_started(nfs_state(nfs3state->nfsx), vlm))) { \ + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_VOL_DISABLE, \ + "Volume is disabled: %s", vlm->name); \ + rtval = RPCSVC_ACTOR_IGNORE; \ + goto erlbl; \ + } \ + } while (0) + +#define nlm4_check_fh_resolve_status(cst, nfstat, erlabl) \ + do { \ + xlator_t *xlatorp = NULL; \ + char buf[256], gfid[GF_UUID_BUF_SIZE]; \ + rpc_transport_t *trans = NULL; \ + if ((cst)->resolve_ret < 0) { \ + trans = rpcsvc_request_transport(cst->req); \ + xlatorp = nfs3_fh_to_xlator(cst->nfs3state, &cst->resolvefh); \ + gf_uuid_unparse(cst->resolvefh.gfid, gfid); \ + snprintf(buf, sizeof(buf), "(%s) %s : %s", \ + trans->peerinfo.identifier, \ + xlatorp ? xlatorp->name : "ERR", gfid); \ + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_RESOLVE_FH_FAIL, \ + "Unable to resolve FH" \ + ": %s", \ + buf); \ + nfstat = nlm4_errno_to_nlm4stat(cst->resolve_errno); \ + goto erlabl; \ + } \ + } while (0) + +void +nlm4_prep_nlm4_testargs(nlm4_testargs *args, struct nfs3_fh *fh, + nlm4_lkowner_t *oh, char *cookiebytes) +{ + memset(args, 0, sizeof(*args)); + args->alock.fh.nlm4_netobj_val = (void *)fh; + args->alock.oh.nlm4_netobj_val = (void *)oh; + args->cookie.nlm4_netobj_val = (void *)cookiebytes; +} + +void +nlm4_prep_nlm4_lockargs(nlm4_lockargs *args, struct nfs3_fh *fh, + nlm4_lkowner_t *oh, char *cookiebytes) +{ + memset(args, 0, sizeof(*args)); + args->alock.fh.nlm4_netobj_val = (void *)fh; + args->alock.oh.nlm4_netobj_val = (void *)oh; + args->cookie.nlm4_netobj_val = (void *)cookiebytes; +} + +void +nlm4_prep_nlm4_cancargs(nlm4_cancargs *args, struct nfs3_fh *fh, + nlm4_lkowner_t *oh, char *cookiebytes) +{ + memset(args, 0, sizeof(*args)); + args->alock.fh.nlm4_netobj_val = (void *)fh; + args->alock.oh.nlm4_netobj_val = (void *)oh; + args->cookie.nlm4_netobj_val = (void *)cookiebytes; +} + +void +nlm4_prep_nlm4_unlockargs(nlm4_unlockargs *args, struct nfs3_fh *fh, + nlm4_lkowner_t *oh, char *cookiebytes) +{ + memset(args, 0, sizeof(*args)); + args->alock.fh.nlm4_netobj_val = (void *)fh; + args->alock.oh.nlm4_netobj_val = (void *)oh; + args->cookie.nlm4_netobj_val = (void *)cookiebytes; +} + +void +nlm4_prep_shareargs(nlm4_shareargs *args, struct nfs3_fh *fh, + nlm4_lkowner_t *oh, char *cookiebytes) +{ + memset(args, 0, sizeof(*args)); + args->share.fh.nlm4_netobj_val = (void *)fh; + args->share.oh.nlm4_netobj_val = (void *)oh; + args->cookie.nlm4_netobj_val = (void *)cookiebytes; +} + +void +nlm4_prep_freeallargs(nlm4_freeallargs *args, nlm4_lkowner_t *oh) +{ + memset(args, 0, sizeof(*args)); + args->name = (void *)oh; +} + +void +nlm_copy_lkowner(gf_lkowner_t *dst, nlm4_netobj *src) +{ + dst->len = src->nlm4_netobj_len; + memcpy(dst->data, src->nlm4_netobj_val, dst->len); +} + +int +nlm_is_oh_same_lkowner(gf_lkowner_t *a, nlm4_netobj *b) +{ + if (!a || !b) { + gf_msg(GF_NLM, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "invalid args"); + return -1; + } + + return (a->len == b->nlm4_netobj_len && + !memcmp(a->data, b->nlm4_netobj_val, a->len)); +} + +nlm4_stats +nlm4_errno_to_nlm4stat(int errnum) +{ + nlm4_stats stat = nlm4_denied; + + switch (errnum) { + case 0: + stat = nlm4_granted; + break; + case EROFS: + stat = nlm4_rofs; + break; + case ESTALE: + stat = nlm4_stale_fh; + break; + case ENOLCK: + stat = nlm4_failed; + break; + default: + stat = nlm4_denied; + break; + } + + return stat; +} + +nfs3_call_state_t * +nlm4_call_state_init(struct nfs3_state *s, rpcsvc_request_t *req) +{ + nfs3_call_state_t *cs = NULL; + + if ((!s) || (!req)) + return NULL; + + cs = nfs3_call_state_init(s, req, NULL); + if (!cs) + return NULL; + + cs->monitor = 1; + + return cs; +} + +int +nlm_monitor(char *caller_name) +{ + nlm_client_t *nlmclnt = NULL; + int monitor = -1; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) { + monitor = nlmclnt->nsm_monitor; + nlmclnt->nsm_monitor = 1; + break; + } + } + UNLOCK(&nlm_client_list_lk); + + if (monitor == -1) + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_CALLER_NOT_FOUND, + "%s was not found in the nlmclnt list", caller_name); + + return monitor; +} + +rpc_clnt_t * +nlm_get_rpc_clnt(char *caller_name) +{ + nlm_client_t *nlmclnt = NULL; + int nlmclnt_found = 0; + rpc_clnt_t *rpc_clnt = NULL; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) { + nlmclnt_found = 1; + break; + } + } + if (!nlmclnt_found) + goto ret; + if (nlmclnt->rpc_clnt) + rpc_clnt = rpc_clnt_ref(nlmclnt->rpc_clnt); +ret: + UNLOCK(&nlm_client_list_lk); + return rpc_clnt; +} + +static void +nlm_client_free(nlm_client_t *nlmclnt) +{ + nlm_fde_t *fde = NULL, *tmp = NULL; + + gf_msg_trace(GF_NLM, 0, "removing nlm-client %s from the list", + nlmclnt->caller_name); + + list_for_each_entry_safe(fde, tmp, &nlmclnt->fdes, fde_list) + { + fd_unref(fde->fd); + list_del(&fde->fde_list); + GF_FREE(fde); + } + + list_del(&nlmclnt->fdes); + list_del(&nlmclnt->nlm_clients); + list_del(&nlmclnt->shares); + + GF_FREE(nlmclnt->caller_name); + + if (nlmclnt->rpc_clnt) { + /* cleanup the saved-frames before last unref */ + rpc_clnt_connection_cleanup(&nlmclnt->rpc_clnt->conn); + /* rpc_clnt_connection_cleanup() calls rpc_clnt_unref() */ + } + + GF_FREE(nlmclnt); +} + +int +nlm_set_rpc_clnt(rpc_clnt_t *rpc_clnt, char *caller_name) +{ + nlm_client_t *nlmclnt = NULL; + int nlmclnt_found = 0; + int ret = -1; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) { + nlmclnt_found = 1; + break; + } + } + + if (!nlmclnt_found) { + nlmclnt = GF_CALLOC(1, sizeof(*nlmclnt), gf_nfs_mt_nlm4_nlmclnt); + if (nlmclnt == NULL) + goto ret; + + INIT_LIST_HEAD(&nlmclnt->fdes); + INIT_LIST_HEAD(&nlmclnt->nlm_clients); + INIT_LIST_HEAD(&nlmclnt->shares); + + list_add(&nlmclnt->nlm_clients, &nlm_client_list); + nlmclnt->caller_name = gf_strdup(caller_name); + } + + if (nlmclnt->rpc_clnt == NULL) { + nlmclnt->rpc_clnt = rpc_clnt_ref(rpc_clnt); + } + ret = 0; +ret: + UNLOCK(&nlm_client_list_lk); + return ret; +} + +int +nlm_unset_rpc_clnt(rpc_clnt_t *rpc) +{ + nlm_client_t *nlmclnt = NULL; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (rpc == nlmclnt->rpc_clnt) { + nlm_client_free(nlmclnt); + break; + } + } + UNLOCK(&nlm_client_list_lk); + + return 0; +} + +int +nlm_add_nlmclnt(char *caller_name) +{ + nlm_client_t *nlmclnt = NULL; + int nlmclnt_found = 0; + int ret = -1; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) { + nlmclnt_found = 1; + break; + } + } + if (!nlmclnt_found) { + nlmclnt = GF_CALLOC(1, sizeof(*nlmclnt), gf_nfs_mt_nlm4_nlmclnt); + if (nlmclnt == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "malloc error"); + goto ret; + } + + INIT_LIST_HEAD(&nlmclnt->fdes); + INIT_LIST_HEAD(&nlmclnt->nlm_clients); + INIT_LIST_HEAD(&nlmclnt->shares); + + list_add(&nlmclnt->nlm_clients, &nlm_client_list); + nlmclnt->caller_name = gf_strdup(caller_name); + } + ret = 0; +ret: + UNLOCK(&nlm_client_list_lk); + return ret; +} + +int +nlm4svc_submit_reply(rpcsvc_request_t *req, void *arg, nlm4_serializer sfunc) +{ + struct iovec outmsg = { + 0, + }; + struct iobuf *iob = NULL; + struct nfs3_state *nfs3 = NULL; + int ret = -1; + ssize_t msglen = 0; + struct iobref *iobref = NULL; + + if (!req) + return -1; + + nfs3 = (struct nfs3_state *)rpcsvc_request_program_private(req); + if (!nfs3) { + gf_msg(GF_NLM, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND, + "mount state not found"); + goto ret; + } + + /* First, get the io buffer into which the reply in arg will + * be serialized. + */ + iob = iobuf_get(nfs3->iobpool); + if (!iob) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobuf"); + goto ret; + } + + iobuf_to_iovec(iob, &outmsg); + /* Use the given serializer to translate the give C structure in arg + * to XDR format which will be written into the buffer in outmsg. + */ + msglen = sfunc(outmsg, arg); + if (msglen < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ENCODE_MSG_FAIL, + "Failed to encode message"); + goto ret; + } + outmsg.iov_len = msglen; + + iobref = iobref_new(); + if (iobref == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobref"); + goto ret; + } + + ret = iobref_add(iobref, iob); + if (ret) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to add iob to iobref"); + goto ret; + } + + /* Then, submit the message for transmission. */ + ret = rpcsvc_submit_message(req, &outmsg, 1, NULL, 0, iobref); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_REP_SUBMIT_FAIL, + "Reply submission failed"); + goto ret; + } + + ret = 0; +ret: + if (iob) + iobuf_unref(iob); + if (iobref) + iobref_unref(iobref); + + return ret; +} + +typedef int (*nlm4_resume_fn_t)(void *cs); + +int32_t +nlm4_file_open_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, dict_t *xdata) +{ + nfs3_call_state_t *cs = frame->local; + + if (op_ret == 0) + fd_bind(cs->fd); + cs->resolve_ret = op_ret; + cs->resume_fn(cs); + + frame->local = NULL; + STACK_DESTROY(frame->root); + GF_REF_PUT(cs); + + return 0; +} + +void * +nsm_monitor(void *arg) +{ + CLIENT *clnt = NULL; + enum clnt_stat ret; + struct mon nsm_mon; + struct sm_stat_res res; + struct timeval tout = {5, 0}; + char *host = NULL; + + host = arg; + nsm_mon.mon_id.mon_name = gf_strdup(host); + nsm_mon.mon_id.my_id.my_name = gf_strdup("localhost"); + nsm_mon.mon_id.my_id.my_prog = NLMCBK_PROGRAM; + nsm_mon.mon_id.my_id.my_vers = NLMCBK_V1; + nsm_mon.mon_id.my_id.my_proc = NLMCBK_SM_NOTIFY; + /* nothing to put in the private data */ +#define SM_PROG 100024 +#define SM_VERS 1 +#define SM_MON 2 + + /* create a connection to nsm on the localhost */ + clnt = clnt_create("localhost", SM_PROG, SM_VERS, "tcp"); + if (!clnt) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_CLNT_CREATE_ERROR, "%s", + clnt_spcreateerror("Clnt_create()")); + goto out; + } + + ret = clnt_call(clnt, SM_MON, (xdrproc_t)xdr_mon, (caddr_t)&nsm_mon, + (xdrproc_t)xdr_sm_stat_res, (caddr_t)&res, tout); + if (ret != RPC_SUCCESS) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_CLNT_CALL_ERROR, + "clnt_call(): %s", clnt_sperrno(ret)); + goto out; + } + if (res.res_stat != STAT_SUCC) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_CLNT_CALL_ERROR, + "clnt_call(): %s", clnt_sperrno(ret)); + goto out; + } + +out: + GF_FREE(nsm_mon.mon_id.mon_name); + GF_FREE(nsm_mon.mon_id.my_id.my_name); + if (clnt != NULL) + clnt_destroy(clnt); + return NULL; +} + +nlm_client_t * +__nlm_get_uniq(char *caller_name) +{ + nlm_client_t *nlmclnt = NULL; + + if (!caller_name) + return NULL; + + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) + return nlmclnt; + } + + return NULL; +} + +nlm_client_t * +nlm_get_uniq(char *caller_name) +{ + nlm_client_t *nlmclnt = NULL; + + LOCK(&nlm_client_list_lk); + nlmclnt = __nlm_get_uniq(caller_name); + UNLOCK(&nlm_client_list_lk); + + return nlmclnt; +} + +int +nlm4_file_open_and_resume(nfs3_call_state_t *cs, nlm4_resume_fn_t resume) +{ + fd_t *fd = NULL; + int ret = -1; + int flags = 0; + nlm_client_t *nlmclnt = NULL; + call_frame_t *frame = NULL; + + if (cs->args.nlm4_lockargs.exclusive == _gf_false) + flags = O_RDONLY; + else + flags = O_WRONLY; + + nlmclnt = nlm_get_uniq(cs->args.nlm4_lockargs.alock.caller_name); + if (nlmclnt == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOLCK, NFS_MSG_NO_MEMORY, + "nlm_get_uniq() " + "returned NULL"); + ret = -ENOLCK; + goto err; + } + cs->resume_fn = resume; + fd = fd_lookup_uint64(cs->resolvedloc.inode, (uint64_t)(uintptr_t)nlmclnt); + if (fd) { + cs->fd = fd; + cs->resolve_ret = 0; + cs->resume_fn(cs); + ret = 0; + goto err; + } + + fd = fd_create_uint64(cs->resolvedloc.inode, (uint64_t)(uintptr_t)nlmclnt); + if (fd == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOLCK, NFS_MSG_NO_MEMORY, + "fd_create_uint64() returned NULL"); + ret = -ENOLCK; + goto err; + } + + cs->fd = fd; + + frame = create_frame(cs->nfsx, cs->nfsx->ctx->pool); + if (!frame) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "unable to create frame"); + ret = -ENOMEM; + goto err; + } + + frame->root->pid = NFS_PID; + frame->root->uid = rpcsvc_request_uid(cs->req); + frame->root->gid = rpcsvc_request_gid(cs->req); + frame->local = GF_REF_GET(cs); + nfs_fix_groups(cs->nfsx, frame->root); + + STACK_WIND_COOKIE(frame, nlm4_file_open_cbk, cs->vol, cs->vol, + cs->vol->fops->open, &cs->resolvedloc, flags, cs->fd, + NULL); + ret = 0; +err: + return ret; +} + +int +nlm4_generic_reply(rpcsvc_request_t *req, nlm4_netobj cookie, nlm4_stats stat) +{ + nlm4_res res; + + memset(&res, 0, sizeof(res)); + res.cookie = cookie; + res.stat.stat = stat; + + nlm4svc_submit_reply(req, (void *)&res, + (nlm4_serializer)xdr_serialize_nlm4_res); + return 0; +} + +int +nlm4svc_null(rpcsvc_request_t *req) +{ + struct iovec dummyvec = { + 0, + }; + + if (!req) { + gf_msg(GF_NLM, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "Got NULL request!"); + return 0; + } + rpcsvc_submit_generic(req, &dummyvec, 1, NULL, 0, NULL); + return 0; +} + +int +nlm4_gf_flock_to_holder(nlm4_holder *holder, struct gf_flock *flock) +{ + switch (flock->l_type) { + case GF_LK_F_WRLCK: + holder->exclusive = 1; + break; + } + + holder->svid = flock->l_pid; + holder->l_offset = flock->l_start; + holder->l_len = flock->l_len; + return 0; +} + +int +nlm4_lock_to_gf_flock(struct gf_flock *flock, nlm4_lock *lock, int excl) +{ + flock->l_pid = lock->svid; + flock->l_start = lock->l_offset; + flock->l_len = lock->l_len; + if (excl) + flock->l_type = F_WRLCK; + else + flock->l_type = F_RDLCK; + flock->l_whence = SEEK_SET; + nlm_copy_lkowner(&flock->l_owner, &lock->oh); + return 0; +} + +rpc_clnt_procedure_t nlm4_clnt_actors[NLM4_PROC_COUNT] = { + [NLM4_NULL] = {"NULL", NULL}, + [NLM4_GRANTED] = {"GRANTED", NULL}, +}; + +char *nlm4_clnt_names[NLM4_PROC_COUNT] = { + [NLM4_NULL] = "NULL", + [NLM4_GRANTED] = "GRANTED", +}; + +rpc_clnt_prog_t nlm4clntprog = { + .progname = "NLMv4", + .prognum = NLM_PROGRAM, + .progver = NLM_V4, + .numproc = NLM4_PROC_COUNT, + .proctable = nlm4_clnt_actors, + .procnames = nlm4_clnt_names, +}; + +int +nlm4_test_reply(nfs3_call_state_t *cs, nlm4_stats stat, struct gf_flock *flock) +{ + nlm4_testres res; + + memset(&res, 0, sizeof(res)); + res.cookie = cs->args.nlm4_testargs.cookie; + res.stat.stat = stat; + if ((stat == nlm4_denied) && flock) + nlm4_gf_flock_to_holder(&res.stat.nlm4_testrply_u.holder, flock); + + nlm4svc_submit_reply(cs->req, (void *)&res, + (nlm4_serializer)xdr_serialize_nlm4_testres); + return 0; +} + +int +nlm4svc_test_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct gf_flock *flock, + dict_t *xdata) +{ + nlm4_stats stat = nlm4_denied; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nlm4_errno_to_nlm4stat(op_errno); + goto err; + } else if (flock->l_type == F_UNLCK) + stat = nlm4_granted; + +err: + nlm4_test_reply(cs, stat, flock); + nfs3_call_state_wipe(cs); + return 0; +} + +int +nlm4_test_fd_resume(void *carg) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + struct gf_flock flock = { + 0, + }; + + if (!carg) + return ret; + + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nfs_request_user_init(&nfu, cs->req); + nlm4_lock_to_gf_flock(&flock, &cs->args.nlm4_testargs.alock, + cs->args.nlm4_testargs.exclusive); + nlm_copy_lkowner(&nfu.lk_owner, &cs->args.nlm4_testargs.alock.oh); + ret = nfs_lk(cs->nfsx, cs->vol, &nfu, cs->fd, F_GETLK, &flock, + nlm4svc_test_cbk, cs); + + GF_REF_PUT(cs); + + return ret; +} + +int +nlm4_test_resume(void *carg) +{ + nlm4_stats stat = nlm4_failed; + int ret = -1; + nfs3_call_state_t *cs = NULL; + fd_t *fd = NULL; + + if (!carg) + return ret; + + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nlm4_check_fh_resolve_status(cs, stat, nlm4err); + fd = fd_anonymous(cs->resolvedloc.inode); + if (!fd) + goto nlm4err; + cs->fd = fd; + ret = nlm4_test_fd_resume(cs); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_OPEN_FAIL, + "unable to open_and_resume"); + stat = nlm4_errno_to_nlm4stat(-ret); + nlm4_test_reply(cs, stat, NULL); + nfs3_call_state_wipe(cs); + } + + GF_REF_PUT(cs); + + return ret; +} + +int +nlm4svc_test(rpcsvc_request_t *req) +{ + xlator_t *vol = NULL; + nlm4_stats stat = nlm4_failed; + struct nfs_state *nfs = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + int ret = RPCSVC_ACTOR_ERROR; + struct nfs3_fh fh = { + {0}, + }; + + if (!req) + return ret; + + nlm4_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + nlm4_handle_call_state_init(nfs->nfs3state, cs, req, stat, rpcerr); + + nlm4_prep_nlm4_testargs(&cs->args.nlm4_testargs, &fh, &cs->lkowner, + cs->cookiebytes); + if (xdr_to_nlm4_testargs(req->msg[0], &cs->args.nlm4_testargs) <= 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + nlm4_validate_gluster_fh(&fh, stat, nlm4err); + nlm4_map_fh_to_volume(cs->nfs3state, fh, req, vol, stat, nlm4err); + + if (nlm_grace_period) { + gf_msg(GF_NLM, GF_LOG_WARNING, 0, NFS_MSG_NLM_GRACE_PERIOD, + "NLM in grace period"); + stat = nlm4_denied_grace_period; + nlm4_test_reply(cs, stat, NULL); + nfs3_call_state_wipe(cs); + return 0; + } + + cs->vol = vol; + nlm4_volume_started_check(nfs3, vol, ret, rpcerr); + + ret = nfs3_fh_resolve_and_resume(cs, &fh, NULL, nlm4_test_resume); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR, + "unable to resolve and resume"); + nlm4_test_reply(cs, stat, NULL); + nfs3_call_state_wipe(cs); + return 0; + } + +rpcerr: + if (ret < 0) + nfs3_call_state_wipe(cs); + + return ret; +} + +struct nlm4_notify_args { + GF_REF_DECL; /* refcounting */ + + nfs3_call_state_t *cs; /* call state, w/ lock request details */ + call_frame_t *frame; /* frame to us for the reply */ +}; + +static int +nlm4svc_send_granted_cbk(struct rpc_req *req, struct iovec *iov, int count, + void *myframe) +{ + call_frame_t *frame = myframe; + struct nlm4_notify_args *args = frame->local; + + GF_REF_PUT(args); + return 0; +} + +static void +nlm4_notify_free(struct nlm4_notify_args *ncf) +{ + GF_REF_PUT(ncf->cs); + STACK_DESTROY(ncf->frame->root); + GF_FREE(ncf); +} + +static struct nlm4_notify_args * +nlm4_notify_init(nfs3_call_state_t *cs) +{ + struct nlm4_notify_args *ncf = NULL; + + ncf = GF_CALLOC(1, sizeof(struct nlm4_notify_args), gf_nfs_mt_nlm4_notify); + if (!ncf) + /* GF_CALLOW will log the ENOMEM error */ + goto out; + + GF_REF_INIT(ncf, nlm4_notify_free); + ncf->cs = GF_REF_GET(cs); + +out: + return ncf; +} + +static int +nlm_handle_connect(struct rpc_clnt *rpc_clnt, struct nlm4_notify_args *ncf); + +int +nlm_rpcclnt_notify(struct rpc_clnt *rpc_clnt, void *mydata, rpc_clnt_event_t fn, + void *data) +{ + struct nlm4_notify_args *ncf = mydata; + + GF_VALIDATE_OR_GOTO("NLM4-notify", ncf, out); + + switch (fn) { + case RPC_CLNT_CONNECT: + nlm_handle_connect(rpc_clnt, ncf); + break; + + case RPC_CLNT_MSG: + break; + + case RPC_CLNT_DISCONNECT: + nlm_unset_rpc_clnt(rpc_clnt); + break; + + case RPC_CLNT_DESTROY: + GF_REF_PUT(ncf); + break; + + default: + break; + } +out: + return 0; +} + +void * +nlm4_establish_callback(nfs3_call_state_t *cs, call_frame_t *cbk_frame) +{ + int ret = -1; + union gf_sock_union sock_union; + dict_t *options = NULL; + char peerip[INET6_ADDRSTRLEN + 1] = {0}; + char *portstr = NULL; + char myip[INET6_ADDRSTRLEN + 1] = {0}; + rpc_clnt_t *rpc_clnt = NULL; + int port = -1; + struct nlm4_notify_args *ncf = NULL; + + GF_ASSERT(cs->nfsx); + THIS = cs->nfsx; + + rpc_transport_get_peeraddr(cs->trans, NULL, 0, &sock_union.storage, + sizeof(sock_union.storage)); + + switch (sock_union.sa.sa_family) { + case AF_INET6: + /* can not come here as NLM listens on IPv4 */ + gf_msg(GF_NLM, GF_LOG_ERROR, EAFNOSUPPORT, + NFS_MSG_UNSUPPORTED_VERSION, + "NLM is not supported on IPv6 in this release"); + goto err; + /* + inet_ntop (AF_INET6, + &((struct sockaddr_in6 + *)sockaddr)->sin6_addr, peerip, INET6_ADDRSTRLEN+1); break; + */ + case AF_INET: + inet_ntop(AF_INET, &sock_union.sin.sin_addr, peerip, + INET6_ADDRSTRLEN + 1); + inet_ntop(AF_INET, + &(((struct sockaddr_in *)&cs->trans->myinfo.sockaddr) + ->sin_addr), + myip, INET6_ADDRSTRLEN + 1); + + break; + default: + break; + /* FIXME: handle the error */ + } + + /* looks like libc rpc supports only ipv4 */ + port = pmap_getport(&sock_union.sin, NLM_PROGRAM, NLM_V4, IPPROTO_TCP); + + if (port == 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_GET_PORT_ERROR, + "Unable to get NLM port of the client." + " Is the firewall running on client?" + " OR Are RPC services running (rpcinfo -p)?"); + goto err; + } + + options = dict_new(); + if (options == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL, + "dict allocation failed"); + goto err; + } + + ret = dict_set_str(options, "transport-type", "socket"); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + ret = dict_set_dynstr(options, "remote-host", gf_strdup(peerip)); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + ret = gf_asprintf(&portstr, "%d", port); + if (ret == -1) + goto err; + + ret = dict_set_dynstr(options, "remote-port", portstr); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_dynstr error"); + goto err; + } + + /* needed in case virtual IP is used */ + ret = dict_set_dynstr(options, "transport.socket.source-addr", + gf_strdup(myip)); + if (ret == -1) + goto err; + + ret = dict_set_str(options, "auth-null", "on"); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_dynstr error"); + goto err; + } + + ncf = nlm4_notify_init(cs); + if (!ncf) { + ret = -1; + goto err; + } + + ncf->frame = cbk_frame; + ncf->frame->local = ncf; + + /* TODO: is 32 frames in transit enough ? */ + rpc_clnt = rpc_clnt_new(options, cs->nfsx, "NLM-client", 32); + if (rpc_clnt == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY, + "rpc_clnt NULL"); + goto err; + } + + ret = rpc_clnt_register_notify(rpc_clnt, nlm_rpcclnt_notify, ncf); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_RPC_CLNT_ERROR, + "rpc_clnt_register_connect error"); + goto err; + } + + /* After this connect succeeds, granted msg is sent in notify */ + ret = rpc_transport_connect(rpc_clnt->conn.trans, port); + + if (ret == -1 && EINPROGRESS == errno) + ret = 0; + +err: + if (options) + dict_unref(options); + if (ret == -1) { + if (rpc_clnt) + rpc_clnt_unref(rpc_clnt); + if (ncf) + GF_REF_PUT(ncf); + } + + return rpc_clnt; +} + +static void +nlm4svc_send_granted(struct nlm4_notify_args *ncf) +{ + int ret = -1; + nfs3_call_state_t *cs = ncf->cs; + rpc_clnt_t *rpc_clnt = NULL; + struct iovec outmsg = { + 0, + }; + nlm4_testargs testargs; + struct iobuf *iobuf = NULL; + struct iobref *iobref = NULL; + char peerip[INET6_ADDRSTRLEN + 1]; + union gf_sock_union sock_union; + + rpc_clnt = nlm_get_rpc_clnt(cs->args.nlm4_lockargs.alock.caller_name); + if (rpc_clnt == NULL) { + nlm4_establish_callback(cs, ncf->frame); + return; + } + + rpc_transport_get_peeraddr(cs->trans, NULL, 0, &sock_union.storage, + sizeof(sock_union.storage)); + + switch (sock_union.sa.sa_family) { + case AF_INET6: + inet_ntop(AF_INET6, &sock_union.sin6.sin6_addr, peerip, + INET6_ADDRSTRLEN + 1); + break; + case AF_INET: + inet_ntop(AF_INET, &sock_union.sin.sin_addr, peerip, + INET6_ADDRSTRLEN + 1); + break; + default: + break; + } + + testargs.cookie = cs->args.nlm4_lockargs.cookie; + testargs.exclusive = cs->args.nlm4_lockargs.exclusive; + testargs.alock = cs->args.nlm4_lockargs.alock; + + iobuf = iobuf_get(cs->nfs3state->iobpool); + if (!iobuf) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobuf"); + goto ret; + } + + iobuf_to_iovec(iobuf, &outmsg); + /* Use the given serializer to translate the give C structure in arg + * to XDR format which will be written into the buffer in outmsg. + */ + outmsg.iov_len = xdr_serialize_nlm4_testargs(outmsg, &testargs); + + iobref = iobref_new(); + if (iobref == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to get iobref"); + goto ret; + } + + ret = iobref_add(iobref, iobuf); + if (ret) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "Failed to add iob to iobref"); + goto ret; + } + + GF_REF_GET(ncf); + ret = rpc_clnt_submit(rpc_clnt, &nlm4clntprog, NLM4_GRANTED, + nlm4svc_send_granted_cbk, &outmsg, 1, NULL, 0, iobref, + ncf->frame, NULL, 0, NULL, 0, NULL); + + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_RPC_CLNT_ERROR, + "rpc_clnt_submit error"); + goto ret; + } +ret: + if (iobref) + iobref_unref(iobref); + if (iobuf) + iobuf_unref(iobuf); + + rpc_clnt_unref(rpc_clnt); + return; +} + +int +nlm_cleanup_fds(char *caller_name) +{ + int nlmclnt_found = 0; + nlm_client_t *nlmclnt = NULL; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) { + nlmclnt_found = 1; + break; + } + } + + if (!nlmclnt_found) + goto ret; + + nlm_client_free(nlmclnt); +ret: + UNLOCK(&nlm_client_list_lk); + return 0; +} + +void +nlm_search_and_delete(fd_t *fd, nlm4_lock *lk) +{ + nlm_fde_t *fde = NULL; + nlm_client_t *nlmclnt = NULL; + int nlmclnt_found = 0; + int fde_found = 0; + int transit_cnt = 0; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(lk->caller_name, nlmclnt->caller_name)) { + nlmclnt_found = 1; + break; + } + } + + if (!nlmclnt_found) + goto ret; + + list_for_each_entry(fde, &nlmclnt->fdes, fde_list) + { + if (fde->fd == fd) { + fde_found = 1; + break; + } + } + + if (!fde_found) + goto ret; + transit_cnt = fde->transit_cnt; + if (transit_cnt) + goto ret; + list_del(&fde->fde_list); + +ret: + UNLOCK(&nlm_client_list_lk); + + if (fde_found && !transit_cnt) { + fd_unref(fde->fd); + GF_FREE(fde); + } + return; +} + +int +nlm_dec_transit_count(fd_t *fd, char *caller_name) +{ + nlm_fde_t *fde = NULL; + nlm_client_t *nlmclnt = NULL; + int nlmclnt_found = 0; + int fde_found = 0; + int transit_cnt = -1; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) { + nlmclnt_found = 1; + break; + } + } + + if (!nlmclnt_found) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_NLMCLNT_NOT_FOUND, + "nlmclnt not found"); + nlmclnt = NULL; + goto ret; + } + + list_for_each_entry(fde, &nlmclnt->fdes, fde_list) + { + if (fde->fd == fd) { + fde_found = 1; + break; + } + } + + if (fde_found) { + transit_cnt = --fde->transit_cnt; + goto ret; + } +ret: + + UNLOCK(&nlm_client_list_lk); + return transit_cnt; +} + +nlm_client_t * +nlm_search_and_add(fd_t *fd, char *caller_name) +{ + nlm_fde_t *fde = NULL; + nlm_client_t *nlmclnt = NULL; + int nlmclnt_found = 0; + int fde_found = 0; + + LOCK(&nlm_client_list_lk); + list_for_each_entry(nlmclnt, &nlm_client_list, nlm_clients) + { + if (!strcmp(caller_name, nlmclnt->caller_name)) { + nlmclnt_found = 1; + break; + } + } + + if (!nlmclnt_found) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_NLMCLNT_NOT_FOUND, + "nlmclnt not found"); + nlmclnt = NULL; + goto ret; + } + + list_for_each_entry(fde, &nlmclnt->fdes, fde_list) + { + if (fde->fd == fd) { + fde_found = 1; + break; + } + } + + if (fde_found) + goto ret; + + fde = GF_CALLOC(1, sizeof(*fde), gf_nfs_mt_nlm4_fde); + + fde->fd = fd_ref(fd); + list_add(&fde->fde_list, &nlmclnt->fdes); +ret: + if (nlmclnt_found && fde) + fde->transit_cnt++; + UNLOCK(&nlm_client_list_lk); + return nlmclnt; +} + +int +nlm4svc_lock_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct gf_flock *flock, + dict_t *xdata) +{ + nlm4_stats stat = nlm4_denied; + int transit_cnt = -1; + char *caller_name = NULL; + nfs3_call_state_t *cs = NULL; + pthread_t thr; + struct nlm4_notify_args *ncf = NULL; + + cs = frame->local; + caller_name = cs->args.nlm4_lockargs.alock.caller_name; + transit_cnt = nlm_dec_transit_count(cs->fd, caller_name); + + if (op_ret == -1) { + if (transit_cnt == 0) + nlm_search_and_delete(cs->fd, &cs->args.nlm4_lockargs.alock); + stat = nlm4_errno_to_nlm4stat(op_errno); + goto err; + } else { + stat = nlm4_granted; + if (cs->monitor && !nlm_monitor(caller_name)) { + /* FIXME: handle nsm_monitor failure */ + (void)gf_thread_create(&thr, NULL, nsm_monitor, (void *)caller_name, + "nlmmon"); + } + } + +err: + if (cs->args.nlm4_lockargs.block) { + ncf = nlm4_notify_init(cs); + if (ncf) { + ncf->frame = copy_frame(frame); + ncf->frame->local = ncf; + nlm4svc_send_granted(ncf); + } + } else { + nlm4_generic_reply(cs->req, cs->args.nlm4_lockargs.cookie, stat); + nfs3_call_state_wipe(cs); + } + + return 0; +} + +int +nlm4_lock_fd_resume(void *carg) +{ + nlm4_stats stat = nlm4_denied; + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + struct gf_flock flock = { + 0, + }; + + if (!carg) + return ret; + + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nlm4_check_fh_resolve_status(cs, stat, nlm4err); + (void)nlm_search_and_add(cs->fd, cs->args.nlm4_lockargs.alock.caller_name); + nfs_request_user_init(&nfu, cs->req); + nlm4_lock_to_gf_flock(&flock, &cs->args.nlm4_lockargs.alock, + cs->args.nlm4_lockargs.exclusive); + nlm_copy_lkowner(&nfu.lk_owner, &cs->args.nlm4_lockargs.alock.oh); + if (cs->args.nlm4_lockargs.block) { + nlm4_generic_reply(cs->req, cs->args.nlm4_lockargs.cookie, + nlm4_blocked); + ret = nfs_lk(cs->nfsx, cs->vol, &nfu, cs->fd, F_SETLKW, &flock, + nlm4svc_lock_cbk, cs); + /* FIXME: handle error from nfs_lk() specially by just + * cleaning up cs and unblock the client lock request. + */ + ret = 0; + } else + ret = nfs_lk(cs->nfsx, cs->vol, &nfu, cs->fd, F_SETLK, &flock, + nlm4svc_lock_cbk, cs); + +nlm4err: + if (ret < 0) { + stat = nlm4_errno_to_nlm4stat(-ret); + gf_msg(GF_NLM, GF_LOG_ERROR, stat, NFS_MSG_LOCK_FAIL, + "unable to call lk()"); + nlm4_generic_reply(cs->req, cs->args.nlm4_lockargs.cookie, stat); + nfs3_call_state_wipe(cs); + } + + GF_REF_PUT(cs); + + return ret; +} + +int +nlm4_lock_resume(void *carg) +{ + nlm4_stats stat = nlm4_failed; + int ret = -1; + nfs3_call_state_t *cs = NULL; + + if (!carg) + return ret; + + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nlm4_check_fh_resolve_status(cs, stat, nlm4err); + ret = nlm4_file_open_and_resume(cs, nlm4_lock_fd_resume); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_OPEN_FAIL, + "unable to open and resume"); + stat = nlm4_errno_to_nlm4stat(-ret); + nlm4_generic_reply(cs->req, cs->args.nlm4_lockargs.cookie, stat); + nfs3_call_state_wipe(cs); + } + + GF_REF_PUT(cs); + + return ret; +} + +int +nlm4svc_lock_common(rpcsvc_request_t *req, int mon) +{ + int ret = RPCSVC_ACTOR_ERROR; + nlm4_stats stat = nlm4_failed; + struct nfs3_fh fh = { + {0}, + }; + xlator_t *vol = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + struct nfs_state *nfs = NULL; + + if (!req) + return ret; + + nlm4_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + nlm4_handle_call_state_init(nfs->nfs3state, cs, req, stat, rpcerr); + + nlm4_prep_nlm4_lockargs(&cs->args.nlm4_lockargs, &cs->lockfh, &cs->lkowner, + cs->cookiebytes); + if (xdr_to_nlm4_lockargs(req->msg[0], &cs->args.nlm4_lockargs) <= 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + fh = cs->lockfh; + cs->monitor = mon; + nlm4_validate_gluster_fh(&fh, stat, nlm4err); + nlm4_map_fh_to_volume(cs->nfs3state, fh, req, vol, stat, nlm4err); + + if (nlm_grace_period && !cs->args.nlm4_lockargs.reclaim) { + gf_msg(GF_NLM, GF_LOG_WARNING, 0, NFS_MSG_NLM_GRACE_PERIOD, + "NLM in grace period"); + stat = nlm4_denied_grace_period; + nlm4_generic_reply(req, cs->args.nlm4_unlockargs.cookie, stat); + nfs3_call_state_wipe(cs); + return 0; + } + + cs->vol = vol; + cs->trans = rpcsvc_request_transport_ref(req); + nlm4_volume_started_check(nfs3, vol, ret, rpcerr); + + ret = nlm_add_nlmclnt(cs->args.nlm4_lockargs.alock.caller_name); + + ret = nfs3_fh_resolve_and_resume(cs, &fh, NULL, nlm4_lock_resume); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR, + "unable to resolve and resume"); + nlm4_generic_reply(cs->req, cs->args.nlm4_lockargs.cookie, stat); + nfs3_call_state_wipe(cs); + return 0; + } + +rpcerr: + if (ret < 0) { + nfs3_call_state_wipe(cs); + } + + return ret; +} + +int +nlm4svc_lock(rpcsvc_request_t *req) +{ + return nlm4svc_lock_common(req, 1); +} + +int +nlm4svc_nm_lock(rpcsvc_request_t *req) +{ + return nlm4svc_lock_common(req, 0); +} + +int +nlm4svc_cancel_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct gf_flock *flock, + dict_t *xdata) +{ + nlm4_stats stat = nlm4_denied; + nfs3_call_state_t *cs = NULL; + + cs = frame->local; + if (op_ret == -1) { + stat = nlm4_errno_to_nlm4stat(op_errno); + goto err; + } else { + stat = nlm4_granted; + nlm_search_and_delete(cs->fd, &cs->args.nlm4_lockargs.alock); + } + +err: + nlm4_generic_reply(cs->req, cs->args.nlm4_cancargs.cookie, stat); + nfs3_call_state_wipe(cs); + return 0; +} + +int +nlm4_cancel_fd_resume(void *carg) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + struct gf_flock flock = { + 0, + }; + + if (!carg) + return ret; + + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nfs_request_user_init(&nfu, cs->req); + nlm4_lock_to_gf_flock(&flock, &cs->args.nlm4_cancargs.alock, + cs->args.nlm4_cancargs.exclusive); + nlm_copy_lkowner(&nfu.lk_owner, &cs->args.nlm4_cancargs.alock.oh); + flock.l_type = F_UNLCK; + ret = nfs_lk(cs->nfsx, cs->vol, &nfu, cs->fd, F_SETLK, &flock, + nlm4svc_cancel_cbk, cs); + + GF_REF_PUT(cs); + + return ret; +} + +int +nlm4_cancel_resume(void *carg) +{ + nlm4_stats stat = nlm4_failed; + int ret = -EFAULT; + nfs3_call_state_t *cs = NULL; + nlm_client_t *nlmclnt = NULL; + + if (!carg) + return ret; + + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nlm4_check_fh_resolve_status(cs, stat, nlm4err); + + nlmclnt = nlm_get_uniq(cs->args.nlm4_cancargs.alock.caller_name); + if (nlmclnt == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOLCK, NFS_MSG_NO_MEMORY, + "nlm_get_uniq() returned NULL"); + goto nlm4err; + } + cs->fd = fd_lookup_uint64(cs->resolvedloc.inode, + (uint64_t)(uintptr_t)nlmclnt); + if (cs->fd == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_FD_LOOKUP_NULL, + "fd_lookup_uint64 retrned NULL"); + goto nlm4err; + } + ret = nlm4_cancel_fd_resume(cs); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_WARNING, -ret, NFS_MSG_LOCK_FAIL, + "unable to unlock_fd_resume()"); + stat = nlm4_errno_to_nlm4stat(-ret); + nlm4_generic_reply(cs->req, cs->args.nlm4_cancargs.cookie, stat); + + nfs3_call_state_wipe(cs); + } + + GF_REF_PUT(cs); + + /* clean up is taken care of */ + return 0; +} + +int +nlm4svc_cancel(rpcsvc_request_t *req) +{ + xlator_t *vol = NULL; + nlm4_stats stat = nlm4_failed; + struct nfs_state *nfs = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + int ret = RPCSVC_ACTOR_ERROR; + struct nfs3_fh fh = { + {0}, + }; + + if (!req) + return ret; + + nlm4_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + nlm4_handle_call_state_init(nfs->nfs3state, cs, req, stat, rpcerr); + + nlm4_prep_nlm4_cancargs(&cs->args.nlm4_cancargs, &fh, &cs->lkowner, + cs->cookiebytes); + if (xdr_to_nlm4_cancelargs(req->msg[0], &cs->args.nlm4_cancargs) <= 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + nlm4_validate_gluster_fh(&fh, stat, nlm4err); + nlm4_map_fh_to_volume(cs->nfs3state, fh, req, vol, stat, nlm4err); + + if (nlm_grace_period) { + gf_msg(GF_NLM, GF_LOG_WARNING, 0, NFS_MSG_NLM_GRACE_PERIOD, + "NLM in grace period"); + stat = nlm4_denied_grace_period; + nlm4_generic_reply(req, cs->args.nlm4_unlockargs.cookie, stat); + nfs3_call_state_wipe(cs); + return 0; + } + + cs->vol = vol; + nlm4_volume_started_check(nfs3, vol, ret, rpcerr); + + ret = nfs3_fh_resolve_and_resume(cs, &fh, NULL, nlm4_cancel_resume); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR, + "unable to resolve and resume"); + nlm4_generic_reply(cs->req, cs->args.nlm4_cancargs.cookie, stat); + nfs3_call_state_wipe(cs); + return 0; + } + +rpcerr: + if (ret < 0) { + nfs3_call_state_wipe(cs); + } + return ret; +} + +int +nlm4svc_unlock_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct gf_flock *flock, + dict_t *xdata) +{ + nlm4_stats stat = nlm4_denied; + nfs3_call_state_t *cs = NULL; + + cs = GF_REF_GET((nfs3_call_state_t *)frame->local); + if (op_ret == -1) { + stat = nlm4_errno_to_nlm4stat(op_errno); + goto err; + } else { + stat = nlm4_granted; + if (flock->l_type == F_UNLCK) + nlm_search_and_delete(cs->fd, &cs->args.nlm4_unlockargs.alock); + } + +err: + nlm4_generic_reply(cs->req, cs->args.nlm4_unlockargs.cookie, stat); + GF_REF_PUT(cs); + return 0; +} + +int +nlm4_unlock_fd_resume(void *carg) +{ + int ret = -EFAULT; + nfs_user_t nfu = { + 0, + }; + nfs3_call_state_t *cs = NULL; + struct gf_flock flock = { + 0, + }; + + if (!carg) + return ret; + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nfs_request_user_init(&nfu, cs->req); + nlm4_lock_to_gf_flock(&flock, &cs->args.nlm4_unlockargs.alock, 0); + nlm_copy_lkowner(&nfu.lk_owner, &cs->args.nlm4_unlockargs.alock.oh); + flock.l_type = F_UNLCK; + ret = nfs_lk(cs->nfsx, cs->vol, &nfu, cs->fd, F_SETLK, &flock, + nlm4svc_unlock_cbk, cs); + + GF_REF_PUT(cs); + + return ret; +} + +int +nlm4_unlock_resume(void *carg) +{ + nlm4_stats stat = nlm4_failed; + int ret = -1; + nfs3_call_state_t *cs = NULL; + nlm_client_t *nlmclnt = NULL; + char *caller_name = NULL; + + if (!carg) + return ret; + + cs = GF_REF_GET((nfs3_call_state_t *)carg); + nlm4_check_fh_resolve_status(cs, stat, nlm4err); + caller_name = cs->args.nlm4_unlockargs.alock.caller_name; + + nlmclnt = nlm_get_uniq(caller_name); + if (nlmclnt == NULL) { + stat = nlm4_granted; + gf_msg(GF_NLM, GF_LOG_WARNING, ENOLCK, NFS_MSG_NO_MEMORY, + "nlm_get_uniq() returned NULL for %s", caller_name); + goto nlm4err; + } + cs->fd = fd_lookup_uint64(cs->resolvedloc.inode, + (uint64_t)(uintptr_t)nlmclnt); + if (cs->fd == NULL) { + stat = nlm4_granted; + gf_msg(GF_NLM, GF_LOG_WARNING, 0, NFS_MSG_FD_LOOKUP_NULL, + "fd_lookup_uint64() returned NULL"); + goto nlm4err; + } + ret = nlm4_unlock_fd_resume(cs); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_WARNING, -ret, NFS_MSG_LOCK_FAIL, + "unable to unlock_fd_resume"); + stat = nlm4_errno_to_nlm4stat(-ret); + nlm4_generic_reply(cs->req, cs->args.nlm4_unlockargs.cookie, stat); + + nfs3_call_state_wipe(cs); + } + + GF_REF_PUT(cs); + + /* we have already taken care of cleanup */ + return 0; +} + +int +nlm4svc_unlock(rpcsvc_request_t *req) +{ + xlator_t *vol = NULL; + nlm4_stats stat = nlm4_failed; + struct nfs_state *nfs = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + int ret = RPCSVC_ACTOR_ERROR; + struct nfs3_fh fh = { + {0}, + }; + + if (!req) + return ret; + + nlm4_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + nlm4_handle_call_state_init(nfs->nfs3state, cs, req, stat, rpcerr); + + nlm4_prep_nlm4_unlockargs(&cs->args.nlm4_unlockargs, &fh, &cs->lkowner, + cs->cookiebytes); + if (xdr_to_nlm4_unlockargs(req->msg[0], &cs->args.nlm4_unlockargs) <= 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + nlm4_validate_gluster_fh(&fh, stat, nlm4err); + nlm4_map_fh_to_volume(cs->nfs3state, fh, req, vol, stat, nlm4err); + + if (nlm_grace_period) { + gf_msg(GF_NLM, GF_LOG_WARNING, 0, NFS_MSG_NLM_GRACE_PERIOD, + "NLM in grace period"); + stat = nlm4_denied_grace_period; + nlm4_generic_reply(req, cs->args.nlm4_unlockargs.cookie, stat); + nfs3_call_state_wipe(cs); + return 0; + } + + cs->vol = vol; + /* FIXME: check if trans is being used at all for unlock */ + cs->trans = rpcsvc_request_transport_ref(req); + nlm4_volume_started_check(nfs3, vol, ret, rpcerr); + + ret = nfs3_fh_resolve_and_resume(cs, &fh, NULL, nlm4_unlock_resume); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR, + "unable to resolve and resume"); + nlm4_generic_reply(req, cs->args.nlm4_unlockargs.cookie, stat); + nfs3_call_state_wipe(cs); + return 0; + } + +rpcerr: + if (ret < 0) { + nfs3_call_state_wipe(cs); + } + return ret; +} + +int +nlm4_share_reply(nfs3_call_state_t *cs, nlm4_stats stat) +{ + nlm4_shareres res = {{0}, 0, 0}; + + if (!cs) + return -1; + + res.cookie = cs->args.nlm4_shareargs.cookie; + res.stat = stat; + res.sequence = 0; + + nlm4svc_submit_reply(cs->req, (void *)&res, + (nlm4_serializer)xdr_serialize_nlm4_shareres); + return 0; +} + +nlm_share_t * +nlm4_share_new() +{ + nlm_share_t *share = NULL; + + share = GF_CALLOC(1, sizeof(nlm_share_t), gf_nfs_mt_nlm4_share); + if (!share) + goto out; + + INIT_LIST_HEAD(&share->client_list); + INIT_LIST_HEAD(&share->inode_list); +out: + return share; +} + +int +nlm4_add_share_to_inode(nlm_share_t *share) +{ + int ret = -1; + uint64_t ctx = 0; + struct list_head *head = NULL; + xlator_t *this = NULL; + inode_t *inode = NULL; + struct nfs_inode_ctx *ictx = NULL; + struct nfs_state *priv = NULL; + + this = THIS; + priv = this->private; + inode = share->inode; + ret = inode_ctx_get(inode, this, &ctx); + + if (ret == -1) { + ictx = GF_CALLOC(1, sizeof(struct nfs_inode_ctx), gf_nfs_mt_inode_ctx); + if (!ictx) { + gf_msg(this->name, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY, + "could not allocate nfs inode ctx"); + ret = -1; + goto out; + } + ictx->generation = priv->generation; + + head = &ictx->shares; + INIT_LIST_HEAD(head); + + ret = inode_ctx_put(inode, this, (uint64_t)(uintptr_t)ictx); + if (ret) { + gf_msg(this->name, GF_LOG_ERROR, 0, NFS_MSG_SHARE_LIST_STORE_FAIL, + "could not store share list"); + goto out; + } + } else { + ictx = (struct nfs_inode_ctx *)(uintptr_t)ctx; + head = &ictx->shares; + } + + list_add(&share->inode_list, head); + +out: + if (ret && head) + GF_FREE(head); + + return ret; +} + +int +nlm4_approve_share_reservation(nfs3_call_state_t *cs) +{ + int ret = -1; + uint64_t ctx = 0; + fsh_mode req_mode = 0; + fsh_access req_access = 0; + inode_t *inode = NULL; + nlm_share_t *share = NULL; + struct list_head *head = NULL; + struct nfs_inode_ctx *ictx = NULL; + + if (!cs) + goto out; + + inode = cs->resolvedloc.inode; + + ret = inode_ctx_get(inode, THIS, &ctx); + if (ret) { + ret = 0; + goto out; + } + ictx = (struct nfs_inode_ctx *)(uintptr_t)ctx; + + head = &ictx->shares; + if (!head || list_empty(head)) + goto out; + + req_mode = cs->args.nlm4_shareargs.share.mode; + req_access = cs->args.nlm4_shareargs.share.access; + + list_for_each_entry(share, head, inode_list) + { + ret = (((req_mode & share->access) == 0) && + ((req_access & share->mode) == 0)); + if (!ret) { + ret = -1; + goto out; + } + } + ret = 0; + +out: + return ret; +} + +int +nlm4_create_share_reservation(nfs3_call_state_t *cs) +{ + int ret = -1; + nlm_share_t *share = NULL; + nlm_client_t *client = NULL; + inode_t *inode = NULL; + + LOCK(&nlm_client_list_lk); + + inode = inode_ref(cs->resolvedloc.inode); + if (!inode) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_INODE_NOT_FOUND, + "inode not found"); + goto out; + } + + client = __nlm_get_uniq(cs->args.nlm4_shareargs.share.caller_name); + if (!client) { + /* DO NOT add client. the client is supposed + to be here, since nlm4svc_share adds it */ + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_CLIENT_NOT_FOUND, + "client not found"); + goto out; + } + + ret = nlm4_approve_share_reservation(cs); + if (ret) + goto out; + + share = nlm4_share_new(); + if (!share) { + ret = -1; + goto out; + } + + share->inode = inode; + share->mode = cs->args.nlm4_shareargs.share.mode; + share->access = cs->args.nlm4_shareargs.share.access; + nlm_copy_lkowner(&share->lkowner, &cs->args.nlm4_shareargs.share.oh); + + ret = nlm4_add_share_to_inode(share); + if (ret) + goto out; + + list_add(&share->client_list, &client->shares); + +out: + if (ret && inode) { + inode_unref(inode); + GF_FREE(share); + } + + UNLOCK(&nlm_client_list_lk); + return ret; +} + +/* + SHARE and UNSHARE calls DO NOT perform STACK_WIND, + the (non-monitored) share reservations are maintained + at *nfs xlator level only*, in memory +*/ +int +nlm4_share_resume(void *call_state) +{ + int ret = -1; + nlm4_stats stat = nlm4_failed; + nfs3_call_state_t *cs = NULL; + + if (!call_state) + return ret; + + cs = (nfs3_call_state_t *)call_state; + nlm4_check_fh_resolve_status(cs, stat, out); + + ret = nlm4_create_share_reservation(cs); + if (!ret) + stat = nlm4_granted; + +out: + nlm4_share_reply(cs, stat); + nfs3_call_state_wipe(cs); + return 0; +} + +int +nlm4svc_share(rpcsvc_request_t *req) +{ + nlm4_stats stat = nlm4_failed; + xlator_t *vol = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + struct nfs_state *nfs = NULL; + struct nfs3_fh fh = { + {0}, + }; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + + nlm4_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + nlm4_handle_call_state_init(nfs->nfs3state, cs, req, stat, rpcerr); + + nlm4_prep_shareargs(&cs->args.nlm4_shareargs, &cs->lockfh, &cs->lkowner, + cs->cookiebytes); + + if (xdr_to_nlm4_shareargs(req->msg[0], &cs->args.nlm4_shareargs) <= 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding SHARE args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + fh = cs->lockfh; + nlm4_validate_gluster_fh(&fh, stat, nlm4err); + nlm4_map_fh_to_volume(cs->nfs3state, fh, req, vol, stat, nlm4err); + + if (nlm_grace_period && !cs->args.nlm4_shareargs.reclaim) { + gf_msg_debug(GF_NLM, 0, "NLM in grace period"); + stat = nlm4_denied_grace_period; + nlm4_share_reply(cs, stat); + nfs3_call_state_wipe(cs); + return 0; + } + + cs->vol = vol; + cs->trans = rpcsvc_request_transport_ref(req); + nlm4_volume_started_check(nfs3, vol, ret, rpcerr); + + ret = nlm_add_nlmclnt(cs->args.nlm4_shareargs.share.caller_name); + + ret = nfs3_fh_resolve_and_resume(cs, &fh, NULL, nlm4_share_resume); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_SHARE_CALL_FAIL, + "SHARE call failed"); + nlm4_share_reply(cs, stat); + nfs3_call_state_wipe(cs); + return 0; + } + +rpcerr: + if (ret < 0) + nfs3_call_state_wipe(cs); + + return ret; +} + +int +nlm4_remove_share_reservation(nfs3_call_state_t *cs) +{ + int ret = -1; + uint64_t ctx = 0; + fsh_mode req_mode = 0; + fsh_access req_access = 0; + nlm_share_t *share = NULL; + nlm_share_t *tmp = NULL; + nlm_client_t *client = NULL; + char *caller = NULL; + inode_t *inode = NULL; + xlator_t *this = NULL; + struct list_head *head = NULL; + nlm4_shareargs *args = NULL; + struct nfs_inode_ctx *ictx = NULL; + + LOCK(&nlm_client_list_lk); + + args = &cs->args.nlm4_shareargs; + caller = args->share.caller_name; + + client = __nlm_get_uniq(caller); + if (!client) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_CLIENT_NOT_FOUND, + "client not found: %s", caller); + goto out; + } + + inode = cs->resolvedloc.inode; + if (!inode) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_INODE_NOT_FOUND, + "inode not found: client: %s", caller); + goto out; + } + + this = THIS; + ret = inode_ctx_get(inode, this, &ctx); + if (ret) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_INODE_SHARES_NOT_FOUND, + "no shares found for inode:" + "gfid: %s; client: %s", + inode->gfid, caller); + goto out; + } + ictx = (struct nfs_inode_ctx *)(uintptr_t)ctx; + + head = &ictx->shares; + if (list_empty(head)) { + ret = -1; + goto out; + } + + ret = 0; + req_mode = args->share.mode; + req_access = args->share.access; + + list_for_each_entry_safe(share, tmp, head, inode_list) + { + ret = ((req_mode == share->mode) && (req_access == share->access) && + nlm_is_oh_same_lkowner(&share->lkowner, &args->share.oh)); + if (ret) { + list_del(&share->client_list); + list_del(&share->inode_list); + inode_unref(share->inode); + GF_FREE(share); + break; + } + } + + ret = 0; +out: + UNLOCK(&nlm_client_list_lk); + return ret; +} + +int +nlm4_unshare_resume(void *call_state) +{ + int ret = -1; + nlm4_stats stat = nlm4_failed; + nfs3_call_state_t *cs = NULL; + + if (!call_state) + return ret; + + cs = (nfs3_call_state_t *)call_state; + + nlm4_check_fh_resolve_status(cs, stat, out); + ret = nlm4_remove_share_reservation(cs); + if (!ret) + stat = nlm4_granted; + +out: + nlm4_share_reply(cs, stat); + nfs3_call_state_wipe(cs); + return 0; +} + +int +nlm4svc_unshare(rpcsvc_request_t *req) +{ + nlm4_stats stat = nlm4_failed; + xlator_t *vol = NULL; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + struct nfs_state *nfs = NULL; + struct nfs3_fh fh = { + {0}, + }; + int ret = RPCSVC_ACTOR_ERROR; + + if (!req) + return ret; + + nlm4_validate_nfs3_state(req, nfs3, stat, rpcerr, ret); + nfs = nfs_state(nfs3->nfsx); + nlm4_handle_call_state_init(nfs->nfs3state, cs, req, stat, rpcerr); + + nlm4_prep_shareargs(&cs->args.nlm4_shareargs, &cs->lockfh, &cs->lkowner, + cs->cookiebytes); + + if (xdr_to_nlm4_shareargs(req->msg[0], &cs->args.nlm4_shareargs) <= 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding UNSHARE args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto rpcerr; + } + + fh = cs->lockfh; + nlm4_validate_gluster_fh(&fh, stat, nlm4err); + nlm4_map_fh_to_volume(cs->nfs3state, fh, req, vol, stat, nlm4err); + + if (nlm_grace_period && !cs->args.nlm4_shareargs.reclaim) { + gf_msg_debug(GF_NLM, 0, "NLM in grace period"); + stat = nlm4_denied_grace_period; + nlm4_share_reply(cs, stat); + nfs3_call_state_wipe(cs); + return 0; + } + + cs->vol = vol; + cs->trans = rpcsvc_request_transport_ref(req); + nlm4_volume_started_check(nfs3, vol, ret, rpcerr); + + ret = nfs3_fh_resolve_and_resume(cs, &fh, NULL, nlm4_unshare_resume); + +nlm4err: + if (ret < 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, -ret, NFS_MSG_UNSHARE_CALL_FAIL, + "UNSHARE call failed"); + nlm4_share_reply(cs, stat); + ret = 0; + return 0; + } + +rpcerr: + if (ret < 0) + nfs3_call_state_wipe(cs); + + return ret; +} + +int +nlm4_free_all_shares(char *caller_name) +{ + nlm_share_t *share = NULL; + nlm_share_t *tmp = NULL; + nlm_client_t *client = NULL; + + LOCK(&nlm_client_list_lk); + + client = __nlm_get_uniq(caller_name); + if (!client) { + gf_msg_debug(GF_NLM, 0, "client not found: %s", caller_name); + goto out; + } + + list_for_each_entry_safe(share, tmp, &client->shares, client_list) + { + list_del(&share->inode_list); + list_del(&share->client_list); + inode_unref(share->inode); + GF_FREE(share); + } +out: + UNLOCK(&nlm_client_list_lk); + return 0; +} + +int +nlm4svc_free_all(rpcsvc_request_t *req) +{ + int ret = RPCSVC_ACTOR_ERROR; + nlm4_stats stat = nlm4_failed; + nfs3_state_t *nfs3 = NULL; + nfs3_call_state_t *cs = NULL; + struct nfs_state *nfs = NULL; + + nlm4_validate_nfs3_state(req, nfs3, stat, err, ret); + nfs = nfs_state(nfs3->nfsx); + nlm4_handle_call_state_init(nfs->nfs3state, cs, req, stat, err); + + nlm4_prep_freeallargs(&cs->args.nlm4_freeallargs, &cs->lkowner); + + if (xdr_to_nlm4_freeallargs(req->msg[0], &cs->args.nlm4_freeallargs) <= 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR, + "Error decoding FREE_ALL args"); + rpcsvc_request_seterr(req, GARBAGE_ARGS); + goto err; + } + + ret = nlm4_free_all_shares(cs->args.nlm4_freeallargs.name); + if (ret) + goto err; + + ret = nlm_cleanup_fds(cs->args.nlm4_freeallargs.name); + if (ret) + goto err; + +err: + nfs3_call_state_wipe(cs); + if (ret) + gf_msg_debug(GF_NLM, 0, "error in free all; stat: %d", stat); + return ret; +} + +void +nlm4svc_sm_notify(struct nlm_sm_status *status) +{ + gf_msg(GF_NLM, GF_LOG_INFO, 0, NFS_MSG_SM_NOTIFY, + "sm_notify: " + "%s, state: %d", + status->mon_name, status->state); + nlm_cleanup_fds(status->mon_name); +} + +/* RPC_CLNT_CONNECT gets called on (re)connects and should be able to handle + * different NLM requests. */ +static int +nlm_handle_connect(struct rpc_clnt *rpc_clnt, struct nlm4_notify_args *ncf) +{ + int ret = -1; + int nlm_proc = NLM4_NULL; + nfs3_call_state_t *cs = NULL; + struct nlm4_lock *alock = NULL; + char *caller_name = NULL; + + cs = GF_REF_GET(ncf->cs); + if (!cs || !cs->req) { + gf_msg(GF_NLM, GF_LOG_ERROR, EINVAL, NFS_MSG_RPC_CLNT_ERROR, + "Spurious notify?!"); + goto out; + } + + /* NLM4_* actions from nlm4.h */ + if (cs->req->prognum == NLM_PROGRAM) { + nlm_proc = cs->req->procnum; + } else { + /* hmm, cs->req has not been filled completely */ + if (cs->resume_fn == nlm4_lock_fd_resume) + nlm_proc = NLM4_LOCK; + else if (cs->resume_fn == nlm4_cancel_fd_resume) + nlm_proc = NLM4_CANCEL; + else if (cs->resume_fn == nlm4_unlock_fd_resume) + nlm_proc = NLM4_UNLOCK; + else { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_RPC_CLNT_ERROR, + "(re)connect with an " + "unexpected NLM4 procedure (%d)", + nlm_proc); + goto out; + } + } + + switch (nlm_proc) { + case NLM4_LOCK: + alock = &cs->args.nlm4_lockargs.alock; + caller_name = alock->caller_name; + + ret = nlm_set_rpc_clnt(rpc_clnt, caller_name); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_RPC_CLNT_ERROR, + "Failed to set " + "rpc clnt"); + goto out; + } + + /* extra ref taken with nlm_set_rpc_clnt() */ + rpc_clnt_unref(rpc_clnt); + + nlm4svc_send_granted(ncf); + break; + + case NLM4_CANCEL: + /* alock = &cs->args.nlm4_cancargs.alock; */ + ret = nlm4svc_cancel(cs->req); + break; + + case NLM4_UNLOCK: + /* alock = &cs->args.nlm4_unlockargs.alock; */ + ret = nlm4svc_unlock(cs->req); + break; + + default: + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_RPC_CLNT_ERROR, + "(re)connect with an unexpected NLM4 procedure " + "(%d)", + nlm_proc); + } + +out: + if (cs) + GF_REF_PUT(cs); + + return ret; +} + +static rpcsvc_actor_t nlm4svc_actors[NLM4_PROC_COUNT] = { + /* 0 */ + {"NULL", nlm4svc_null, NULL, NLM4_NULL, DRC_IDEMPOTENT, 0}, + {"TEST", nlm4svc_test, NULL, NLM4_TEST, DRC_IDEMPOTENT, 0}, + {"LOCK", nlm4svc_lock, NULL, NLM4_LOCK, DRC_IDEMPOTENT, 0}, + {"CANCEL", nlm4svc_cancel, NULL, NLM4_CANCEL, DRC_NON_IDEMPOTENT, 0}, + {"UNLOCK", nlm4svc_unlock, NULL, NLM4_UNLOCK, DRC_NON_IDEMPOTENT, 0}, + /* 5 */ + {"GRANTED", NULL, NULL, NLM4_GRANTED, DRC_NA, 0}, + {"TEST", NULL, NULL, NLM4_TEST_MSG, DRC_NA, 0}, + {"LOCK", NULL, NULL, NLM4_LOCK_MSG, DRC_NA, 0}, + {"CANCEL", NULL, NULL, NLM4_CANCEL_MSG, DRC_NA, 0}, + {"UNLOCK", NULL, NULL, NLM4_UNLOCK_MSG, DRC_NA, 0}, + /* 10 */ + {"GRANTED", NULL, NULL, NLM4_GRANTED_MSG, DRC_NA, 0}, + {"TEST", NULL, NULL, NLM4_TEST_RES, DRC_NA, 0}, + {"LOCK", NULL, NULL, NLM4_LOCK_RES, DRC_NA, 0}, + {"CANCEL", NULL, NULL, NLM4_CANCEL_RES, DRC_NA, 0}, + {"UNLOCK", NULL, NULL, NLM4_UNLOCK_RES, DRC_NA, 0}, + /* 15 ; procedures 17,18,19 are not defined by nlm */ + {"GRANTED", NULL, NULL, NLM4_GRANTED_RES, DRC_NA, 0}, + {"SM_NOTIFY", NULL, NULL, NLM4_SM_NOTIFY, DRC_NA, 0}, + {"SEVENTEEN", NULL, NULL, NLM4_SEVENTEEN, DRC_NA, 0}, + {"EIGHTEEN", NULL, NULL, NLM4_EIGHTEEN, DRC_NA, 0}, + {"NINETEEN", NULL, NULL, NLM4_NINETEEN, DRC_NA, 0}, + /* 20 */ + {"SHARE", nlm4svc_share, NULL, NLM4_SHARE, DRC_NON_IDEMPOTENT, 0}, + {"UNSHARE", nlm4svc_unshare, NULL, NLM4_UNSHARE, DRC_NON_IDEMPOTENT, 0}, + {"NM_LOCK", nlm4svc_nm_lock, NULL, NLM4_NM_LOCK, DRC_NON_IDEMPOTENT, 0}, + {"FREE_ALL", nlm4svc_free_all, NULL, NLM4_FREE_ALL, DRC_IDEMPOTENT, 0}, +}; + +static rpcsvc_program_t nlm4prog = { + .progname = "NLM4", + .prognum = NLM_PROGRAM, + .progver = NLM_V4, + .progport = GF_NLM4_PORT, + .actors = nlm4svc_actors, + .numactors = NLM4_PROC_COUNT, + .min_auth = AUTH_NULL, +}; + +int +nlm4_init_state(xlator_t *nfsx) +{ + return 0; +} + +extern void * +nsm_thread(void *argv); + +void +nlm_grace_period_over(void *arg) +{ + nlm_grace_period = 0; +} + +rpcsvc_program_t * +nlm4svc_init(xlator_t *nfsx) +{ + struct nfs3_state *ns = NULL; + struct nfs_state *nfs = NULL; + dict_t *options = NULL; + int ret = -1; + char *portstr = NULL; + pthread_t thr; + struct timespec timeout = { + 0, + }; + FILE *pidfile = NULL; + pid_t pid = -1; + + /* Already inited */ + if (nlm4_inited) + return &nlm4prog; + + nfs = (struct nfs_state *)nfsx->private; + + ns = nfs->nfs3state; + if (!ns) { + gf_msg(GF_NLM, GF_LOG_ERROR, EINVAL, NFS_MSG_NLM_INIT_FAIL, + "NLM4 init failed"); + goto err; + } + nlm4prog.private = ns; + + options = dict_new(); + if (options == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL, + "dict allocation failed"); + goto err; + } + + ret = gf_asprintf(&portstr, "%d", GF_NLM4_PORT); + if (ret == -1) + goto err; + + ret = dict_set_dynstr(options, "transport.socket.listen-port", portstr); + if (ret == -1) + goto err; + ret = dict_set_str(options, "transport-type", "socket"); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + if (nfs->allow_insecure) { + ret = dict_set_str(options, "rpc-auth-allow-insecure", "on"); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + ret = dict_set_str(options, "rpc-auth.ports.insecure", "on"); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + } + + ret = dict_set_str(options, "transport.address-family", "inet"); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED, + "dict_set_str error"); + goto err; + } + + ret = rpcsvc_create_listeners(nfs->rpcsvc, options, "NLM"); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_LISTENERS_CREATE_FAIL, + "Unable to create listeners"); + goto err; + } + INIT_LIST_HEAD(&nlm_client_list); + LOCK_INIT(&nlm_client_list_lk); + + /* unlink sm-notify.pid so that when we restart rpc.statd/sm-notify + * it thinks that the machine has restarted and sends NOTIFY to clients. + */ + + /* TODO: + notify/rpc.statd is done differently on OSX + + On OSX rpc.statd is controlled by rpc.lockd and are part for launchd + (unified service management framework) + + A runcmd() should be invoking "launchctl start com.apple.lockd" + instead. This is still a theory but we need to thoroughly test it + out. Until then NLM support is non-existent on OSX. + */ + ret = sys_unlink(GF_SM_NOTIFY_PIDFILE); + if (ret == -1 && errno != ENOENT) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_UNLINK_ERROR, + "unable to unlink %s: %d", GF_SM_NOTIFY_PIDFILE, errno); + goto err; + } + /* temporary work around to restart statd, not distro/OS independent. + * Need to figure out a more graceful way + * killall will cause problems on solaris. + */ + + char *pid_file = GF_RPC_STATD_PIDFILE; + if (nfs->rpc_statd_pid_file) + pid_file = nfs->rpc_statd_pid_file; + pidfile = fopen(pid_file, "r"); + if (pidfile) { + ret = fscanf(pidfile, "%d", &pid); + if (ret <= 0) { + gf_msg(GF_NLM, GF_LOG_WARNING, errno, NFS_MSG_GET_PID_FAIL, + "unable to get pid of " + "rpc.statd from %s ", + GF_RPC_STATD_PIDFILE); + ret = runcmd(KILLALL_CMD, "-9", "rpc.statd", NULL); + } else + kill(pid, SIGKILL); + + fclose(pidfile); + } else { + gf_msg(GF_NLM, GF_LOG_WARNING, errno, NFS_MSG_OPEN_FAIL, + "opening %s of rpc.statd failed (%s)", pid_file, + strerror(errno)); + /* if ret == -1, do nothing - case either statd was not + * running or was running in valgrind mode + */ + ret = runcmd(KILLALL_CMD, "-9", "rpc.statd", NULL); + } + + ret = sys_unlink(GF_RPC_STATD_PIDFILE); + if (ret == -1 && errno != ENOENT) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_UNLINK_ERROR, + "unable to unlink %s", pid_file); + goto err; + } + + ret = runcmd(nfs->rpc_statd, NULL); + if (ret == -1) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_START_ERROR, + "unable to start %s", nfs->rpc_statd); + goto err; + } + + (void)gf_thread_create(&thr, NULL, nsm_thread, nfsx, "nfsnsm"); + + timeout.tv_sec = nlm_grace_period; + timeout.tv_nsec = 0; + + gf_timer_call_after(nfsx->ctx, timeout, nlm_grace_period_over, NULL); + nlm4_inited = _gf_true; + + if (options) + dict_unref(options); + return &nlm4prog; +err: + if (options) + dict_unref(options); + return NULL; +} + +int32_t +nlm_priv(xlator_t *this) +{ + int32_t ret = -1; + uint32_t client_count = 0; + uint64_t file_count = 0; + nlm_client_t *client = NULL; + nlm_fde_t *fde = NULL; + char key[GF_DUMP_MAX_BUF_LEN] = {0}; + char gfid_str[64] = {0}; + + gf_proc_dump_add_section("nfs.nlm"); + + if ((nlm4_inited == _gf_false) || TRY_LOCK(&nlm_client_list_lk)) + goto out; + + list_for_each_entry(client, &nlm_client_list, nlm_clients) + { + gf_proc_dump_build_key(key, "client", "%d.hostname", client_count); + gf_proc_dump_write(key, "%s\n", client->caller_name); + + file_count = 0; + list_for_each_entry(fde, &client->fdes, fde_list) + { + gf_proc_dump_build_key(key, "file", "%" PRIu64 ".gfid", file_count); + memset(gfid_str, 0, 64); + uuid_utoa_r(fde->fd->inode->gfid, gfid_str); + gf_proc_dump_write(key, "%s", gfid_str); + file_count++; + } + + gf_proc_dump_build_key(key, "client", "files-locked"); + gf_proc_dump_write(key, "%" PRIu64 "\n", file_count); + client_count++; + } + + gf_proc_dump_build_key(key, "nlm", "client-count"); + gf_proc_dump_write(key, "%d", client_count); + ret = 0; + UNLOCK(&nlm_client_list_lk); + +out: + if (ret) { + gf_proc_dump_build_key(key, "nlm", "statedump_error"); + gf_proc_dump_write(key, + "Unable to dump nlm state because " + "nlm is not initialised or nlm_client_list_lk " + "lock couldn't be acquired"); + } + + return ret; +} diff --git a/xlators/nfs/server/src/nlm4.h b/xlators/nfs/server/src/nlm4.h new file mode 100644 index 00000000000..a22032cac69 --- /dev/null +++ b/xlators/nfs/server/src/nlm4.h @@ -0,0 +1,111 @@ +/* + Copyright (c) 2012 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +#ifndef _NLM4_H_ +#define _NLM4_H_ + +#include <sys/types.h> +#include <signal.h> +#include "rpcsvc.h" +#include <glusterfs/dict.h> +#include <glusterfs/xlator.h> +#include <glusterfs/iobuf.h> +#include "nfs.h" +#include <glusterfs/list.h> +#include "xdr-nfs3.h" +#include <glusterfs/locking.h> +#include "nfs3-fh.h" +#include <glusterfs/compat-uuid.h> +#include "nlm4-xdr.h" +#include <glusterfs/lkowner.h> + +#define NLM4_NULL 0 +#define NLM4_TEST 1 +#define NLM4_LOCK 2 +#define NLM4_CANCEL 3 +#define NLM4_UNLOCK 4 +#define NLM4_GRANTED 5 +#define NLM4_TEST_MSG 6 +#define NLM4_LOCK_MSG 7 +#define NLM4_CANCEL_MSG 8 +#define NLM4_UNLOCK_MSG 9 +#define NLM4_GRANTED_MSG 10 +#define NLM4_TEST_RES 11 +#define NLM4_LOCK_RES 12 +#define NLM4_CANCEL_RES 13 +#define NLM4_UNLOCK_RES 14 +#define NLM4_GRANTED_RES 15 +#define NLM4_SM_NOTIFY 16 +#define NLM4_SEVENTEEN 17 +#define NLM4_EIGHTEEN 18 +#define NLM4_NINETEEN 19 +#define NLM4_SHARE 20 +#define NLM4_UNSHARE 21 +#define NLM4_NM_LOCK 22 +#define NLM4_FREE_ALL 23 +#define NLM4_PROC_COUNT 24 + +/* Registered with portmap */ +#define GF_NLM4_PORT 38468 +#define GF_NLM GF_NFS "-NLM" +#if defined(GF_DARWIN_HOST_OS) +#define GF_RPC_STATD_PROG "/usr/sbin/rpc.statd" +#define GF_RPC_STATD_PIDFILE "/var/run/statd.pid" +#define GF_SM_NOTIFY_PIDFILE "/var/run/statd.notify.pid" +#elif defined(__NetBSD__) +#define GF_RPC_STATD_PROG "/usr/sbin/rpc.statd" +#define GF_RPC_STATD_PIDFILE "/var/run/rpc.statd.pid" +#define GF_SM_NOTIFY_PIDFILE "/var/run/inexistent.pid" +#else +#define GF_RPC_STATD_PROG "/sbin/rpc.statd" +#define GF_RPC_STATD_PIDFILE "/var/run/rpc.statd.pid" +#define GF_SM_NOTIFY_PIDFILE "/var/run/sm-notify.pid" +#endif + +extern rpcsvc_program_t * +nlm4svc_init(xlator_t *nfsx); + +extern int +nlm4_init_state(xlator_t *nfsx); + +#define NLM_PROGRAM 100021 +#define NLM_V4 4 + +typedef struct nlm4_lwowner { + char temp[1024]; +} nlm4_lkowner_t; + +typedef struct nlm_client { + struct sockaddr_storage sa; + pid_t uniq; + struct list_head nlm_clients; + struct list_head fdes; + struct list_head shares; + struct rpc_clnt *rpc_clnt; + char *caller_name; + int nsm_monitor; +} nlm_client_t; + +typedef struct nlm_share { + struct list_head client_list; + struct list_head inode_list; + gf_lkowner_t lkowner; + inode_t *inode; + fsh_mode mode; + fsh_access access; +} nlm_share_t; + +typedef struct nlm_fde { + struct list_head fde_list; + fd_t *fd; + int transit_cnt; +} nlm_fde_t; + +#endif diff --git a/xlators/nfs/server/src/nlmcbk_svc.c b/xlators/nfs/server/src/nlmcbk_svc.c new file mode 100644 index 00000000000..eaa7b916190 --- /dev/null +++ b/xlators/nfs/server/src/nlmcbk_svc.c @@ -0,0 +1,134 @@ +/* + Copyright (c) 2012 Gluster, Inc. <http://www.gluster.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +/* + * Please do not edit this file. + * It was generated using rpcgen. + */ + +#include "nlm4.h" +#include <glusterfs/logging.h> +#include "nfs-messages.h" +#include <stdio.h> +#include <stdlib.h> +#include <rpc/pmap_clnt.h> +#include <string.h> +#include <memory.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#ifndef SIG_PF +#define SIG_PF void (*)(int) +#endif + +void +nlm4svc_sm_notify(struct nlm_sm_status *status); + +void * +nlmcbk_sm_notify_0_svc(struct nlm_sm_status *status, struct svc_req *req) +{ + nlm4svc_sm_notify(status); + return NULL; +} + +static void +nlmcbk_program_0(struct svc_req *rqstp, register SVCXPRT *transp) +{ + union { + struct nlm_sm_status nlmcbk_sm_notify_0_arg; + } argument; + char *result; + xdrproc_t _xdr_argument, _xdr_result; + char *(*local)(char *, struct svc_req *); + + switch (rqstp->rq_proc) { + case NULLPROC: + (void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL); + return; + + case NLMCBK_SM_NOTIFY: + _xdr_argument = (xdrproc_t)xdr_nlm_sm_status; + _xdr_result = (xdrproc_t)xdr_void; + local = (char *(*)(char *, struct svc_req *))nlmcbk_sm_notify_0_svc; + break; + + default: + svcerr_noproc(transp); + return; + } + memset((char *)&argument, 0, sizeof(argument)); + if (!svc_getargs(transp, (xdrproc_t)_xdr_argument, (caddr_t)&argument)) { + svcerr_decode(transp); + return; + } + result = (*local)((char *)&argument, rqstp); + if (!svc_sendreply(transp, (xdrproc_t)_xdr_result, result)) { + svcerr_systemerr(transp); + } + + if (!svc_freeargs(transp, (xdrproc_t)_xdr_argument, (caddr_t)&argument)) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_ARG_FREE_FAIL, + "unable to free arguments"); + return; + } + return; +} + +void * +nsm_thread(void *argv) +{ + xlator_t *nfsx = argv; + register SVCXPRT *transp; + int ret = 0; + + GF_ASSERT(nfsx); + + THIS = nfsx; + + ret = pmap_unset(NLMCBK_PROGRAM, NLMCBK_V1); + if (ret == 0) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_PMAP_UNSET_FAIL, + "pmap_unset failed"); + return NULL; + } + transp = svcudp_create(RPC_ANYSOCK); + if (transp == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_UDP_SERV_FAIL, + "cannot create udp service."); + return NULL; + } + if (!svc_register(transp, NLMCBK_PROGRAM, NLMCBK_V1, nlmcbk_program_0, + IPPROTO_UDP)) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_REG_NLMCBK_FAIL, + "unable to register (NLMCBK_PROGRAM, " + "NLMCBK_V0, udp)."); + return NULL; + } + + transp = svctcp_create(RPC_ANYSOCK, 0, 0); + if (transp == NULL) { + gf_msg(GF_NLM, GF_LOG_ERROR, errno, NFS_MSG_TCP_SERV_FAIL, + "cannot create tcp service."); + return NULL; + } + if (!svc_register(transp, NLMCBK_PROGRAM, NLMCBK_V1, nlmcbk_program_0, + IPPROTO_TCP)) { + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_REG_NLMCBK_FAIL, + "unable to register (NLMCBK_PROGRAM, " + "NLMCBK_V0, tcp)."); + return NULL; + } + + svc_run(); + gf_msg(GF_NLM, GF_LOG_ERROR, 0, NFS_MSG_SVC_RUN_RETURNED, + "svc_run returned"); + return NULL; + /* NOTREACHED */ +} |
