diff options
Diffstat (limited to 'usr/src')
-rw-r--r-- | usr/src/lib/smbsrv/libfksmbsrv/Makefile.com | 1 | ||||
-rw-r--r-- | usr/src/uts/common/Makefile.files | 1 | ||||
-rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb2_fsctl_fs.c | 4 | ||||
-rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb2_fsctl_odx.c | 847 | ||||
-rw-r--r-- | usr/src/uts/common/smbsrv/smb2_kproto.h | 2 |
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 */ |