NFS: add COPY_NOTIFY operation
authorOlga Kornievskaia <kolga@netapp.com>
Tue, 4 Jun 2019 20:14:30 +0000 (16:14 -0400)
committerOlga Kornievskaia <olga.kornievskaia@gmail.com>
Wed, 9 Oct 2019 16:05:45 +0000 (12:05 -0400)
Try using the delegation stateid, then the open stateid.

Only NL4_NETATTR, No support for NL4_NAME and NL4_URL.
Allow only one source server address to be returned for now.

To distinguish between same server copy offload ("intra") and
a copy between different server ("inter"), do a check of server
owner identity and also make sure server is capable of doing
a copy offload.

Signed-off-by: Andy Adamson <andros@netapp.com>
Signed-off-by: Olga Kornievskaia <kolga@netapp.com>
fs/nfs/nfs42.h
fs/nfs/nfs42proc.c
fs/nfs/nfs42xdr.c
fs/nfs/nfs4_fs.h
fs/nfs/nfs4client.c
fs/nfs/nfs4file.c
fs/nfs/nfs4proc.c
fs/nfs/nfs4xdr.c
include/linux/nfs4.h
include/linux/nfs_fs_sb.h
include/linux/nfs_xdr.h

index 901cca7..4995731 100644 (file)
@@ -13,6 +13,7 @@
 #define PNFS_LAYOUTSTATS_MAXDEV (4)
 
 /* nfs4.2proc.c */
+#ifdef CONFIG_NFS_V4_2
 int nfs42_proc_allocate(struct file *, loff_t, loff_t);
 ssize_t nfs42_proc_copy(struct file *, loff_t, struct file *, loff_t, size_t);
 int nfs42_proc_deallocate(struct file *, loff_t, loff_t);
@@ -23,5 +24,16 @@ int nfs42_proc_clone(struct file *, struct file *, loff_t, loff_t, loff_t);
 int nfs42_proc_layouterror(struct pnfs_layout_segment *lseg,
                           const struct nfs42_layout_error *errors,
                           size_t n);
+int nfs42_proc_copy_notify(struct file *, struct file *,
+                          struct nfs42_copy_notify_res *);
+static inline bool nfs42_files_from_same_server(struct file *in,
+                                               struct file *out)
+{
+       struct nfs_client *c_in = (NFS_SERVER(file_inode(in)))->nfs_client;
+       struct nfs_client *c_out = (NFS_SERVER(file_inode(out)))->nfs_client;
 
+       return nfs4_check_serverowner_major_id(c_in->cl_serverowner,
+                                              c_out->cl_serverowner);
+}
+#endif /* CONFIG_NFS_V4_2 */
 #endif /* __LINUX_FS_NFS_NFS4_2_H */
index 5196bfa..6317dd8 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (c) 2014 Anna Schumaker <Anna.Schumaker@Netapp.com>
  */
 #include <linux/fs.h>
+#include <linux/sunrpc/addr.h>
 #include <linux/sunrpc/sched.h>
 #include <linux/nfs.h>
 #include <linux/nfs3.h>
 #include "pnfs.h"
 #include "nfs4session.h"
 #include "internal.h"
+#include "delegation.h"
 
 #define NFSDBG_FACILITY NFSDBG_PROC
 static int nfs42_do_offload_cancel_async(struct file *dst, nfs4_stateid *std);
 
+static void nfs42_set_netaddr(struct file *filep, struct nfs42_netaddr *naddr)
+{
+       struct nfs_client *clp = (NFS_SERVER(file_inode(filep)))->nfs_client;
+       unsigned short port = 2049;
+
+       rcu_read_lock();
+       naddr->netid_len = scnprintf(naddr->netid,
+                                       sizeof(naddr->netid), "%s",
+                                       rpc_peeraddr2str(clp->cl_rpcclient,
+                                       RPC_DISPLAY_NETID));
+       naddr->addr_len = scnprintf(naddr->addr,
+                                       sizeof(naddr->addr),
+                                       "%s.%u.%u",
+                                       rpc_peeraddr2str(clp->cl_rpcclient,
+                                       RPC_DISPLAY_ADDR),
+                                       port >> 8, port & 255);
+       rcu_read_unlock();
+}
+
 static int _nfs42_proc_fallocate(struct rpc_message *msg, struct file *filep,
                struct nfs_lock_context *lock, loff_t offset, loff_t len)
 {
@@ -459,6 +480,76 @@ static int nfs42_do_offload_cancel_async(struct file *dst,
        return status;
 }
 
+int _nfs42_proc_copy_notify(struct file *src, struct file *dst,
+                           struct nfs42_copy_notify_args *args,
+                           struct nfs42_copy_notify_res *res)
+{
+       struct nfs_server *src_server = NFS_SERVER(file_inode(src));
+       struct rpc_message msg = {
+               .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_COPY_NOTIFY],
+               .rpc_argp = args,
+               .rpc_resp = res,
+       };
+       int status;
+       struct nfs_open_context *ctx;
+       struct nfs_lock_context *l_ctx;
+
+       ctx = get_nfs_open_context(nfs_file_open_context(src));
+       l_ctx = nfs_get_lock_context(ctx);
+       if (IS_ERR(l_ctx))
+               return PTR_ERR(l_ctx);
+
+       status = nfs4_set_rw_stateid(&args->cna_src_stateid, ctx, l_ctx,
+                                    FMODE_READ);
+       nfs_put_lock_context(l_ctx);
+       if (status)
+               return status;
+
+       status = nfs4_call_sync(src_server->client, src_server, &msg,
+                               &args->cna_seq_args, &res->cnr_seq_res, 0);
+       if (status == -ENOTSUPP)
+               src_server->caps &= ~NFS_CAP_COPY_NOTIFY;
+
+       put_nfs_open_context(nfs_file_open_context(src));
+       return status;
+}
+
+int nfs42_proc_copy_notify(struct file *src, struct file *dst,
+                               struct nfs42_copy_notify_res *res)
+{
+       struct nfs_server *src_server = NFS_SERVER(file_inode(src));
+       struct nfs42_copy_notify_args *args;
+       struct nfs4_exception exception = {
+               .inode = file_inode(src),
+       };
+       int status;
+
+       if (!(src_server->caps & NFS_CAP_COPY_NOTIFY))
+               return -EOPNOTSUPP;
+
+       args = kzalloc(sizeof(struct nfs42_copy_notify_args), GFP_NOFS);
+       if (args == NULL)
+               return -ENOMEM;
+
+       args->cna_src_fh  = NFS_FH(file_inode(src)),
+       args->cna_dst.nl4_type = NL4_NETADDR;
+       nfs42_set_netaddr(dst, &args->cna_dst.u.nl4_addr);
+       exception.stateid = &args->cna_src_stateid;
+
+       do {
+               status = _nfs42_proc_copy_notify(src, dst, args, res);
+               if (status == -ENOTSUPP) {
+                       status = -EOPNOTSUPP;
+                       goto out;
+               }
+               status = nfs4_handle_exception(src_server, status, &exception);
+       } while (exception.retry);
+
+out:
+       kfree(args);
+       return status;
+}
+
 static loff_t _nfs42_proc_llseek(struct file *filep,
                struct nfs_lock_context *lock, loff_t offset, int whence)
 {
index aed865a..ccabc0c 100644 (file)
 #define encode_offload_cancel_maxsz    (op_encode_hdr_maxsz + \
                                         XDR_QUADLEN(NFS4_STATEID_SIZE))
 #define decode_offload_cancel_maxsz    (op_decode_hdr_maxsz)
+#define encode_copy_notify_maxsz       (op_encode_hdr_maxsz + \
+                                        XDR_QUADLEN(NFS4_STATEID_SIZE) + \
+                                        1 + /* nl4_type */ \
+                                        1 + XDR_QUADLEN(NFS4_OPAQUE_LIMIT))
+#define decode_copy_notify_maxsz       (op_decode_hdr_maxsz + \
+                                        3 + /* cnr_lease_time */\
+                                        XDR_QUADLEN(NFS4_STATEID_SIZE) + \
+                                        1 + /* Support 1 cnr_source_server */\
+                                        1 + /* nl4_type */ \
+                                        1 + XDR_QUADLEN(NFS4_OPAQUE_LIMIT))
 #define encode_deallocate_maxsz                (op_encode_hdr_maxsz + \
                                         encode_fallocate_maxsz)
 #define decode_deallocate_maxsz                (op_decode_hdr_maxsz)
                                         decode_sequence_maxsz + \
                                         decode_putfh_maxsz + \
                                         decode_offload_cancel_maxsz)
+#define NFS4_enc_copy_notify_sz                (compound_encode_hdr_maxsz + \
+                                        encode_putfh_maxsz + \
+                                        encode_copy_notify_maxsz)
+#define NFS4_dec_copy_notify_sz                (compound_decode_hdr_maxsz + \
+                                        decode_putfh_maxsz + \
+                                        decode_copy_notify_maxsz)
 #define NFS4_enc_deallocate_sz         (compound_encode_hdr_maxsz + \
                                         encode_sequence_maxsz + \
                                         encode_putfh_maxsz + \
@@ -166,6 +182,26 @@ static void encode_allocate(struct xdr_stream *xdr,
        encode_fallocate(xdr, args);
 }
 
+static void encode_nl4_server(struct xdr_stream *xdr,
+                             const struct nl4_server *ns)
+{
+       encode_uint32(xdr, ns->nl4_type);
+       switch (ns->nl4_type) {
+       case NL4_NAME:
+       case NL4_URL:
+               encode_string(xdr, ns->u.nl4_str_sz, ns->u.nl4_str);
+               break;
+       case NL4_NETADDR:
+               encode_string(xdr, ns->u.nl4_addr.netid_len,
+                             ns->u.nl4_addr.netid);
+               encode_string(xdr, ns->u.nl4_addr.addr_len,
+                             ns->u.nl4_addr.addr);
+               break;
+       default:
+               WARN_ON_ONCE(1);
+       }
+}
+
 static void encode_copy(struct xdr_stream *xdr,
                        const struct nfs42_copy_args *args,
                        struct compound_hdr *hdr)
@@ -191,6 +227,15 @@ static void encode_offload_cancel(struct xdr_stream *xdr,
        encode_nfs4_stateid(xdr, &args->osa_stateid);
 }
 
+static void encode_copy_notify(struct xdr_stream *xdr,
+                              const struct nfs42_copy_notify_args *args,
+                              struct compound_hdr *hdr)
+{
+       encode_op_hdr(xdr, OP_COPY_NOTIFY, decode_copy_notify_maxsz, hdr);
+       encode_nfs4_stateid(xdr, &args->cna_src_stateid);
+       encode_nl4_server(xdr, &args->cna_dst);
+}
+
 static void encode_deallocate(struct xdr_stream *xdr,
                              const struct nfs42_falloc_args *args,
                              struct compound_hdr *hdr)
@@ -355,6 +400,25 @@ static void nfs4_xdr_enc_offload_cancel(struct rpc_rqst *req,
 }
 
 /*
+ * Encode COPY_NOTIFY request
+ */
+static void nfs4_xdr_enc_copy_notify(struct rpc_rqst *req,
+                                    struct xdr_stream *xdr,
+                                    const void *data)
+{
+       const struct nfs42_copy_notify_args *args = data;
+       struct compound_hdr hdr = {
+               .minorversion = nfs4_xdr_minorversion(&args->cna_seq_args),
+       };
+
+       encode_compound_hdr(xdr, req, &hdr);
+       encode_sequence(xdr, &args->cna_seq_args, &hdr);
+       encode_putfh(xdr, args->cna_src_fh, &hdr);
+       encode_copy_notify(xdr, args, &hdr);
+       encode_nops(&hdr);
+}
+
+/*
  * Encode DEALLOCATE request
  */
 static void nfs4_xdr_enc_deallocate(struct rpc_rqst *req,
@@ -490,6 +554,58 @@ static int decode_write_response(struct xdr_stream *xdr,
        return decode_verifier(xdr, &res->verifier.verifier);
 }
 
+static int decode_nl4_server(struct xdr_stream *xdr, struct nl4_server *ns)
+{
+       struct nfs42_netaddr *naddr;
+       uint32_t dummy;
+       char *dummy_str;
+       __be32 *p;
+       int status;
+
+       /* nl_type */
+       p = xdr_inline_decode(xdr, 4);
+       if (unlikely(!p))
+               return -EIO;
+       ns->nl4_type = be32_to_cpup(p);
+       switch (ns->nl4_type) {
+       case NL4_NAME:
+       case NL4_URL:
+               status = decode_opaque_inline(xdr, &dummy, &dummy_str);
+               if (unlikely(status))
+                       return status;
+               if (unlikely(dummy > NFS4_OPAQUE_LIMIT))
+                       return -EIO;
+               memcpy(&ns->u.nl4_str, dummy_str, dummy);
+               ns->u.nl4_str_sz = dummy;
+               break;
+       case NL4_NETADDR:
+               naddr = &ns->u.nl4_addr;
+
+               /* netid string */
+               status = decode_opaque_inline(xdr, &dummy, &dummy_str);
+               if (unlikely(status))
+                       return status;
+               if (unlikely(dummy > RPCBIND_MAXNETIDLEN))
+                       return -EIO;
+               naddr->netid_len = dummy;
+               memcpy(naddr->netid, dummy_str, naddr->netid_len);
+
+               /* uaddr string */
+               status = decode_opaque_inline(xdr, &dummy, &dummy_str);
+               if (unlikely(status))
+                       return status;
+               if (unlikely(dummy > RPCBIND_MAXUADDRLEN))
+                       return -EIO;
+               naddr->addr_len = dummy;
+               memcpy(naddr->addr, dummy_str, naddr->addr_len);
+               break;
+       default:
+               WARN_ON_ONCE(1);
+               return -EIO;
+       }
+       return 0;
+}
+
 static int decode_copy_requirements(struct xdr_stream *xdr,
                                    struct nfs42_copy_res *res) {
        __be32 *p;
@@ -529,6 +645,42 @@ static int decode_offload_cancel(struct xdr_stream *xdr,
        return decode_op_hdr(xdr, OP_OFFLOAD_CANCEL);
 }
 
+static int decode_copy_notify(struct xdr_stream *xdr,
+                             struct nfs42_copy_notify_res *res)
+{
+       __be32 *p;
+       int status, count;
+
+       status = decode_op_hdr(xdr, OP_COPY_NOTIFY);
+       if (status)
+               return status;
+       /* cnr_lease_time */
+       p = xdr_inline_decode(xdr, 12);
+       if (unlikely(!p))
+               return -EIO;
+       p = xdr_decode_hyper(p, &res->cnr_lease_time.seconds);
+       res->cnr_lease_time.nseconds = be32_to_cpup(p);
+
+       status = decode_opaque_fixed(xdr, &res->cnr_stateid, NFS4_STATEID_SIZE);
+       if (unlikely(status))
+               return -EIO;
+
+       /* number of source addresses */
+       p = xdr_inline_decode(xdr, 4);
+       if (unlikely(!p))
+               return -EIO;
+
+       count = be32_to_cpup(p);
+       if (count > 1)
+               pr_warn("NFS: %s: nsvr %d > Supported. Use first servers\n",
+                        __func__, count);
+
+       status = decode_nl4_server(xdr, &res->cnr_src);
+       if (unlikely(status))
+               return -EIO;
+       return 0;
+}
+
 static int decode_deallocate(struct xdr_stream *xdr, struct nfs42_falloc_res *res)
 {
        return decode_op_hdr(xdr, OP_DEALLOCATE);
@@ -657,6 +809,32 @@ out:
 }
 
 /*
+ * Decode COPY_NOTIFY response
+ */
+static int nfs4_xdr_dec_copy_notify(struct rpc_rqst *rqstp,
+                                   struct xdr_stream *xdr,
+                                   void *data)
+{
+       struct nfs42_copy_notify_res *res = data;
+       struct compound_hdr hdr;
+       int status;
+
+       status = decode_compound_hdr(xdr, &hdr);
+       if (status)
+               goto out;
+       status = decode_sequence(xdr, &res->cnr_seq_res, rqstp);
+       if (status)
+               goto out;
+       status = decode_putfh(xdr);
+       if (status)
+               goto out;
+       status = decode_copy_notify(xdr, res);
+
+out:
+       return status;
+}
+
+/*
  * Decode DEALLOCATE request
  */
 static int nfs4_xdr_dec_deallocate(struct rpc_rqst *rqstp,
index 16b2e5c..8e590b4 100644 (file)
@@ -457,6 +457,8 @@ int nfs41_discover_server_trunking(struct nfs_client *clp,
                        struct nfs_client **, const struct cred *);
 extern void nfs4_schedule_session_recovery(struct nfs4_session *, int);
 extern void nfs41_notify_server(struct nfs_client *);
+bool nfs4_check_serverowner_major_id(struct nfs41_server_owner *o1,
+                       struct nfs41_server_owner *o2);
 #else
 static inline void nfs4_schedule_session_recovery(struct nfs4_session *session, int err)
 {
index da62040..54aaf55 100644 (file)
@@ -629,7 +629,7 @@ out:
 /*
  * Returns true if the server major ids match
  */
-static bool
+bool
 nfs4_check_serverowner_major_id(struct nfs41_server_owner *o1,
                                struct nfs41_server_owner *o2)
 {
index 339663d..686a6c4 100644 (file)
@@ -133,6 +133,9 @@ static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
                                      struct file *file_out, loff_t pos_out,
                                      size_t count, unsigned int flags)
 {
+       struct nfs42_copy_notify_res *cn_resp = NULL;
+       ssize_t ret;
+
        /* Only offload copy if superblock is the same */
        if (file_inode(file_in)->i_sb != file_inode(file_out)->i_sb)
                return -EXDEV;
@@ -140,7 +143,22 @@ static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
                return -EOPNOTSUPP;
        if (file_inode(file_in) == file_inode(file_out))
                return -EOPNOTSUPP;
-       return nfs42_proc_copy(file_in, pos_in, file_out, pos_out, count);
+       if (!nfs42_files_from_same_server(file_in, file_out)) {
+               cn_resp = kzalloc(sizeof(struct nfs42_copy_notify_res),
+                               GFP_NOFS);
+               if (unlikely(cn_resp == NULL))
+                       return -ENOMEM;
+
+               ret = nfs42_proc_copy_notify(file_in, file_out, cn_resp);
+               if (ret) {
+                       ret = -EOPNOTSUPP;
+                       goto out;
+               }
+       }
+       ret = nfs42_proc_copy(file_in, pos_in, file_out, pos_out, count);
+out:
+       kfree(cn_resp);
+       return ret;
 }
 
 static ssize_t nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
index 11eafcf..505045b 100644 (file)
@@ -9899,6 +9899,7 @@ static const struct nfs4_minor_version_ops nfs_v4_2_minor_ops = {
                | NFS_CAP_ALLOCATE
                | NFS_CAP_COPY
                | NFS_CAP_OFFLOAD_CANCEL
+               | NFS_CAP_COPY_NOTIFY
                | NFS_CAP_DEALLOCATE
                | NFS_CAP_SEEK
                | NFS_CAP_LAYOUTSTATS
index ab07db0..2f9315d 100644 (file)
@@ -7581,6 +7581,7 @@ const struct rpc_procinfo nfs4_procedures[] = {
        PROC42(CLONE,           enc_clone,              dec_clone),
        PROC42(COPY,            enc_copy,               dec_copy),
        PROC42(OFFLOAD_CANCEL,  enc_offload_cancel,     dec_offload_cancel),
+       PROC42(COPY_NOTIFY,     enc_copy_notify,        dec_copy_notify),
        PROC(LOOKUPP,           enc_lookupp,            dec_lookupp),
        PROC42(LAYOUTERROR,     enc_layouterror,        dec_layouterror),
 };
index 5810e24..5e7a526 100644 (file)
@@ -537,6 +537,7 @@ enum {
        NFSPROC4_CLNT_CLONE,
        NFSPROC4_CLNT_COPY,
        NFSPROC4_CLNT_OFFLOAD_CANCEL,
+       NFSPROC4_CLNT_COPY_NOTIFY,
 
        NFSPROC4_CLNT_LOOKUPP,
        NFSPROC4_CLNT_LAYOUTERROR,
index a87fe85..e1c8748 100644 (file)
@@ -276,5 +276,6 @@ struct nfs_server {
 #define NFS_CAP_COPY           (1U << 24)
 #define NFS_CAP_OFFLOAD_CANCEL (1U << 25)
 #define NFS_CAP_LAYOUTERROR    (1U << 26)
+#define NFS_CAP_COPY_NOTIFY    (1U << 27)
 
 #endif
index 9b8324e..0a7af40 100644 (file)
@@ -1463,6 +1463,22 @@ struct nfs42_offload_status_res {
        int                             osr_status;
 };
 
+struct nfs42_copy_notify_args {
+       struct nfs4_sequence_args       cna_seq_args;
+
+       struct nfs_fh           *cna_src_fh;
+       nfs4_stateid            cna_src_stateid;
+       struct nl4_server       cna_dst;
+};
+
+struct nfs42_copy_notify_res {
+       struct nfs4_sequence_res        cnr_seq_res;
+
+       struct nfstime4         cnr_lease_time;
+       nfs4_stateid            cnr_stateid;
+       struct nl4_server       cnr_src;
+};
+
 struct nfs42_seek_args {
        struct nfs4_sequence_args       seq_args;