From a8eed184a0f9e5fdeec9a40d8ffbc2f3d56beb74 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 14 Dec 2010 13:36:08 -0800 Subject: Implement "use sendfile = yes" for SMB2. (cherry picked from commit 95cb7adcd03a1abbd0af395b6c96dd8e0eebd3d1) Autobuild-User: Jeremy Allison Autobuild-Date: Wed Dec 15 02:24:08 CET 2010 on sn-devel-104 --- source3/include/proto.h | 5 ++ source3/smbd/reply.c | 5 +- source3/smbd/smb2_read.c | 148 +++++++++++++++++++++++++++++++++++++++++++++ source3/smbd/smb2_server.c | 9 +++ 4 files changed, 164 insertions(+), 3 deletions(-) (limited to 'source3') diff --git a/source3/include/proto.h b/source3/include/proto.h index 2ba2153258..0f02dfbecf 100644 --- a/source3/include/proto.h +++ b/source3/include/proto.h @@ -5272,6 +5272,11 @@ NTSTATUS unlink_internals(connection_struct *conn, struct smb_request *req, uint32 dirtype, struct smb_filename *smb_fname, bool has_wild); void reply_unlink(struct smb_request *req); +ssize_t fake_sendfile(files_struct *fsp, SMB_OFF_T startpos, size_t nread); +void sendfile_short_send(files_struct *fsp, + ssize_t nread, + size_t headersize, + size_t smb_maxcnt); void reply_readbraw(struct smb_request *req); void reply_lockread(struct smb_request *req); void reply_read(struct smb_request *req); diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c index 9601f5353a..26badc4ebf 100644 --- a/source3/smbd/reply.c +++ b/source3/smbd/reply.c @@ -2804,8 +2804,7 @@ static void fail_readraw(void) Fake (read/write) sendfile. Returns -1 on read or write fail. ****************************************************************************/ -static ssize_t fake_sendfile(files_struct *fsp, SMB_OFF_T startpos, - size_t nread) +ssize_t fake_sendfile(files_struct *fsp, SMB_OFF_T startpos, size_t nread) { size_t bufsize; size_t tosend = nread; @@ -2869,7 +2868,7 @@ static ssize_t fake_sendfile(files_struct *fsp, SMB_OFF_T startpos, requested. Fill with zeros (all we can do). ****************************************************************************/ -static void sendfile_short_send(files_struct *fsp, +void sendfile_short_send(files_struct *fsp, ssize_t nread, size_t headersize, size_t smb_maxcnt) diff --git a/source3/smbd/smb2_read.c b/source3/smbd/smb2_read.c index 97f12d582c..d5f6896f64 100644 --- a/source3/smbd/smb2_read.c +++ b/source3/smbd/smb2_read.c @@ -172,6 +172,7 @@ static void smbd_smb2_request_read_done(struct tevent_req *subreq) struct smbd_smb2_read_state { struct smbd_smb2_request *smb2req; files_struct *fsp; + uint64_t in_file_id_volatile; uint32_t in_length; uint64_t in_offset; uint32_t in_minimum; @@ -179,6 +180,139 @@ struct smbd_smb2_read_state { uint32_t out_remaining; }; +/* struct smbd_smb2_read_state destructor. Send the SMB2_READ data. */ +static int smb2_sendfile_send_data(struct smbd_smb2_read_state *state) +{ + struct lock_struct lock; + uint32_t in_length = state->in_length; + uint64_t in_offset = state->in_offset; + files_struct *fsp = state->fsp; + ssize_t nread; + + nread = SMB_VFS_SENDFILE(fsp->conn->sconn->sock, + fsp, + NULL, + in_offset, + in_length); + DEBUG(10,("smb2_sendfile_send_data: SMB_VFS_SENDFILE returned %d on file %s\n", + (int)nread, + fsp_str_dbg(fsp) )); + + if (nread == -1) { + if (errno == ENOSYS || errno == EINTR) { + /* + * Special hack for broken systems with no working + * sendfile. Fake this up by doing read/write calls. + */ + set_use_sendfile(SNUM(fsp->conn), false); + nread = fake_sendfile(fsp, in_offset, in_length); + if (nread == -1) { + DEBUG(0,("smb2_sendfile_send_data: " + "fake_sendfile failed for " + "file %s (%s).\n", + fsp_str_dbg(fsp), + strerror(errno))); + exit_server_cleanly("smb2_sendfile_send_data: " + "fake_sendfile failed"); + } + goto out; + } + + DEBUG(0,("smb2_sendfile_send_data: sendfile failed for file " + "%s (%s). Terminating\n", + fsp_str_dbg(fsp), + strerror(errno))); + exit_server_cleanly("smb2_sendfile_send_data: sendfile failed"); + } else if (nread == 0) { + /* + * Some sendfile implementations return 0 to indicate + * that there was a short read, but nothing was + * actually written to the socket. In this case, + * fallback to the normal read path so the header gets + * the correct byte count. + */ + DEBUG(3, ("send_file_readX: sendfile sent zero bytes " + "falling back to the normal read: %s\n", + fsp_str_dbg(fsp))); + + nread = fake_sendfile(fsp, in_offset, in_length); + if (nread == -1) { + DEBUG(0,("smb2_sendfile_send_data: " + "fake_sendfile failed for file " + "%s (%s). Terminating\n", + fsp_str_dbg(fsp), + strerror(errno))); + exit_server_cleanly("smb2_sendfile_send_data: " + "fake_sendfile failed"); + } + } + + out: + + if (nread < in_length) { + sendfile_short_send(fsp, nread, 0, in_length); + } + + init_strict_lock_struct(fsp, + state->in_file_id_volatile, + in_offset, + in_length, + READ_LOCK, + &lock); + + SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lock); + return 0; +} + +static NTSTATUS schedule_smb2_sendfile_read(struct smbd_smb2_request *smb2req, + struct smbd_smb2_read_state *state) +{ + struct smbd_smb2_read_state *state_copy = NULL; + files_struct *fsp = state->fsp; + + /* + * We cannot use sendfile if... + * We were not configured to do so OR + * Signing is active OR + * This is a compound SMB2 operation OR + * fsp is a STREAM file OR + * We're using a write cache OR + * It's not a regular file OR + * Requested offset is greater than file size OR + * there's not enough data in the file. + * Phew :-). Luckily this means most + * reads on most normal files. JRA. + */ + + if (!_lp_use_sendfile(SNUM(fsp->conn)) || + smb2req->do_signing || + smb2req->in.vector_count != 4 || + (fsp->base_fsp != NULL) || + (fsp->wcp != NULL) || + (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) || + (state->in_offset >= fsp->fsp_name->st.st_ex_size) || + (fsp->fsp_name->st.st_ex_size < state->in_offset + + state->in_length)) { + return NT_STATUS_RETRY; + } + + /* We've already checked there's this amount of data + to read. */ + state->out_data.length = state->in_length; + state->out_remaining = 0; + + /* Make a copy of state attached to the smb2req. Attach + the destructor here as this will trigger the sendfile + call when the request is destroyed. */ + state_copy = TALLOC_P(smb2req, struct smbd_smb2_read_state); + if (!state_copy) { + return NT_STATUS_NO_MEMORY; + } + *state_copy = *state; + talloc_set_destructor(state_copy, smb2_sendfile_send_data); + return NT_STATUS_OK; +} + static void smbd_smb2_read_pipe_done(struct tevent_req *subreq); /******************************************************************* @@ -291,6 +425,7 @@ static struct tevent_req *smbd_smb2_read_send(TALLOC_CTX *mem_ctx, } state->fsp = fsp; + state->in_file_id_volatile = in_file_id_volatile; if (IS_IPC(smbreq->conn)) { struct tevent_req *subreq = NULL; @@ -364,6 +499,19 @@ static struct tevent_req *smbd_smb2_read_send(TALLOC_CTX *mem_ctx, return tevent_req_post(req, ev); } + /* Try sendfile in preference. */ + status = schedule_smb2_sendfile_read(smb2req, state); + if (NT_STATUS_IS_OK(status)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } else { + if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) { + SMB_VFS_STRICT_UNLOCK(conn, fsp, &lock); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + } + /* Ok, read into memory. Allocate the out buffer. */ state->out_data = data_blob_talloc(state, NULL, in_length); if (in_length > 0 && tevent_req_nomem(state->out_data.data, req)) { diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index 8dbf3276c0..924e41fa48 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -1487,6 +1487,15 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) print_req_vectors(req); } + /* I am a sick, sick man... :-). Sendfile hack ... JRA. */ + if (req->out.vector_count == 4 && + req->out.vector[3].iov_base == NULL && + req->out.vector[3].iov_len != 0) { + /* Dynamic part is NULL. Chop it off, + We're going to send it via sendfile. */ + req->out.vector_count -= 1; + } + subreq = tstream_writev_queue_send(req, req->sconn->smb2.event_ctx, req->sconn->smb2.stream, -- cgit