Merge tag 'virtio-next-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[cascardo/linux.git] / fs / nfsd / nfs4proc.c
index ac71d13..d30bea8 100644 (file)
@@ -43,6 +43,8 @@
 #include "current_stateid.h"
 #include "netns.h"
 #include "acl.h"
+#include "pnfs.h"
+#include "trace.h"
 
 #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
 #include <linux/security.h>
@@ -1178,6 +1180,259 @@ nfsd4_verify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        return status == nfserr_same ? nfs_ok : status;
 }
 
+#ifdef CONFIG_NFSD_PNFS
+static const struct nfsd4_layout_ops *
+nfsd4_layout_verify(struct svc_export *exp, unsigned int layout_type)
+{
+       if (!exp->ex_layout_type) {
+               dprintk("%s: export does not support pNFS\n", __func__);
+               return NULL;
+       }
+
+       if (exp->ex_layout_type != layout_type) {
+               dprintk("%s: layout type %d not supported\n",
+                       __func__, layout_type);
+               return NULL;
+       }
+
+       return nfsd4_layout_ops[layout_type];
+}
+
+static __be32
+nfsd4_getdeviceinfo(struct svc_rqst *rqstp,
+               struct nfsd4_compound_state *cstate,
+               struct nfsd4_getdeviceinfo *gdp)
+{
+       const struct nfsd4_layout_ops *ops;
+       struct nfsd4_deviceid_map *map;
+       struct svc_export *exp;
+       __be32 nfserr;
+
+       dprintk("%s: layout_type %u dev_id [0x%llx:0x%x] maxcnt %u\n",
+              __func__,
+              gdp->gd_layout_type,
+              gdp->gd_devid.fsid_idx, gdp->gd_devid.generation,
+              gdp->gd_maxcount);
+
+       map = nfsd4_find_devid_map(gdp->gd_devid.fsid_idx);
+       if (!map) {
+               dprintk("%s: couldn't find device ID to export mapping!\n",
+                       __func__);
+               return nfserr_noent;
+       }
+
+       exp = rqst_exp_find(rqstp, map->fsid_type, map->fsid);
+       if (IS_ERR(exp)) {
+               dprintk("%s: could not find device id\n", __func__);
+               return nfserr_noent;
+       }
+
+       nfserr = nfserr_layoutunavailable;
+       ops = nfsd4_layout_verify(exp, gdp->gd_layout_type);
+       if (!ops)
+               goto out;
+
+       nfserr = nfs_ok;
+       if (gdp->gd_maxcount != 0)
+               nfserr = ops->proc_getdeviceinfo(exp->ex_path.mnt->mnt_sb, gdp);
+
+       gdp->gd_notify_types &= ops->notify_types;
+       exp_put(exp);
+out:
+       return nfserr;
+}
+
+static __be32
+nfsd4_layoutget(struct svc_rqst *rqstp,
+               struct nfsd4_compound_state *cstate,
+               struct nfsd4_layoutget *lgp)
+{
+       struct svc_fh *current_fh = &cstate->current_fh;
+       const struct nfsd4_layout_ops *ops;
+       struct nfs4_layout_stateid *ls;
+       __be32 nfserr;
+       int accmode;
+
+       switch (lgp->lg_seg.iomode) {
+       case IOMODE_READ:
+               accmode = NFSD_MAY_READ;
+               break;
+       case IOMODE_RW:
+               accmode = NFSD_MAY_READ | NFSD_MAY_WRITE;
+               break;
+       default:
+               dprintk("%s: invalid iomode %d\n",
+                       __func__, lgp->lg_seg.iomode);
+               nfserr = nfserr_badiomode;
+               goto out;
+       }
+
+       nfserr = fh_verify(rqstp, current_fh, 0, accmode);
+       if (nfserr)
+               goto out;
+
+       nfserr = nfserr_layoutunavailable;
+       ops = nfsd4_layout_verify(current_fh->fh_export, lgp->lg_layout_type);
+       if (!ops)
+               goto out;
+
+       /*
+        * Verify minlength and range as per RFC5661:
+        *  o  If loga_length is less than loga_minlength,
+        *     the metadata server MUST return NFS4ERR_INVAL.
+        *  o  If the sum of loga_offset and loga_minlength exceeds
+        *     NFS4_UINT64_MAX, and loga_minlength is not
+        *     NFS4_UINT64_MAX, the error NFS4ERR_INVAL MUST result.
+        *  o  If the sum of loga_offset and loga_length exceeds
+        *     NFS4_UINT64_MAX, and loga_length is not NFS4_UINT64_MAX,
+        *     the error NFS4ERR_INVAL MUST result.
+        */
+       nfserr = nfserr_inval;
+       if (lgp->lg_seg.length < lgp->lg_minlength ||
+           (lgp->lg_minlength != NFS4_MAX_UINT64 &&
+            lgp->lg_minlength > NFS4_MAX_UINT64 - lgp->lg_seg.offset) ||
+           (lgp->lg_seg.length != NFS4_MAX_UINT64 &&
+            lgp->lg_seg.length > NFS4_MAX_UINT64 - lgp->lg_seg.offset))
+               goto out;
+       if (lgp->lg_seg.length == 0)
+               goto out;
+
+       nfserr = nfsd4_preprocess_layout_stateid(rqstp, cstate, &lgp->lg_sid,
+                                               true, lgp->lg_layout_type, &ls);
+       if (nfserr) {
+               trace_layout_get_lookup_fail(&lgp->lg_sid);
+               goto out;
+       }
+
+       nfserr = nfserr_recallconflict;
+       if (atomic_read(&ls->ls_stid.sc_file->fi_lo_recalls))
+               goto out_put_stid;
+
+       nfserr = ops->proc_layoutget(current_fh->fh_dentry->d_inode,
+                                    current_fh, lgp);
+       if (nfserr)
+               goto out_put_stid;
+
+       nfserr = nfsd4_insert_layout(lgp, ls);
+
+out_put_stid:
+       nfs4_put_stid(&ls->ls_stid);
+out:
+       return nfserr;
+}
+
+static __be32
+nfsd4_layoutcommit(struct svc_rqst *rqstp,
+               struct nfsd4_compound_state *cstate,
+               struct nfsd4_layoutcommit *lcp)
+{
+       const struct nfsd4_layout_seg *seg = &lcp->lc_seg;
+       struct svc_fh *current_fh = &cstate->current_fh;
+       const struct nfsd4_layout_ops *ops;
+       loff_t new_size = lcp->lc_last_wr + 1;
+       struct inode *inode;
+       struct nfs4_layout_stateid *ls;
+       __be32 nfserr;
+
+       nfserr = fh_verify(rqstp, current_fh, 0, NFSD_MAY_WRITE);
+       if (nfserr)
+               goto out;
+
+       nfserr = nfserr_layoutunavailable;
+       ops = nfsd4_layout_verify(current_fh->fh_export, lcp->lc_layout_type);
+       if (!ops)
+               goto out;
+       inode = current_fh->fh_dentry->d_inode;
+
+       nfserr = nfserr_inval;
+       if (new_size <= seg->offset) {
+               dprintk("pnfsd: last write before layout segment\n");
+               goto out;
+       }
+       if (new_size > seg->offset + seg->length) {
+               dprintk("pnfsd: last write beyond layout segment\n");
+               goto out;
+       }
+       if (!lcp->lc_newoffset && new_size > i_size_read(inode)) {
+               dprintk("pnfsd: layoutcommit beyond EOF\n");
+               goto out;
+       }
+
+       nfserr = nfsd4_preprocess_layout_stateid(rqstp, cstate, &lcp->lc_sid,
+                                               false, lcp->lc_layout_type,
+                                               &ls);
+       if (nfserr) {
+               trace_layout_commit_lookup_fail(&lcp->lc_sid);
+               /* fixup error code as per RFC5661 */
+               if (nfserr == nfserr_bad_stateid)
+                       nfserr = nfserr_badlayout;
+               goto out;
+       }
+
+       nfserr = ops->proc_layoutcommit(inode, lcp);
+       if (nfserr)
+               goto out_put_stid;
+
+       if (new_size > i_size_read(inode)) {
+               lcp->lc_size_chg = 1;
+               lcp->lc_newsize = new_size;
+       } else {
+               lcp->lc_size_chg = 0;
+       }
+
+out_put_stid:
+       nfs4_put_stid(&ls->ls_stid);
+out:
+       return nfserr;
+}
+
+static __be32
+nfsd4_layoutreturn(struct svc_rqst *rqstp,
+               struct nfsd4_compound_state *cstate,
+               struct nfsd4_layoutreturn *lrp)
+{
+       struct svc_fh *current_fh = &cstate->current_fh;
+       __be32 nfserr;
+
+       nfserr = fh_verify(rqstp, current_fh, 0, NFSD_MAY_NOP);
+       if (nfserr)
+               goto out;
+
+       nfserr = nfserr_layoutunavailable;
+       if (!nfsd4_layout_verify(current_fh->fh_export, lrp->lr_layout_type))
+               goto out;
+
+       switch (lrp->lr_seg.iomode) {
+       case IOMODE_READ:
+       case IOMODE_RW:
+       case IOMODE_ANY:
+               break;
+       default:
+               dprintk("%s: invalid iomode %d\n", __func__,
+                       lrp->lr_seg.iomode);
+               nfserr = nfserr_inval;
+               goto out;
+       }
+
+       switch (lrp->lr_return_type) {
+       case RETURN_FILE:
+               nfserr = nfsd4_return_file_layouts(rqstp, cstate, lrp);
+               break;
+       case RETURN_FSID:
+       case RETURN_ALL:
+               nfserr = nfsd4_return_client_layouts(rqstp, cstate, lrp);
+               break;
+       default:
+               dprintk("%s: invalid return_type %d\n", __func__,
+                       lrp->lr_return_type);
+               nfserr = nfserr_inval;
+               break;
+       }
+out:
+       return nfserr;
+}
+#endif /* CONFIG_NFSD_PNFS */
+
 /*
  * NULL call.
  */
@@ -1679,6 +1934,36 @@ static inline u32 nfsd4_create_session_rsize(struct svc_rqst *rqstp, struct nfsd
                op_encode_channel_attrs_maxsz) * sizeof(__be32);
 }
 
+#ifdef CONFIG_NFSD_PNFS
+/*
+ * At this stage we don't really know what layout driver will handle the request,
+ * so we need to define an arbitrary upper bound here.
+ */
+#define MAX_LAYOUT_SIZE                128
+static inline u32 nfsd4_layoutget_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
+{
+       return (op_encode_hdr_size +
+               1 /* logr_return_on_close */ +
+               op_encode_stateid_maxsz +
+               1 /* nr of layouts */ +
+               MAX_LAYOUT_SIZE) * sizeof(__be32);
+}
+
+static inline u32 nfsd4_layoutcommit_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
+{
+       return (op_encode_hdr_size +
+               1 /* locr_newsize */ +
+               2 /* ns_size */) * sizeof(__be32);
+}
+
+static inline u32 nfsd4_layoutreturn_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
+{
+       return (op_encode_hdr_size +
+               1 /* lrs_stateid */ +
+               op_encode_stateid_maxsz) * sizeof(__be32);
+}
+#endif /* CONFIG_NFSD_PNFS */
+
 static struct nfsd4_operation nfsd4_ops[] = {
        [OP_ACCESS] = {
                .op_func = (nfsd4op_func)nfsd4_access,
@@ -1966,6 +2251,31 @@ static struct nfsd4_operation nfsd4_ops[] = {
                .op_get_currentstateid = (stateid_getter)nfsd4_get_freestateid,
                .op_rsize_bop = (nfsd4op_rsize)nfsd4_only_status_rsize,
        },
+#ifdef CONFIG_NFSD_PNFS
+       [OP_GETDEVICEINFO] = {
+               .op_func = (nfsd4op_func)nfsd4_getdeviceinfo,
+               .op_flags = ALLOWED_WITHOUT_FH,
+               .op_name = "OP_GETDEVICEINFO",
+       },
+       [OP_LAYOUTGET] = {
+               .op_func = (nfsd4op_func)nfsd4_layoutget,
+               .op_flags = OP_MODIFIES_SOMETHING,
+               .op_name = "OP_LAYOUTGET",
+               .op_rsize_bop = (nfsd4op_rsize)nfsd4_layoutget_rsize,
+       },
+       [OP_LAYOUTCOMMIT] = {
+               .op_func = (nfsd4op_func)nfsd4_layoutcommit,
+               .op_flags = OP_MODIFIES_SOMETHING,
+               .op_name = "OP_LAYOUTCOMMIT",
+               .op_rsize_bop = (nfsd4op_rsize)nfsd4_layoutcommit_rsize,
+       },
+       [OP_LAYOUTRETURN] = {
+               .op_func = (nfsd4op_func)nfsd4_layoutreturn,
+               .op_flags = OP_MODIFIES_SOMETHING,
+               .op_name = "OP_LAYOUTRETURN",
+               .op_rsize_bop = (nfsd4op_rsize)nfsd4_layoutreturn_rsize,
+       },
+#endif /* CONFIG_NFSD_PNFS */
 
        /* NFSv4.2 operations */
        [OP_ALLOCATE] = {