summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/lib/smbsrv/libfksmbsrv/Makefile.com1
-rw-r--r--usr/src/uts/common/Makefile.files1
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_fsctl_fs.c4
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_fsctl_odx.c847
-rw-r--r--usr/src/uts/common/smbsrv/smb2_kproto.h2
5 files changed, 854 insertions, 1 deletions
diff --git a/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com b/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com
index 64543315e6..507122dadd 100644
--- a/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com
+++ b/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com
@@ -135,6 +135,7 @@ OBJS_FS_SMBSRV = \
smb2_flush.o \
smb2_fsctl_copychunk.o \
smb2_fsctl_fs.o \
+ smb2_fsctl_odx.o \
smb2_fsctl_sparse.o \
smb2_ioctl.o \
smb2_lease.o \
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index 058fe8006b..708e9914fc 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -1210,6 +1210,7 @@ SMBSRV_OBJS += $(SMBSRV_SHARED_OBJS) \
smb2_flush.o \
smb2_fsctl_copychunk.o \
smb2_fsctl_fs.o \
+ smb2_fsctl_odx.o \
smb2_fsctl_sparse.o \
smb2_ioctl.o \
smb2_lease.o \
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_fsctl_fs.c b/usr/src/uts/common/fs/smbsrv/smb2_fsctl_fs.c
index 69c9f37506..829beda2e4 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_fsctl_fs.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_fsctl_fs.c
@@ -146,8 +146,10 @@ smb2_fsctl_fs(smb_request_t *sr, smb_fsctl_t *fsctl)
func = smb2_fsctl_notsup;
break;
case FSCTL_OFFLOAD_READ: /* 153 */
+ func = smb2_fsctl_odx_read;
+ break;
case FSCTL_OFFLOAD_WRITE: /* 154 */
- func = smb2_fsctl_notsup;
+ func = smb2_fsctl_odx_write;
break;
case FSCTL_SET_INTEGRITY_INFORMATION: /* 160 */
func = smb2_fsctl_notsup;
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_fsctl_odx.c b/usr/src/uts/common/fs/smbsrv/smb2_fsctl_odx.c
new file mode 100644
index 0000000000..0452cddb39
--- /dev/null
+++ b/usr/src/uts/common/fs/smbsrv/smb2_fsctl_odx.c
@@ -0,0 +1,847 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * Support functions for smb2_ioctl/fsctl codes:
+ * FSCTL_SRV_OFFLOAD_READ
+ * FSCTL_SRV_OFFLOAD_WRITE
+ * (and related)
+ */
+
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_fsops.h>
+#include <smb/winioctl.h>
+
+/*
+ * Summary of how offload data transfer works:
+ *
+ * The client drives a server-side copy. Outline:
+ * 1: open src_file
+ * 2: create dst_file and set its size
+ * 3: while src_file not all copied {
+ * offload_read(src_file, &token);
+ * while token not all copied {
+ * offload_write(dst_file, token);
+ * }
+ * }
+ *
+ * Each "offload read" request returns a "token" representing some
+ * portion of the source file. The server decides what kind of
+ * token to use, and how much of the source file it should cover.
+ * The length represented may be less then the client requested.
+ * No data are copied during offload_read (just meta-data).
+ *
+ * Each "offload write" request copies some portion of the data
+ * represented by the "token" into the output file. The amount
+ * of data copied may be less than the client requested, and the
+ * client keeps sending offload write requests until they have
+ * copied all the data represented by the current token.
+ */
+
+/* [MS-FSA] OFFLOAD_READ_FLAG_ALL_ZERO_BEYOND_CURRENT_RANGE */
+#define OFFLOAD_READ_FLAG_ALL_ZERO_BEYOND 1
+
+/*
+ * [MS-FSCC] 2.3.79 STORAGE_OFFLOAD_TOKEN
+ * Note reserved: 0xFFFF0002 – 0xFFFFFFFF
+ *
+ * ...TOKEN_TYPE_ZERO_DATA: A well-known Token that indicates ...
+ * (offload write should just zero to the destination)
+ * The payload (tok_other) is ignored with this type.
+ */
+#define STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA 0xFFFF0001
+
+/* Our vendor-specific token type: struct tok_native1 */
+#define STORAGE_OFFLOAD_TOKEN_TYPE_NATIVE1 0x10001
+
+#define TOKEN_TOTAL_SIZE 512
+#define TOKEN_MAX_PAYLOAD 504 /* 512 - 8 */
+
+/* This mask is for sanity checking offsets etc. */
+#define OFFMASK ((uint64_t)DEV_BSIZE-1)
+
+typedef struct smb_odx_token {
+ uint32_t tok_type; /* big-endian on the wire */
+ uint16_t tok_reserved; /* zero */
+ uint16_t tok_len; /* big-endian on the wire */
+ union {
+ uint8_t u_tok_other[TOKEN_MAX_PAYLOAD];
+ struct tok_native1 {
+ smb2fid_t tn1_fid;
+ uint64_t tn1_off;
+ uint64_t tn1_eof;
+ } u_tok_native1;
+ } tok_u;
+} smb_odx_token_t;
+
+typedef struct odx_write_args {
+ uint32_t in_struct_size;
+ uint32_t in_flags;
+ uint64_t in_dstoff;
+ uint64_t in_xlen;
+ uint64_t in_xoff;
+ uint32_t out_struct_size;
+ uint32_t out_flags;
+ uint64_t out_xlen;
+ uint64_t wa_eof;
+} odx_write_args_t;
+
+static int smb_odx_get_token(mbuf_chain_t *, smb_odx_token_t *);
+static int smb_odx_get_token_native1(mbuf_chain_t *, struct tok_native1 *);
+static int smb_odx_put_token(mbuf_chain_t *, smb_odx_token_t *);
+static int smb_odx_put_token_native1(mbuf_chain_t *, struct tok_native1 *);
+
+static uint32_t smb2_fsctl_odx_write_zeros(smb_request_t *, odx_write_args_t *);
+static uint32_t smb2_fsctl_odx_write_native1(smb_request_t *,
+ odx_write_args_t *, smb_odx_token_t *);
+
+
+/* We can disable this feature for testing etc. */
+int smb2_odx_enable = 1;
+
+/*
+ * These two variables determine the intervals of offload_read and
+ * offload_write calls (respectively) during an offload copy.
+ *
+ * For the offload read token we could offer a token representing
+ * the whole file, but we'll have the client come back for a new
+ * "token" after each 256M so we have a chance to look for "holes".
+ * This lets us use the special "zero" token while we're in any
+ * un-allocated parts of the file, so offload_write can use the
+ * (more efficient) smb_fsop_freesp instead of copying.
+ *
+ * We limit the size of offload_write to 16M per request so we
+ * don't end up taking so long with I/O that the client might
+ * time out the request. Keep: write_max <= read_max
+ */
+uint32_t smb2_odx_read_max = (1<<28); /* 256M */
+uint32_t smb2_odx_write_max = (1<<24); /* 16M */
+
+/*
+ * This buffer size determines the I/O size for the copy during
+ * offoad write, where it will read/write using this buffer.
+ * Note: We kmem_alloc this, so don't make it HUGE. It only
+ * needs to be large enough to allow the copy to proceed with
+ * reasonable efficiency. 1M is currently the largest possible
+ * block size with ZFS, so that's what we'll use here.
+ */
+uint32_t smb2_odx_buf_size = (1<<20); /* 1M */
+
+
+/*
+ * FSCTL_OFFLOAD_READ
+ * [MS-FSCC] 2.3.77
+ *
+ * Similar (in concept) to FSCTL_SRV_REQUEST_RESUME_KEY
+ *
+ * The returned data is an (opaque to the client) 512-byte "token"
+ * that represents the specified range (offset, length) of the
+ * source file. The "token" we return here comes back to us in an
+ * FSCTL_OFFLOAD_READ. We must stash whatever we'll need then in
+ * the token we return here.
+ *
+ * We want server-side copy to be able to copy "holes" efficiently,
+ * but would rather avoid the complexity of encoding a list of all
+ * allocated ranges into our returned token, so this compromise:
+ *
+ * When the current range is entirely within a "hole", we'll return
+ * the special "zeros" token, and the offload write using that token
+ * will use the simple and very efficient smb_fsop_freesp. In this
+ * scenario, we'll have a copy stride of smb2_odx_read_max (256M).
+ *
+ * When there's any data in the range to copy, we'll return our
+ * "native" token, and the subsequent offload_write will walk the
+ * allocated ranges copying and/or zeroing as needed. In this
+ * scenario, we'll have a copy stride of smb2_odx_write_max (16M).
+ *
+ * One additional optimization allowed by the protocol is that when
+ * we discover that there's no more data after the current range,
+ * we can set the flag ..._ALL_ZERO_BEYOND which tells that client
+ * they can stop copying here if they like.
+ */
+uint32_t
+smb2_fsctl_odx_read(smb_request_t *sr, smb_fsctl_t *fsctl)
+{
+ smb_attr_t src_attr;
+ smb_odx_token_t *tok = NULL;
+ struct tok_native1 *tn1;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ uint64_t src_size, src_rnd_size;
+ off64_t data, hole;
+ uint32_t in_struct_size;
+ uint32_t in_flags;
+ uint32_t in_ttl;
+ uint64_t in_file_off;
+ uint64_t in_copy_len;
+ uint64_t out_xlen;
+ uint32_t out_struct_size = TOKEN_TOTAL_SIZE + 16;
+ uint32_t out_flags = 0;
+ uint32_t status;
+ uint32_t tok_type;
+ int rc;
+
+ if (smb2_odx_enable == 0)
+ return (NT_STATUS_NOT_SUPPORTED);
+
+ /*
+ * Make sure the (src) ofile granted access allows read.
+ * [MS-FSA] didn't mention this, so it's not clear where
+ * this should happen relative to other checks. Usually
+ * access checks happen early.
+ */
+ status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
+ if (status != NT_STATUS_SUCCESS)
+ return (status);
+
+ /*
+ * Decode FSCTL_OFFLOAD_READ_INPUT struct,
+ * and do in/out size checks.
+ */
+ rc = smb_mbc_decodef(
+ fsctl->in_mbc, "lll4.qq",
+ &in_struct_size, /* l */
+ &in_flags, /* l */
+ &in_ttl, /* l */
+ /* reserved 4. */
+ &in_file_off, /* q */
+ &in_copy_len); /* q */
+ if (rc != 0)
+ return (NT_STATUS_BUFFER_TOO_SMALL);
+ if (fsctl->MaxOutputResp < out_struct_size)
+ return (NT_STATUS_BUFFER_TOO_SMALL);
+
+ /*
+ * More arg checking per MS-FSA
+ */
+ if ((in_file_off & OFFMASK) != 0 ||
+ (in_copy_len & OFFMASK) != 0)
+ return (NT_STATUS_INVALID_PARAMETER);
+ if (in_struct_size != 32)
+ return (NT_STATUS_INVALID_PARAMETER);
+ if (in_file_off > INT64_MAX ||
+ (in_file_off + in_copy_len) < in_file_off)
+ return (NT_STATUS_INVALID_PARAMETER);
+
+ /*
+ * [MS-FSA] (summarizing)
+ * If not data stream, or if sparse, encrypted, compressed...
+ * return STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED.
+ *
+ * We'll ignore most of those except to require:
+ * Plain file, not a stream.
+ */
+ if (!smb_node_is_file(ofile->f_node))
+ return (NT_STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED);
+ if (SMB_IS_STREAM(ofile->f_node))
+ return (NT_STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED);
+
+ /*
+ * [MS-FSA] If Open.Stream.IsDeleted ...
+ * We don't really have this.
+ */
+
+ /*
+ * If CopyLength == 0, "return immediately success".
+ */
+ if (in_copy_len == 0) {
+ out_xlen = 0;
+ tok_type = STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA;
+ goto done;
+ }
+
+ /*
+ * Check for lock conflicting with the read.
+ */
+ status = smb_lock_range_access(sr, ofile->f_node,
+ in_file_off, in_copy_len, B_FALSE);
+ if (status != 0)
+ return (status); /* == FILE_LOCK_CONFLICT */
+
+ /*
+ * Get the file size (rounded to a full block)
+ * and check the requested offset.
+ */
+ bzero(&src_attr, sizeof (src_attr));
+ src_attr.sa_mask = SMB_AT_SIZE;
+ status = smb2_ofile_getattr(sr, ofile, &src_attr);
+ if (status != NT_STATUS_SUCCESS)
+ return (status);
+ src_size = src_attr.sa_vattr.va_size;
+ if (in_file_off >= src_size)
+ return (NT_STATUS_END_OF_FILE);
+
+ /*
+ * Limit the transfer length based on (rounded) EOF.
+ * Clients expect ranges of whole disk blocks.
+ * If we get a read in this rounded-up range,
+ * we'll supply zeros.
+ */
+ src_rnd_size = (src_size + OFFMASK) & ~OFFMASK;
+ out_xlen = in_copy_len;
+ if ((in_file_off + out_xlen) > src_rnd_size)
+ out_xlen = src_rnd_size - in_file_off;
+
+ /*
+ * Also, have the client come back for a new token after every
+ * smb2_odx_read_max bytes, so we'll have opportunities to
+ * recognize "holes" in the source file.
+ */
+ if (out_xlen > smb2_odx_read_max)
+ out_xlen = smb2_odx_read_max;
+
+ /*
+ * Ask the filesystem if there are any allocated regions in
+ * the requested range, and return either the "zeros" token
+ * or our "native" token as appropriate (details above).
+ */
+ data = in_file_off;
+ tok_type = STORAGE_OFFLOAD_TOKEN_TYPE_NATIVE1;
+ rc = smb_fsop_next_alloc_range(ofile->f_cr, ofile->f_node,
+ &data, &hole);
+ switch (rc) {
+ case 0:
+ /* Found some data. Is it beyond this range? */
+ if (data >= (in_file_off + out_xlen))
+ tok_type = STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA;
+ break;
+ case ENXIO:
+ /* No data here or following. */
+ tok_type = STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA;
+ out_flags |= OFFLOAD_READ_FLAG_ALL_ZERO_BEYOND;
+ break;
+ case ENOSYS: /* FS does not support VOP_IOCTL... */
+ case ENOTTY: /* ... or _FIO_SEEK_DATA, _HOLE */
+ break;
+ default:
+ cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d", rc);
+ break;
+ }
+
+done:
+ /* Already checked MaxOutputResp */
+ (void) smb_mbc_encodef(
+ fsctl->out_mbc, "llq",
+ out_struct_size, /* l */
+ out_flags, /* l */
+ out_xlen); /* q */
+
+ /*
+ * Build the ODX token to return
+ */
+ tok = smb_srm_zalloc(sr, sizeof (*tok));
+ tok->tok_type = tok_type;
+ tok->tok_reserved = 0;
+ if (tok_type == STORAGE_OFFLOAD_TOKEN_TYPE_NATIVE1) {
+ tok->tok_len = sizeof (*tn1);
+ tn1 = &tok->tok_u.u_tok_native1;
+ tn1->tn1_fid.persistent = ofile->f_persistid;
+ tn1->tn1_fid.temporal = ofile->f_fid;
+ tn1->tn1_off = in_file_off;
+ tn1->tn1_eof = src_size;
+ }
+
+ rc = smb_odx_put_token(fsctl->out_mbc, tok);
+ if (rc != 0)
+ return (NT_STATUS_BUFFER_TOO_SMALL);
+
+ return (NT_STATUS_SUCCESS);
+}
+
+/*
+ * FSCTL_SRV_OFFLOAD_WRITE
+ * [MS-FSCC] 2.3.80
+ *
+ * Similar (in concept) to FSCTL_COPYCHUNK_WRITE
+ *
+ * Copies from a source file identified by a "token"
+ * (previously returned by FSCTL_OFFLOAD_READ)
+ * to the file on which the ioctl is issued.
+ */
+uint32_t
+smb2_fsctl_odx_write(smb_request_t *sr, smb_fsctl_t *fsctl)
+{
+ smb_attr_t dst_attr;
+ odx_write_args_t args;
+ smb_odx_token_t *tok = NULL;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ uint64_t dst_rnd_size;
+ uint32_t status = NT_STATUS_INVALID_PARAMETER;
+ int rc;
+
+ bzero(&args, sizeof (args));
+ args.out_struct_size = 16;
+
+ if (smb2_odx_enable == 0)
+ return (NT_STATUS_NOT_SUPPORTED);
+
+ /*
+ * Make sure the (dst) ofile granted_access allows write.
+ * [MS-FSA] didn't mention this, so it's not clear where
+ * this should happen relative to other checks. Usually
+ * access checks happen early.
+ */
+ status = smb_ofile_access(ofile, ofile->f_cr, FILE_WRITE_DATA);
+ if (status != NT_STATUS_SUCCESS)
+ return (status);
+
+ /*
+ * Decode FSCTL_OFFLOAD_WRITE_INPUT struct,
+ * and do in/out size checks.
+ */
+ rc = smb_mbc_decodef(
+ fsctl->in_mbc, "llqqq",
+ &args.in_struct_size, /* l */
+ &args.in_flags, /* l */
+ &args.in_dstoff, /* q */
+ &args.in_xlen, /* q */
+ &args.in_xoff); /* q */
+ if (rc != 0)
+ return (NT_STATUS_BUFFER_TOO_SMALL);
+ tok = smb_srm_zalloc(sr, sizeof (*tok));
+ rc = smb_odx_get_token(fsctl->in_mbc, tok);
+ if (rc != 0)
+ return (NT_STATUS_BUFFER_TOO_SMALL);
+ if (fsctl->MaxOutputResp < args.out_struct_size)
+ return (NT_STATUS_BUFFER_TOO_SMALL);
+
+ /*
+ * More arg checking per MS-FSA
+ */
+ if ((args.in_dstoff & OFFMASK) != 0 ||
+ (args.in_xoff & OFFMASK) != 0 ||
+ (args.in_xlen & OFFMASK) != 0)
+ return (NT_STATUS_INVALID_PARAMETER);
+ if (args.in_struct_size != (TOKEN_TOTAL_SIZE + 32))
+ return (NT_STATUS_INVALID_PARAMETER);
+ if (args.in_dstoff > INT64_MAX ||
+ (args.in_dstoff + args.in_xlen) < args.in_dstoff)
+ return (NT_STATUS_INVALID_PARAMETER);
+
+ /*
+ * If CopyLength == 0, "return immediately success".
+ */
+ if (args.in_xlen == 0) {
+ status = 0;
+ goto done;
+ }
+
+ /*
+ * [MS-FSA] (summarizing)
+ * If not data stream, or if sparse, encrypted, compressed...
+ * return STATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED.
+ *
+ * We'll ignore most of those except to require:
+ * Plain file, not a stream.
+ */
+ if (!smb_node_is_file(ofile->f_node))
+ return (NT_STATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED);
+ if (SMB_IS_STREAM(ofile->f_node))
+ return (NT_STATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED);
+
+ /*
+ * [MS-FSA] If Open.Stream.IsDeleted ...
+ * We don't really have such a thing.
+ * Also skip Volume.MaxFileSize check.
+ */
+
+ /*
+ * Check for lock conflicting with the write.
+ */
+ status = smb_lock_range_access(sr, ofile->f_node,
+ args.in_dstoff, args.in_xlen, B_TRUE);
+ if (status != 0)
+ return (status); /* == FILE_LOCK_CONFLICT */
+
+ /*
+ * Need the file size
+ */
+ bzero(&dst_attr, sizeof (dst_attr));
+ dst_attr.sa_mask = SMB_AT_SIZE;
+ status = smb2_ofile_getattr(sr, ofile, &dst_attr);
+ if (status != NT_STATUS_SUCCESS)
+ return (status);
+ args.wa_eof = dst_attr.sa_vattr.va_size;
+ dst_rnd_size = (args.wa_eof + OFFMASK) & ~OFFMASK;
+
+ /*
+ * Destination offset vs. EOF
+ */
+ if (args.in_dstoff >= args.wa_eof)
+ return (NT_STATUS_END_OF_FILE);
+
+ /*
+ * Destination offset+len vs. EOF
+ *
+ * The spec. is silent about copying when the file length is
+ * not block aligned, but clients appear to ask us to copy a
+ * range that's rounded up to a block size. We'll limit the
+ * transfer size to the rounded up file size, but the actual
+ * copy will stop at EOF (args.wa_eof).
+ */
+ if ((args.in_dstoff + args.in_xlen) > dst_rnd_size)
+ args.in_xlen = dst_rnd_size - args.in_dstoff;
+
+ /*
+ * Finally, run the I/O
+ */
+ switch (tok->tok_type) {
+ case STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA:
+ status = smb2_fsctl_odx_write_zeros(sr, &args);
+ break;
+ case STORAGE_OFFLOAD_TOKEN_TYPE_NATIVE1:
+ status = smb2_fsctl_odx_write_native1(sr, &args, tok);
+ break;
+ default:
+ status = NT_STATUS_INVALID_TOKEN;
+ break;
+ }
+
+done:
+ /*
+ * Checked MaxOutputResp above, so we can ignore errors
+ * from mbc_encodef here.
+ */
+ if (status == NT_STATUS_SUCCESS) {
+ (void) smb_mbc_encodef(
+ fsctl->out_mbc, "llq",
+ args.out_struct_size,
+ args.out_flags,
+ args.out_xlen);
+ }
+
+ return (status);
+}
+
+/*
+ * Handle FSCTL_OFFLOAD_WRITE with token type
+ * STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA
+ *
+ * In this handler, the "token" represents a source of zeros.
+ */
+static uint32_t
+smb2_fsctl_odx_write_zeros(smb_request_t *sr, odx_write_args_t *args)
+{
+ smb_ofile_t *dst_ofile = sr->fid_ofile;
+ uint64_t xlen = args->in_xlen;
+ uint32_t status = 0;
+ int rc;
+
+ ASSERT(args->in_xlen > 0);
+
+ /*
+ * Limit the I/O size. In here we're just doing freesp,
+ * which is assumed to require only meta-data I/O, so
+ * we'll allow up to smb2_odx_read_max (256M) per call.
+ * This is essentially just a double-check of the range
+ * we gave the client at the offload_read call, making
+ * sure they can't use a zero token for longer ranges
+ * than offload_read would allow.
+ */
+ if (xlen > smb2_odx_read_max)
+ xlen = smb2_odx_read_max;
+
+ /*
+ * Also limit to the actual file size, which may be
+ * smaller than the (block-aligned) transfer size.
+ * Report the rounded up size to the caller at EOF.
+ */
+ args->out_xlen = xlen;
+ if ((args->in_dstoff + xlen) > args->wa_eof)
+ xlen = args->wa_eof - args->in_dstoff;
+
+ /*
+ * Arrange for zeros to appear in the range:
+ * in_dstoff, (in_dstoff + in_xlen)
+ *
+ * Just "free" the range and let it allocate as needed
+ * when someone later writes in this range.
+ */
+ rc = smb_fsop_freesp(sr, dst_ofile->f_cr, dst_ofile,
+ args->in_dstoff, xlen);
+ if (rc != 0) {
+ status = smb_errno2status(rc);
+ if (status == NT_STATUS_INVALID_PARAMETER ||
+ status == NT_STATUS_NOT_SUPPORTED)
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ args->out_xlen = 0;
+ } else {
+ status = 0;
+ }
+
+ return (status);
+}
+
+/*
+ * Handle FSCTL_OFFLOAD_WRITE with token type
+ * STORAGE_OFFLOAD_TOKEN_TYPE_NATIVE1
+ */
+static uint32_t
+smb2_fsctl_odx_write_native1(smb_request_t *sr,
+ odx_write_args_t *args, smb_odx_token_t *tok)
+{
+ struct tok_native1 *tn1;
+ smb_ofile_t *dst_ofile = sr->fid_ofile;
+ smb_ofile_t *src_ofile = NULL;
+ void *buffer = NULL;
+ size_t bufsize = smb2_odx_buf_size;
+ uint64_t src_offset;
+ uint32_t resid;
+ uint32_t xlen;
+ uint32_t status;
+
+ /*
+ * Lookup the source ofile using the resume key,
+ * which smb2_fsctl_offload_read encoded as an
+ * smb2fid_t. Similar to smb2sr_lookup_fid(),
+ * but different error code.
+ */
+ tn1 = &tok->tok_u.u_tok_native1;
+ src_ofile = smb_ofile_lookup_by_fid(sr,
+ (uint16_t)tn1->tn1_fid.temporal);
+ if (src_ofile == NULL ||
+ src_ofile->f_persistid != tn1->tn1_fid.persistent) {
+ status = NT_STATUS_INVALID_TOKEN;
+ goto out;
+ }
+
+ /*
+ * Make sure src_ofile is open on a regular file, and
+ * granted access includes READ_DATA
+ */
+ if (!smb_node_is_file(src_ofile->f_node)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+ status = smb_ofile_access(src_ofile, src_ofile->f_cr, FILE_READ_DATA);
+ if (status != NT_STATUS_SUCCESS)
+ goto out;
+
+ /*
+ * Limit the I/O size. In here we're actually copying,
+ * so limit to smb2_odx_write_max (16M) per call.
+ * Note that xlen is a 32-bit value here.
+ */
+ if (args->in_xlen > smb2_odx_write_max)
+ xlen = smb2_odx_write_max;
+ else
+ xlen = (uint32_t)args->in_xlen;
+
+ /*
+ * Also limit to the actual file size, which may be
+ * smaller than the (block-aligned) transfer size.
+ * Report the rounded up size to the caller at EOF.
+ */
+ args->out_xlen = xlen;
+ if ((args->in_dstoff + xlen) > args->wa_eof)
+ xlen = (uint32_t)(args->wa_eof - args->in_dstoff);
+
+ /*
+ * Note: in_xoff is relative to the beginning of the "token"
+ * (a range of the source file tn1_off, tn1_eof). Make sure
+ * in_xoff is within the range represented by this token.
+ */
+ src_offset = tn1->tn1_off + args->in_xoff;
+ if (src_offset >= tn1->tn1_eof ||
+ src_offset < tn1->tn1_off) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /*
+ * Get a buffer used for copying, always
+ * smb2_odx_buf_size (1M)
+ *
+ * Rather than sleep for this relatively large allocation,
+ * allow the allocation to fail and return an error.
+ * The client should then fall back to normal copy.
+ */
+ buffer = kmem_alloc(bufsize, KM_NOSLEEP | KM_NORMALPRI);
+ if (buffer == NULL) {
+ status = NT_STATUS_INSUFF_SERVER_RESOURCES;
+ goto out;
+ }
+
+ /*
+ * Copy src to dst for xlen
+ */
+ resid = xlen;
+ status = smb2_sparse_copy(sr, src_ofile, dst_ofile,
+ src_offset, args->in_dstoff, &resid, buffer, bufsize);
+
+ /*
+ * If the result was a partial copy, round down the
+ * reported transfer size to a block boundary.
+ */
+ if (resid != 0) {
+ xlen -= resid;
+ xlen &= ~OFFMASK;
+ args->out_xlen = xlen;
+ }
+
+ /*
+ * If we did any I/O, ignore the error that stopped us.
+ * We'll report this error during the next call.
+ */
+ if (args->out_xlen > 0)
+ status = 0;
+
+out:
+ if (src_ofile != NULL)
+ smb_ofile_release(src_ofile);
+
+ if (buffer != NULL)
+ kmem_free(buffer, bufsize);
+
+ return (status);
+}
+
+/*
+ * Get an smb_odx_token_t from the (input) mbuf chain.
+ * Consumes exactly TOKEN_TOTAL_SIZE bytes.
+ */
+static int
+smb_odx_get_token(mbuf_chain_t *mbc, smb_odx_token_t *tok)
+{
+ mbuf_chain_t tok_mbc;
+ int start_pos = mbc->chain_offset;
+ int rc;
+
+ if (MBC_ROOM_FOR(mbc, TOKEN_TOTAL_SIZE) == 0)
+ return (-1);
+
+ /*
+ * No big-endian support in smb_mbc_encodef, so swap
+ * the big-endian fields: tok_type (32-bits),
+ * (reserved is 16-bit zero, so no swap),
+ * and tok_len (16-bits)
+ */
+ rc = smb_mbc_decodef(
+ mbc, "l..w",
+ &tok->tok_type,
+ /* tok_reserved */
+ &tok->tok_len);
+ if (rc != 0)
+ return (rc);
+ tok->tok_type = BSWAP_32(tok->tok_type);
+ tok->tok_len = BSWAP_16(tok->tok_len);
+
+ if (tok->tok_len > TOKEN_MAX_PAYLOAD)
+ return (-1);
+ rc = MBC_SHADOW_CHAIN(&tok_mbc, mbc,
+ mbc->chain_offset, tok->tok_len);
+ if (rc != 0)
+ return (rc);
+
+ switch (tok->tok_type) {
+ case STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA:
+ /* no payload */
+ break;
+ case STORAGE_OFFLOAD_TOKEN_TYPE_NATIVE1:
+ rc = smb_odx_get_token_native1(&tok_mbc,
+ &tok->tok_u.u_tok_native1);
+ break;
+ default:
+ /* caller will error out */
+ break;
+ }
+
+ if (rc == 0) {
+ /* Advance past what we shadowed. */
+ mbc->chain_offset = start_pos + TOKEN_TOTAL_SIZE;
+ }
+
+ return (rc);
+}
+
+static int
+smb_odx_get_token_native1(mbuf_chain_t *mbc, struct tok_native1 *tn1)
+{
+ int rc;
+
+ rc = smb_mbc_decodef(
+ mbc, "qqqq",
+ &tn1->tn1_fid.persistent,
+ &tn1->tn1_fid.temporal,
+ &tn1->tn1_off,
+ &tn1->tn1_eof);
+
+ return (rc);
+}
+
+/*
+ * Put an smb_odx_token_t into the (output) mbuf chain,
+ * padded to TOKEN_TOTAL_SIZE bytes.
+ */
+static int
+smb_odx_put_token(mbuf_chain_t *mbc, smb_odx_token_t *tok)
+{
+ int rc, padlen;
+ int start_pos = mbc->chain_offset;
+ int end_pos = start_pos + TOKEN_TOTAL_SIZE;
+
+ if (tok->tok_len > TOKEN_MAX_PAYLOAD)
+ return (-1);
+
+ /*
+ * No big-endian support in smb_mbc_encodef, so swap
+ * the big-endian fields: tok_type (32-bits),
+ * (reserved is 16-bit zero, so no swap),
+ * and tok_len (16-bits)
+ */
+ rc = smb_mbc_encodef(
+ mbc, "lww",
+ BSWAP_32(tok->tok_type),
+ 0, /* tok_reserved */
+ BSWAP_16(tok->tok_len));
+ if (rc != 0)
+ return (rc);
+
+ switch (tok->tok_type) {
+ case STORAGE_OFFLOAD_TOKEN_TYPE_ZERO_DATA:
+ /* no payload */
+ break;
+ case STORAGE_OFFLOAD_TOKEN_TYPE_NATIVE1:
+ rc = smb_odx_put_token_native1(mbc,
+ &tok->tok_u.u_tok_native1);
+ break;
+ default:
+ ASSERT(0);
+ return (-1);
+ }
+
+ /* Pad out to TOKEN_TOTAL_SIZE bytes. */
+ if (mbc->chain_offset < end_pos) {
+ padlen = end_pos - mbc->chain_offset;
+ (void) smb_mbc_encodef(mbc, "#.", padlen);
+ }
+ ASSERT(mbc->chain_offset == end_pos);
+
+ return (rc);
+}
+
+static int
+smb_odx_put_token_native1(mbuf_chain_t *mbc, struct tok_native1 *tn1)
+{
+ int rc;
+
+ rc = smb_mbc_encodef(
+ mbc, "qqqq",
+ tn1->tn1_fid.persistent,
+ tn1->tn1_fid.temporal,
+ tn1->tn1_off,
+ tn1->tn1_eof);
+
+ return (rc);
+}
diff --git a/usr/src/uts/common/smbsrv/smb2_kproto.h b/usr/src/uts/common/smbsrv/smb2_kproto.h
index 886f0e946d..97b13af868 100644
--- a/usr/src/uts/common/smbsrv/smb2_kproto.h
+++ b/usr/src/uts/common/smbsrv/smb2_kproto.h
@@ -70,6 +70,8 @@ int smb3_encrypt_init_mech(smb_session_t *s);
uint32_t smb2_fsctl_fs(smb_request_t *, smb_fsctl_t *);
uint32_t smb2_fsctl_netfs(smb_request_t *, smb_fsctl_t *);
uint32_t smb2_fsctl_copychunk(smb_request_t *, smb_fsctl_t *);
+uint32_t smb2_fsctl_odx_read(smb_request_t *, smb_fsctl_t *);
+uint32_t smb2_fsctl_odx_write(smb_request_t *, smb_fsctl_t *);
uint32_t smb2_fsctl_set_resilient(smb_request_t *, smb_fsctl_t *);
/* smb2_fsctl_sparse.c */