[RFC 2/3] ksmbd: add splice-based read payload helper
"wang zhaolong" <[email protected]>
| Newsgroups | org.kernel.vger.linux-cifs,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Wang Zhaolong <[email protected]> Add ksmbd_vfs_read_payload() to collect page-cache backed splice buffers into a bio_vec payload. The actor takes a reference to every page so the payload remains valid after the internal splice pipe is drained. Preserve the buffered READ path's directory, zero-length, access and byte-range lock semantics. Streams, non-regular files, O_DIRECT, DAX and files without splice_read support return -EOPNOTSUPP with the offset unchanged, allowing the caller to retry through kernel_read(). A short read before EOF is handled the same way to preserve the existing READ data path. Signed-off-by: Wang Zhaolong <[email protected]> --- fs/smb/server/vfs.c | 195 ++++++++++++++++++++++++++++++++++++++------ fs/smb/server/vfs.h | 4 + 2 files changed, 176 insertions(+), 23 deletions(-) diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index d0a0ad15d803..3a3f1b8e2d33 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -17,10 +17,13 @@ #include <linux/dcache.h> #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/sched/xacct.h> #include <linux/crc32c.h> +#include <linux/net.h> +#include <linux/overflow.h> +#include <linux/pipe_fs_i.h> #include <linux/splice.h> #include <linux/fileattr.h> #include "glob.h" #include "oplock.h" @@ -326,10 +329,33 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end, out: spin_unlock(&ctx->flc_lock); return error; } +static int ksmbd_vfs_check_read_access(struct ksmbd_work *work, + struct ksmbd_file *fp) +{ + if (work->conn->connection_type && + !(fp->daccess & (FILE_READ_DATA_LE | FILE_EXECUTE_LE))) { + pr_err("no right to read(%pD)\n", fp->filp); + return -EACCES; + } + return 0; +} + +static int ksmbd_vfs_check_read_range(struct ksmbd_work *work, + struct ksmbd_file *fp, loff_t pos, + size_t count) +{ + if (!work->tcon->posix_extensions && + check_lock_range(fp->filp, pos, pos + count - 1, READ)) { + pr_err("unable to read due to lock\n"); + return -EAGAIN; + } + return 0; +} + /** * ksmbd_vfs_read() - vfs helper for smb file read * @work: smb work * @fp: ksmbd file pointer * @count: read byte count @@ -340,47 +366,170 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end, */ int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count, loff_t *pos, char *rbuf) { struct file *filp = fp->filp; - ssize_t nbytes = 0; - struct inode *inode = file_inode(filp); + ssize_t nbytes; + int ret; - if (S_ISDIR(inode->i_mode)) + if (S_ISDIR(file_inode(filp)->i_mode)) return -EISDIR; - if (unlikely(count == 0)) return 0; - - if (work->conn->connection_type) { - if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_EXECUTE_LE))) { - pr_err("no right to read(%pD)\n", fp->filp); - return -EACCES; - } - } - + ret = ksmbd_vfs_check_read_access(work, fp); + if (ret) + return ret; if (ksmbd_stream_fd(fp)) return ksmbd_vfs_stream_read(fp, rbuf, pos, count); - - if (!work->tcon->posix_extensions) { - int ret; - - ret = check_lock_range(filp, *pos, *pos + count - 1, READ); - if (ret) { - pr_err("unable to read due to lock\n"); - return -EAGAIN; - } - } + ret = ksmbd_vfs_check_read_range(work, fp, *pos, count); + if (ret) + return ret; nbytes = kernel_read(filp, rbuf, count, pos); if (nbytes < 0) { pr_err("smb read failed, err = %zd\n", nbytes); return nbytes; } + filp->f_pos = *pos; + ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, nbytes); + return nbytes; +} + +static int ksmbd_read_payload_reserve(struct ksmbd_read_payload *payload, + unsigned int nr_bvecs) +{ + struct bio_vec *bvec; + unsigned int needed, nr_alloc; + if (check_add_overflow(payload->nr_bvecs, nr_bvecs, &needed)) + return -EOVERFLOW; + if (payload->nr_alloc >= needed) + return 0; + nr_alloc = payload->nr_alloc ?: 16; + while (nr_alloc < needed) { + if (nr_alloc > UINT_MAX / 2) { + nr_alloc = needed; + break; + } + nr_alloc *= 2; + } + bvec = kvrealloc(payload->bvec, + array_size(nr_alloc, sizeof(*payload->bvec)), + KSMBD_DEFAULT_GFP | __GFP_ZERO); + if (!bvec) + return -ENOMEM; + payload->bvec = bvec; + payload->nr_alloc = nr_alloc; + return 0; +} + +static int ksmbd_read_payload_actor(struct pipe_inode_info *pipe, + struct pipe_buffer *buf, + struct splice_desc *sd) +{ + struct ksmbd_read_payload *payload = sd->u.data; + unsigned int offset = buf->offset, len = sd->len; + size_t payload_len; + unsigned int nr_bvecs; + int ret; + + nr_bvecs = DIV_ROUND_UP(offset_in_page(offset) + len, PAGE_SIZE); + ret = ksmbd_read_payload_reserve(payload, nr_bvecs); + if (ret) + return ret; + if (check_add_overflow(payload->len, (size_t)len, &payload_len)) + return -EOVERFLOW; + while (len) { + struct page *page = buf->page + offset / PAGE_SIZE; + unsigned int page_offset = offset_in_page(offset); + unsigned int bytes = min_t(unsigned int, len, + PAGE_SIZE - page_offset); + + if (!sendpage_ok(page)) + return -EOPNOTSUPP; + get_page(page); + bvec_set_page(&payload->bvec[payload->nr_bvecs++], page, + bytes, page_offset); + offset += bytes; + len -= bytes; + } + payload->len = payload_len; + return sd->len; +} + +static int ksmbd_direct_splice_actor(struct pipe_inode_info *pipe, + struct splice_desc *sd) +{ + return __splice_from_pipe(pipe, sd, ksmbd_read_payload_actor); +} + +/** + * ksmbd_vfs_read_payload() - collect file pages for an SMB READ response + * @work: smb work + * @fp: ksmbd file pointer + * @count: read byte count + * @pos: file position, advanced only on success + * @payload: payload returned on success, otherwise set to NULL + * + * Return: number of bytes read, or -EOPNOTSUPP with @pos unchanged when the + * caller may safely retry through ksmbd_vfs_read(). + */ +int ksmbd_vfs_read_payload(struct ksmbd_work *work, struct ksmbd_file *fp, + size_t count, loff_t *pos, + struct ksmbd_read_payload **payload) +{ + struct file *filp = fp->filp; + struct ksmbd_read_payload *read_payload; + loff_t start = *pos; + struct splice_desc sd; + ssize_t nbytes; + int ret; + + *payload = NULL; + if (S_ISDIR(file_inode(filp)->i_mode)) + return -EISDIR; + if (unlikely(count == 0)) + return 0; + ret = ksmbd_vfs_check_read_access(work, fp); + if (ret) + return ret; + if (ksmbd_stream_fd(fp) || !S_ISREG(file_inode(filp)->i_mode) || + (filp->f_flags & O_DIRECT) || IS_DAX(file_inode(filp)) || + !(filp->f_mode & FMODE_LSEEK) || !filp->f_op->splice_read) + return -EOPNOTSUPP; + ret = ksmbd_vfs_check_read_range(work, fp, start, count); + if (ret) + return ret; + + read_payload = kzalloc_obj(struct ksmbd_read_payload, + KSMBD_DEFAULT_GFP); + if (!read_payload) + return -ENOMEM; + nbytes = rw_verify_area(READ, filp, pos, count); + if (nbytes) + goto out_free; + sd = (struct splice_desc) { + .total_len = count, + .pos = start, + .u.data = read_payload, + }; + nbytes = splice_direct_to_actor(filp, &sd, ksmbd_direct_splice_actor); + if (nbytes <= 0) + goto out_free; + if (nbytes < count && start + nbytes < i_size_read(file_inode(filp))) { + nbytes = -EOPNOTSUPP; + goto out_free; + } + *pos = start + nbytes; filp->f_pos = *pos; - ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, (s64)nbytes); + ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, nbytes); + *payload = read_payload; + return nbytes; + +out_free: + *pos = start; + ksmbd_read_payload_release(read_payload); return nbytes; } static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos, size_t count) diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h index 7b3d2f4fd985..5afee75c4a3b 100644 --- a/fs/smb/server/vfs.h +++ b/fs/smb/server/vfs.h @@ -34,10 +34,11 @@ enum { #define CREATE_OPTION_SPECIAL 0x20000000 struct ksmbd_work; struct ksmbd_file; struct ksmbd_conn; +struct ksmbd_read_payload; struct ksmbd_dir_info { const char *name; char *wptr; char *rptr; @@ -77,10 +78,13 @@ void ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap, struct dentry *dentry, __le32 *daccess); int ksmbd_vfs_create(struct ksmbd_work *work, const char *name, umode_t mode); int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode); int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count, loff_t *pos, char *rbuf); +int ksmbd_vfs_read_payload(struct ksmbd_work *work, struct ksmbd_file *fp, + size_t count, loff_t *pos, + struct ksmbd_read_payload **payload); int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp, char *buf, size_t count, loff_t *pos, bool sync, ssize_t *written); int ksmbd_vfs_fsync(struct ksmbd_work *work, u64 fid, u64 p_id); int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path); -- 2.47.3