From f1b0ab3f4b2c830af037502a3581090762e6642e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 14 Oct 2013 16:42:55 +0200 Subject: [PATCH 1/6] s3:smb2_server: fix drain_socket error handling smbd_smb2_request_error_ex() should return NTSTATUS and the caller will terminate the connection. Signed-off-by: Stefan Metzmacher Reviewed-by: David Disseldorp (cherry picked from commit 9393e28df59954414313bfae70ffb796d3e332fe) --- source3/smbd/smb2_server.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index 525b81b..c3a78d29 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -2644,10 +2644,24 @@ NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req, if (unread_bytes) { /* Recvfile error. Drain incoming socket. */ - size_t ret = drain_socket(req->sconn->sock, unread_bytes); + size_t ret; + + errno = 0; + ret = drain_socket(req->sconn->sock, unread_bytes); if (ret != unread_bytes) { - smbd_server_connection_terminate(req->sconn, - "Failed to drain SMB2 socket\n"); + NTSTATUS error; + + if (errno == 0) { + error = NT_STATUS_IO_DEVICE_ERROR; + } else { + error = map_nt_error_from_unix_common(errno); + } + + DEBUG(2, ("Failed to drain %u bytes from SMB2 socket: " + "ret[%u] errno[%d] => %s\n", + (unsigned)unread_bytes, + (unsigned)ret, errno, nt_errstr(error))); + return error; } } -- 1.7.9.5 From 56e461fbd2387cf1b4c288beb7fd91ed8bd5c4b7 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 14 Oct 2013 10:33:57 +0200 Subject: [PATCH 2/6] s3:smb2_server: for performance reasons we use tevent_fd and readv/writev directly Going via tevent_req_create/talloc_free at multiple layer costs too much cpu cycles per request. I tested downloading a 16GB (sparse) file with smbclient -b1 -mNT1, and -mSMB2_02. Using smb2 max read = 64512, which means smb1 and smb2 will use the same read size. I build with -O3 -g and compared the results with valgrind --tool=callgrind. With -mNT1 the server uses about 2.000.000.000 cpu cycles. This patch reduces the userspace cpu cycles for -mSMB2_02 from about ~ 8.000.000.000 down to ~ 4.000.000.000. Signed-off-by: Stefan Metzmacher Reviewed-by: David Disseldorp (cherry picked from commit 4244a2686cddcdc754c284df884ae497afa4053a) --- source3/smbd/globals.h | 28 +- source3/smbd/smb2_server.c | 812 ++++++++++++++++++++------------------------ 2 files changed, 402 insertions(+), 438 deletions(-) diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index c7badbc..b008968 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -466,12 +466,22 @@ NTSTATUS smbXsrv_open_global_traverse( NTSTATUS smbXsrv_open_cleanup(uint64_t persistent_id); +struct smbd_smb2_send_queue { + struct smbd_smb2_send_queue *prev, *next; + + struct iovec *vector; + int count; + + TALLOC_CTX *mem_ctx; +}; struct smbd_smb2_request { struct smbd_smb2_request *prev, *next; struct smbd_server_connection *sconn; + struct smbd_smb2_send_queue queue_entry; + /* the session the request operates on, maybe NULL */ struct smbXsrv_session *session; uint64_t last_session_id; @@ -739,9 +749,21 @@ struct smbd_server_connection { } locks; } smb1; struct { - struct tevent_queue *recv_queue; - struct tevent_queue *send_queue; - struct tstream_context *stream; + struct smbd_smb2_request_read_state { + struct smbd_smb2_request *req; + struct { + uint8_t nbt[NBT_HDR_SIZE]; + bool done; + } hdr; + struct iovec vector; + bool doing_receivefile; + size_t min_recv_size; + size_t pktlen; + uint8_t *pktbuf; + } request_read_state; + struct smbd_smb2_send_queue *send_queue; + size_t send_queue_len; + struct tevent_fd *fde; bool negprot_2ff; struct { /* The event that makes us process our blocking lock queue */ diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index c3a78d29..bc83b3f 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -30,6 +30,13 @@ #include "../librpc/gen_ndr/krb5pac.h" #include "auth.h" +static void smbd_smb2_connection_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data); +static NTSTATUS smbd_smb2_io_handler(struct smbd_server_connection *sconn, + uint16_t fde_flags); + #define OUTVEC_ALLOC_SIZE (SMB2_HDR_BODY + 9) static const struct smbd_smb2_dispatch_table { @@ -196,20 +203,9 @@ bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size) static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *sconn) { - NTSTATUS status; - int ret; - TALLOC_FREE(sconn->smb1.fde); - sconn->smb2.recv_queue = tevent_queue_create(sconn, "smb2 recv queue"); - if (sconn->smb2.recv_queue == NULL) { - return NT_STATUS_NO_MEMORY; - } - - sconn->smb2.send_queue = tevent_queue_create(sconn, "smb2 send queue"); - if (sconn->smb2.send_queue == NULL) { - return NT_STATUS_NO_MEMORY; - } + sconn->smb2.send_queue = NULL; sconn->smb2.seqnum_low = 0; sconn->smb2.seqnum_range = 1; @@ -221,11 +217,14 @@ static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *sconn) return NT_STATUS_NO_MEMORY; } - ret = tstream_bsd_existing_socket(sconn, sconn->sock, - &sconn->smb2.stream); - if (ret == -1) { - status = map_nt_error_from_unix(errno); - return status; + sconn->smb2.fde = tevent_add_fd(sconn->ev_ctx, + sconn, + sconn->sock, + TEVENT_FD_READ, + smbd_smb2_connection_handler, + sconn); + if (sconn->smb2.fde == NULL) { + return NT_STATUS_NO_MEMORY; } /* Ensure child is set to non-blocking mode */ @@ -1151,10 +1150,9 @@ static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *re return newreq; } -static void smbd_smb2_request_writev_done(struct tevent_req *subreq); - static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request *req) { + struct smbd_server_connection *sconn = req->sconn; struct smbXsrv_connection *conn = req->sconn->conn; int first_idx = 1; struct iovec *firsttf = NULL; @@ -1218,50 +1216,27 @@ static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request } } - nreq->subreq = tstream_writev_queue_send(nreq, - nreq->sconn->ev_ctx, - nreq->sconn->smb2.stream, - nreq->sconn->smb2.send_queue, - nreq->out.vector, - nreq->out.vector_count); + nreq->queue_entry.mem_ctx = nreq; + nreq->queue_entry.vector = nreq->out.vector; + nreq->queue_entry.count = nreq->out.vector_count; + DLIST_ADD_END(nreq->sconn->smb2.send_queue, &nreq->queue_entry, NULL); + nreq->sconn->smb2.send_queue_len++; - if (nreq->subreq == NULL) { - return NT_STATUS_NO_MEMORY; + status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE); + if (!NT_STATUS_IS_OK(status)) { + return status; } - tevent_req_set_callback(nreq->subreq, - smbd_smb2_request_writev_done, - nreq); - return NT_STATUS_OK; } struct smbd_smb2_request_pending_state { struct smbd_server_connection *sconn; + struct smbd_smb2_send_queue queue_entry; uint8_t buf[NBT_HDR_SIZE + SMB2_TF_HDR_SIZE + SMB2_HDR_BODY + 0x08 + 1]; struct iovec vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ]; }; -static void smbd_smb2_request_pending_writev_done(struct tevent_req *subreq) -{ - struct smbd_smb2_request_pending_state *state = - tevent_req_callback_data(subreq, - struct smbd_smb2_request_pending_state); - struct smbd_server_connection *sconn = state->sconn; - int ret; - int sys_errno; - - ret = tstream_writev_queue_recv(subreq, &sys_errno); - TALLOC_FREE(subreq); - if (ret == -1) { - NTSTATUS status = map_nt_error_from_unix(sys_errno); - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; - } - - TALLOC_FREE(state); -} - static void smbd_smb2_request_pending_timer(struct tevent_context *ev, struct tevent_timer *te, struct timeval current_time, @@ -1393,6 +1368,7 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev, struct smbd_smb2_request *req = talloc_get_type_abort(private_data, struct smbd_smb2_request); + struct smbd_server_connection *sconn = req->sconn; struct smbd_smb2_request_pending_state *state = NULL; uint8_t *outhdr = NULL; const uint8_t *inhdr = NULL; @@ -1407,7 +1383,7 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev, uint64_t nonce_high = 0; uint64_t nonce_low = 0; uint64_t async_id = 0; - struct tevent_req *subreq = NULL; + NTSTATUS status; TALLOC_FREE(req->async_te); @@ -1533,7 +1509,6 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev, } if (req->do_encryption) { - NTSTATUS status; struct smbXsrv_session *x = req->session; struct smbXsrv_connection *conn = x->connection; DATA_BLOB encryption_key = x->global->encryption_key; @@ -1548,7 +1523,6 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev, return; } } else if (req->do_signing) { - NTSTATUS status; struct smbXsrv_session *x = req->session; struct smbXsrv_connection *conn = x->connection; DATA_BLOB signing_key = x->global->channels[0].signing_key; @@ -1564,20 +1538,18 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev, } } - subreq = tstream_writev_queue_send(state, - state->sconn->ev_ctx, - state->sconn->smb2.stream, - state->sconn->smb2.send_queue, - state->vector, - ARRAY_SIZE(state->vector)); - if (subreq == NULL) { - smbd_server_connection_terminate(state->sconn, - nt_errstr(NT_STATUS_NO_MEMORY)); + state->queue_entry.mem_ctx = state; + state->queue_entry.vector = state->vector; + state->queue_entry.count = ARRAY_SIZE(state->vector); + DLIST_ADD_END(sconn->smb2.send_queue, &state->queue_entry, NULL); + sconn->smb2.send_queue_len++; + + status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE); + if (!NT_STATUS_IS_OK(status)) { + smbd_server_connection_terminate(sconn, + nt_errstr(status)); return; } - tevent_req_set_callback(subreq, - smbd_smb2_request_pending_writev_done, - state); } static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req) @@ -2277,12 +2249,13 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req) static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) { + struct smbd_server_connection *sconn = req->sconn; struct smbXsrv_connection *conn = req->sconn->conn; - struct tevent_req *subreq; int first_idx = 1; struct iovec *firsttf = SMBD_SMB2_IDX_TF_IOV(req,out,first_idx); struct iovec *outhdr = SMBD_SMB2_OUT_HDR_IOV(req); struct iovec *outdyn = SMBD_SMB2_OUT_DYN_IOV(req); + NTSTATUS status; req->subreq = NULL; TALLOC_FREE(req->async_te); @@ -2346,7 +2319,6 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) { int last_idx = req->current_idx - SMBD_SMB2_NUM_IOV_PER_REQ; struct iovec *lasthdr = SMBD_SMB2_IDX_HDR_IOV(req,out,last_idx); - NTSTATUS status; /* * As we are sure the header of the last request in the @@ -2416,8 +2388,6 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) * now check if we need to sign the current response */ if (firsttf->iov_len == SMB2_TF_HDR_SIZE) { - NTSTATUS status; - status = smb2_signing_encrypt_pdu(req->first_key, conn->protocol, firsttf, @@ -2426,7 +2396,6 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) return status; } } else if (req->do_signing) { - NTSTATUS status; struct smbXsrv_session *x = req->session; DATA_BLOB signing_key = x->global->channels[0].signing_key; @@ -2448,22 +2417,23 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) req->out.vector_count -= 1; } - subreq = tstream_writev_queue_send(req, - req->sconn->ev_ctx, - req->sconn->smb2.stream, - req->sconn->smb2.send_queue, - req->out.vector, - req->out.vector_count); - if (subreq == NULL) { - return NT_STATUS_NO_MEMORY; - } - tevent_req_set_callback(subreq, smbd_smb2_request_writev_done, req); /* * We're done with this request - * move it off the "being processed" queue. */ DLIST_REMOVE(req->sconn->smb2.requests, req); + req->queue_entry.mem_ctx = req; + req->queue_entry.vector = req->out.vector; + req->queue_entry.count = req->out.vector_count; + DLIST_ADD_END(req->sconn->smb2.send_queue, &req->queue_entry, NULL); + req->sconn->smb2.send_queue_len++; + + status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return NT_STATUS_OK; } @@ -2499,33 +2469,6 @@ void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx, } } -static void smbd_smb2_request_writev_done(struct tevent_req *subreq) -{ - struct smbd_smb2_request *req = tevent_req_callback_data(subreq, - struct smbd_smb2_request); - struct smbd_server_connection *sconn = req->sconn; - int ret; - int sys_errno; - NTSTATUS status; - - ret = tstream_writev_queue_recv(subreq, &sys_errno); - TALLOC_FREE(subreq); - TALLOC_FREE(req); - if (ret == -1) { - status = map_nt_error_from_unix(sys_errno); - DEBUG(2,("smbd_smb2_request_writev_done: client write error %s\n", - nt_errstr(status))); - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; - } - - status = smbd_smb2_request_next_incoming(sconn); - if (!NT_STATUS_IS_OK(status)) { - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; - } -} - NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req, NTSTATUS status, DATA_BLOB body, DATA_BLOB *dyn, @@ -2699,12 +2642,11 @@ NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req, struct smbd_smb2_send_oplock_break_state { struct smbd_server_connection *sconn; + struct smbd_smb2_send_queue queue_entry; uint8_t buf[NBT_HDR_SIZE + SMB2_TF_HDR_SIZE + SMB2_HDR_BODY + 0x18]; struct iovec vector[1+SMBD_SMB2_NUM_IOV_PER_REQ]; }; -static void smbd_smb2_oplock_break_writev_done(struct tevent_req *subreq); - NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn, struct smbXsrv_session *session, struct smbXsrv_tcon *tcon, @@ -2713,7 +2655,6 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn, { struct smbd_smb2_send_oplock_break_state *state; struct smbXsrv_connection *conn = sconn->conn; - struct tevent_req *subreq; uint8_t *tf; size_t tf_len; uint8_t *hdr; @@ -2724,6 +2665,7 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn, bool do_encryption = session->global->encryption_required; uint64_t nonce_high = 0; uint64_t nonce_low = 0; + NTSTATUS status; if (tcon->global->encryption_required) { do_encryption = true; @@ -2804,7 +2746,6 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn, smb2_setup_nbt_length(state->vector, 1 + SMBD_SMB2_NUM_IOV_PER_REQ); if (do_encryption) { - NTSTATUS status; DATA_BLOB encryption_key = session->global->encryption_key; status = smb2_signing_encrypt_pdu(encryption_key, @@ -2816,63 +2757,20 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn, } } - subreq = tstream_writev_queue_send(state, - sconn->ev_ctx, - sconn->smb2.stream, - sconn->smb2.send_queue, - state->vector, - ARRAY_SIZE(state->vector)); - if (subreq == NULL) { - return NT_STATUS_NO_MEMORY; - } - tevent_req_set_callback(subreq, - smbd_smb2_oplock_break_writev_done, - state); - - return NT_STATUS_OK; -} - -static void smbd_smb2_oplock_break_writev_done(struct tevent_req *subreq) -{ - struct smbd_smb2_send_oplock_break_state *state = - tevent_req_callback_data(subreq, - struct smbd_smb2_send_oplock_break_state); - struct smbd_server_connection *sconn = state->sconn; - int ret; - int sys_errno; + state->queue_entry.mem_ctx = state; + state->queue_entry.vector = state->vector; + state->queue_entry.count = ARRAY_SIZE(state->vector); + DLIST_ADD_END(state->sconn->smb2.send_queue, &state->queue_entry, NULL); + state->sconn->smb2.send_queue_len++; - ret = tstream_writev_queue_recv(subreq, &sys_errno); - TALLOC_FREE(subreq); - if (ret == -1) { - NTSTATUS status = map_nt_error_from_unix(sys_errno); - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; + status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE); + if (!NT_STATUS_IS_OK(status)) { + return status; } - TALLOC_FREE(state); + return NT_STATUS_OK; } -struct smbd_smb2_request_read_state { - struct tevent_context *ev; - struct smbd_server_connection *sconn; - struct smbd_smb2_request *smb2_req; - struct { - uint8_t nbt[NBT_HDR_SIZE]; - bool done; - } hdr; - bool doing_receivefile; - size_t min_recv_size; - size_t pktlen; - uint8_t *pktbuf; -}; - -static int smbd_smb2_request_next_vector(struct tstream_context *stream, - void *private_data, - TALLOC_CTX *mem_ctx, - struct iovec **_vector, - size_t *_count); -static void smbd_smb2_request_read_done(struct tevent_req *subreq); - static size_t get_min_receive_file_size(struct smbd_smb2_request *smb2_req) { if (smb2_req->do_signing) { @@ -2884,43 +2782,6 @@ static size_t get_min_receive_file_size(struct smbd_smb2_request *smb2_req) return (size_t)lp_min_receive_file_size(); } -static struct tevent_req *smbd_smb2_request_read_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct smbd_server_connection *sconn) -{ - struct tevent_req *req; - struct smbd_smb2_request_read_state *state; - struct tevent_req *subreq; - - req = tevent_req_create(mem_ctx, &state, - struct smbd_smb2_request_read_state); - if (req == NULL) { - return NULL; - } - state->ev = ev; - state->sconn = sconn; - - state->smb2_req = smbd_smb2_request_allocate(state); - if (tevent_req_nomem(state->smb2_req, req)) { - return tevent_req_post(req, ev); - } - state->smb2_req->sconn = sconn; - state->min_recv_size = get_min_receive_file_size(state->smb2_req); - - subreq = tstream_readv_pdu_queue_send(state->smb2_req, - state->ev, - state->sconn->smb2.stream, - state->sconn->smb2.recv_queue, - smbd_smb2_request_next_vector, - state); - if (tevent_req_nomem(subreq, req)) { - return tevent_req_post(req, ev); - } - tevent_req_set_callback(subreq, smbd_smb2_request_read_done, req); - - return req; -} - static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state) { uint32_t flags; @@ -2962,226 +2823,22 @@ static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state) return true; } -static int smbd_smb2_request_next_vector(struct tstream_context *stream, - void *private_data, - TALLOC_CTX *mem_ctx, - struct iovec **_vector, - size_t *_count) -{ - struct smbd_smb2_request_read_state *state = - talloc_get_type_abort(private_data, - struct smbd_smb2_request_read_state); - struct iovec *vector = NULL; - size_t min_recvfile_size = UINT32_MAX; - - if (state->pktlen > 0) { - if (state->doing_receivefile && !is_smb2_recvfile_write(state)) { - /* - * Not a possible receivefile write. - * Read the rest of the data. - */ - state->doing_receivefile = false; - vector = talloc_array(mem_ctx, struct iovec, 1); - if (vector == NULL) { - return -1; - } - vector[0].iov_base = (void *)(state->pktbuf + - SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN); - vector[0].iov_len = (state->pktlen - - SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN); - *_vector = vector; - *_count = 1; - } else { - /* - * Either this is a receivefile write so we've - * done a short read, or if not we have all the data. - * Either way, we're done and - * smbd_smb2_request_read_done() will handle - * and short read case by looking at the - * state->doing_receivefile value. - */ - *_vector = NULL; - *_count = 0; - } - return 0; - } - - if (!state->hdr.done) { - /* - * first we need to get the NBT header - */ - vector = talloc_array(mem_ctx, struct iovec, 1); - if (vector == NULL) { - return -1; - } - - vector[0].iov_base = (void *)state->hdr.nbt; - vector[0].iov_len = NBT_HDR_SIZE; - - *_vector = vector; - *_count = 1; - - state->hdr.done = true; - return 0; - } - - /* - * Now we analyze the NBT header - */ - state->pktlen = smb2_len(state->hdr.nbt); - - if (state->pktlen == 0) { - /* if there're no remaining bytes, we're done */ - *_vector = NULL; - *_count = 0; - return 0; - } - - state->pktbuf = talloc_array(state->smb2_req, uint8_t, state->pktlen); - if (state->pktbuf == NULL) { - return -1; - } - - vector = talloc_array(mem_ctx, struct iovec, 1); - if (vector == NULL) { - return -1; - } - - vector[0].iov_base = (void *)state->pktbuf; - - if (state->min_recv_size != 0) { - min_recvfile_size = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; - min_recvfile_size += state->min_recv_size; - } - - if (state->pktlen > min_recvfile_size) { - /* - * Might be a receivefile write. Read the SMB2 HEADER + - * SMB2_WRITE header first. Set 'doing_receivefile' - * as we're *attempting* receivefile write. If this - * turns out not to be a SMB2_WRITE request or otherwise - * not suitable then we'll just read the rest of the data - * the next time this function is called. - */ - vector[0].iov_len = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; - state->doing_receivefile = true; - } else { - vector[0].iov_len = state->pktlen; - } - - *_vector = vector; - *_count = 1; - return 0; -} - -static void smbd_smb2_request_read_done(struct tevent_req *subreq) -{ - struct tevent_req *req = - tevent_req_callback_data(subreq, - struct tevent_req); - struct smbd_smb2_request_read_state *state = - tevent_req_data(req, - struct smbd_smb2_request_read_state); - int ret; - int sys_errno; - NTSTATUS status; - NTTIME now; - - ret = tstream_readv_pdu_queue_recv(subreq, &sys_errno); - TALLOC_FREE(subreq); - if (ret == -1) { - status = map_nt_error_from_unix(sys_errno); - tevent_req_nterror(req, status); - return; - } - - if (state->hdr.nbt[0] != 0x00) { - DEBUG(1,("smbd_smb2_request_read_done: ignore NBT[0x%02X] msg\n", - state->hdr.nbt[0])); - - ZERO_STRUCT(state->hdr); - TALLOC_FREE(state->pktbuf); - state->pktlen = 0; - - subreq = tstream_readv_pdu_queue_send(state->smb2_req, - state->ev, - state->sconn->smb2.stream, - state->sconn->smb2.recv_queue, - smbd_smb2_request_next_vector, - state); - if (tevent_req_nomem(subreq, req)) { - return; - } - tevent_req_set_callback(subreq, smbd_smb2_request_read_done, req); - return; - } - - state->smb2_req->request_time = timeval_current(); - now = timeval_to_nttime(&state->smb2_req->request_time); - - status = smbd_smb2_inbuf_parse_compound(state->smb2_req->sconn->conn, - now, - state->pktbuf, - state->pktlen, - state->smb2_req, - &state->smb2_req->in.vector, - &state->smb2_req->in.vector_count); - if (tevent_req_nterror(req, status)) { - return; - } - - if (state->doing_receivefile) { - state->smb2_req->smb1req = talloc_zero(state->smb2_req, - struct smb_request); - if (tevent_req_nomem(state->smb2_req->smb1req, req)) { - return; - } - state->smb2_req->smb1req->unread_bytes = - state->pktlen - SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; - } - - state->smb2_req->current_idx = 1; - - tevent_req_done(req); -} - -static NTSTATUS smbd_smb2_request_read_recv(struct tevent_req *req, - TALLOC_CTX *mem_ctx, - struct smbd_smb2_request **_smb2_req) -{ - struct smbd_smb2_request_read_state *state = - tevent_req_data(req, - struct smbd_smb2_request_read_state); - NTSTATUS status; - - if (tevent_req_is_nterror(req, &status)) { - tevent_req_received(req); - return status; - } - - *_smb2_req = talloc_move(mem_ctx, &state->smb2_req); - tevent_req_received(req); - return NT_STATUS_OK; -} - -static void smbd_smb2_request_incoming(struct tevent_req *subreq); - static NTSTATUS smbd_smb2_request_next_incoming(struct smbd_server_connection *sconn) { + struct smbd_smb2_request_read_state *state = &sconn->smb2.request_read_state; size_t max_send_queue_len; size_t cur_send_queue_len; - struct tevent_req *subreq; - if (tevent_queue_length(sconn->smb2.recv_queue) > 0) { + if (state->req != NULL) { /* - * if there is already a smbd_smb2_request_read + * if there is already a tstream_readv_pdu * pending, we are done. */ return NT_STATUS_OK; } max_send_queue_len = MAX(1, sconn->smb2.max_credits/16); - cur_send_queue_len = tevent_queue_length(sconn->smb2.send_queue); + cur_send_queue_len = sconn->smb2.send_queue_len; if (cur_send_queue_len > max_send_queue_len) { /* @@ -3193,11 +2850,15 @@ static NTSTATUS smbd_smb2_request_next_incoming(struct smbd_server_connection *s } /* ask for the next request */ - subreq = smbd_smb2_request_read_send(sconn, sconn->ev_ctx, sconn); - if (subreq == NULL) { + ZERO_STRUCTP(state); + state->req = smbd_smb2_request_allocate(sconn); + if (state->req == NULL) { return NT_STATUS_NO_MEMORY; } - tevent_req_set_callback(subreq, smbd_smb2_request_incoming, sconn); + state->req->sconn = sconn; + state->min_recv_size = get_min_receive_file_size(state->req); + + TEVENT_FD_READABLE(sconn->smb2.fde); return NT_STATUS_OK; } @@ -3250,47 +2911,304 @@ void smbd_smb2_first_negprot(struct smbd_server_connection *sconn, sconn->num_requests++; } -static void smbd_smb2_request_incoming(struct tevent_req *subreq) +static int socket_error_from_errno(int ret, + int sys_errno, + bool *retry) { - struct smbd_server_connection *sconn = tevent_req_callback_data(subreq, - struct smbd_server_connection); - NTSTATUS status; + *retry = false; + + if (ret >= 0) { + return 0; + } + + if (ret != -1) { + return EIO; + } + + if (sys_errno == 0) { + return EIO; + } + + if (sys_errno == EINTR) { + *retry = true; + return sys_errno; + } + + if (sys_errno == EINPROGRESS) { + *retry = true; + return sys_errno; + } + + if (sys_errno == EAGAIN) { + *retry = true; + return sys_errno; + } + + /* ENOMEM is retryable on Solaris/illumos, and possibly other systems. */ + if (sys_errno == ENOMEM) { + *retry = true; + return sys_errno; + } + +#ifdef EWOULDBLOCK + if (sys_errno == EWOULDBLOCK) { + *retry = true; + return sys_errno; + } +#endif + + return sys_errno; +} + +static NTSTATUS smbd_smb2_flush_send_queue(struct smbd_server_connection *sconn) +{ + int ret; + int err; + bool retry; + + if (sconn->smb2.send_queue == NULL) { + TEVENT_FD_NOT_WRITEABLE(sconn->smb2.fde); + return NT_STATUS_OK; + } + + while (sconn->smb2.send_queue != NULL) { + struct smbd_smb2_send_queue *e = sconn->smb2.send_queue; + + ret = writev(sconn->sock, e->vector, e->count); + if (ret == 0) { + /* propagate end of file */ + return NT_STATUS_INTERNAL_ERROR; + } + err = socket_error_from_errno(ret, errno, &retry); + if (retry) { + /* retry later */ + TEVENT_FD_WRITEABLE(sconn->smb2.fde); + return NT_STATUS_OK; + } + if (err != 0) { + return map_nt_error_from_unix_common(err); + } + while (ret > 0) { + if (ret < e->vector[0].iov_len) { + uint8_t *base; + base = (uint8_t *)e->vector[0].iov_base; + base += ret; + e->vector[0].iov_base = (void *)base; + e->vector[0].iov_len -= ret; + break; + } + ret -= e->vector[0].iov_len; + e->vector += 1; + e->count -= 1; + } + + /* + * there're maybe some empty vectors at the end + * which we need to skip, otherwise we would get + * ret == 0 from the readv() call and return EPIPE + */ + while (e->count > 0) { + if (e->vector[0].iov_len > 0) { + break; + } + e->vector += 1; + e->count -= 1; + } + + if (e->count > 0) { + /* we have more to write */ + TEVENT_FD_WRITEABLE(sconn->smb2.fde); + return NT_STATUS_OK; + } + + sconn->smb2.send_queue_len--; + DLIST_REMOVE(sconn->smb2.send_queue, e); + talloc_free(e->mem_ctx); + } + + return NT_STATUS_OK; +} + +static NTSTATUS smbd_smb2_io_handler(struct smbd_server_connection *sconn, + uint16_t fde_flags) +{ + struct smbd_smb2_request_read_state *state = &sconn->smb2.request_read_state; struct smbd_smb2_request *req = NULL; + size_t min_recvfile_size = UINT32_MAX; + int ret; + int err; + bool retry; + NTSTATUS status; + NTTIME now; + + if (fde_flags & TEVENT_FD_WRITE) { + status = smbd_smb2_flush_send_queue(sconn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (!(fde_flags & TEVENT_FD_READ)) { + return NT_STATUS_OK; + } + + if (state->req == NULL) { + TEVENT_FD_NOT_READABLE(sconn->smb2.fde); + return NT_STATUS_OK; + } + +again: + if (!state->hdr.done) { + state->hdr.done = true; + + state->vector.iov_base = (void *)state->hdr.nbt; + state->vector.iov_len = NBT_HDR_SIZE; + } + + ret = readv(sconn->sock, &state->vector, 1); + if (ret == 0) { + /* propagate end of file */ + return NT_STATUS_END_OF_FILE; + } + err = socket_error_from_errno(ret, errno, &retry); + if (retry) { + /* retry later */ + TEVENT_FD_READABLE(sconn->smb2.fde); + return NT_STATUS_OK; + } + if (err != 0) { + return map_nt_error_from_unix_common(err); + } + + if (ret < state->vector.iov_len) { + uint8_t *base; + base = (uint8_t *)state->vector.iov_base; + base += ret; + state->vector.iov_base = (void *)base; + state->vector.iov_len -= ret; + /* we have more to read */ + TEVENT_FD_READABLE(sconn->smb2.fde); + return NT_STATUS_OK; + } + + if (state->pktlen > 0) { + if (state->doing_receivefile && !is_smb2_recvfile_write(state)) { + /* + * Not a possible receivefile write. + * Read the rest of the data. + */ + state->doing_receivefile = false; + state->vector.iov_base = (void *)(state->pktbuf + + SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN); + state->vector.iov_len = (state->pktlen - + SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN); + goto again; + } + + /* + * Either this is a receivefile write so we've + * done a short read, or if not we have all the data. + */ + goto got_full; + } + + /* + * Now we analyze the NBT header + */ + state->pktlen = smb2_len(state->hdr.nbt); + if (state->pktlen == 0) { + goto got_full; + } + + state->pktbuf = talloc_array(state->req, uint8_t, state->pktlen); + if (state->pktbuf == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->vector.iov_base = (void *)state->pktbuf; + + if (state->min_recv_size != 0) { + min_recvfile_size = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; + min_recvfile_size += state->min_recv_size; + } + + if (state->pktlen > min_recvfile_size) { + /* + * Might be a receivefile write. Read the SMB2 HEADER + + * SMB2_WRITE header first. Set 'doing_receivefile' + * as we're *attempting* receivefile write. If this + * turns out not to be a SMB2_WRITE request or otherwise + * not suitable then we'll just read the rest of the data + * the next time this function is called. + */ + state->vector.iov_len = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; + state->doing_receivefile = true; + } else { + state->vector.iov_len = state->pktlen; + } + + goto again; + +got_full: + + if (state->hdr.nbt[0] != 0x00) { + DEBUG(1,("ignore NBT[0x%02X] msg\n", + state->hdr.nbt[0])); + + req = state->req; + ZERO_STRUCTP(state); + state->req = req; + state->min_recv_size = get_min_receive_file_size(state->req); + req = NULL; + goto again; + } + + req = state->req; + state->req = NULL; + + req->request_time = timeval_current(); + now = timeval_to_nttime(&req->request_time); - status = smbd_smb2_request_read_recv(subreq, sconn, &req); - TALLOC_FREE(subreq); + status = smbd_smb2_inbuf_parse_compound(req->sconn->conn, + now, + state->pktbuf, + state->pktlen, + req, + &req->in.vector, + &req->in.vector_count); if (!NT_STATUS_IS_OK(status)) { - DEBUG(2,("smbd_smb2_request_incoming: client read error %s\n", - nt_errstr(status))); - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; + return status; + } + + if (state->doing_receivefile) { + req->smb1req = talloc_zero(req, struct smb_request); + if (req->smb1req == NULL) { + return NT_STATUS_NO_MEMORY; + } + req->smb1req->unread_bytes = + state->pktlen - SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; } - DEBUG(10,("smbd_smb2_request_incoming: idx[%d] of %d vectors\n", + ZERO_STRUCTP(state); + + req->current_idx = 1; + + DEBUG(10,("smbd_smb2_request idx[%d] of %d vectors\n", req->current_idx, req->in.vector_count)); status = smbd_smb2_request_validate(req); if (!NT_STATUS_IS_OK(status)) { - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; + return status; } status = smbd_smb2_request_setup_out(req); if (!NT_STATUS_IS_OK(status)) { - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; + return status; } status = smbd_smb2_request_dispatch(req); if (!NT_STATUS_IS_OK(status)) { - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; - } - - status = smbd_smb2_request_next_incoming(sconn); - if (!NT_STATUS_IS_OK(status)) { - smbd_server_connection_terminate(sconn, nt_errstr(status)); - return; + return status; } sconn->num_requests++; @@ -3307,4 +3225,28 @@ static void smbd_smb2_request_incoming(struct tevent_req *subreq) change_to_root_user(); check_log_size(); } + + status = smbd_smb2_request_next_incoming(sconn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static void smbd_smb2_connection_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct smbd_server_connection *sconn = + talloc_get_type_abort(private_data, + struct smbd_server_connection); + NTSTATUS status; + + status = smbd_smb2_io_handler(sconn, flags); + if (!NT_STATUS_IS_OK(status)) { + smbd_server_connection_terminate(sconn, nt_errstr(status)); + return; + } } -- 1.7.9.5 From f0fbe1ef49695eeed1eec706767986733cbcb3e7 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Sat, 12 Oct 2013 02:40:12 +0200 Subject: [PATCH 3/6] s3:smb2_server: use tevent_req_notify_callback() in smbd_smb2_request_pending_queue() If the request is already done we can avoid one iteration of tevent_loop_once(), which means we avoids one talloc_stackframe_pool/talloc_free pair. Signed-off-by: Stefan Metzmacher Reviewed-by: David Disseldorp (cherry picked from commit acfd4b068a5b99ac1d3fe716afff34cb7d2a0147) --- source3/smbd/smb2_server.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index bc83b3f..9b5503a 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -1252,6 +1252,13 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req, uint32_t flags; if (!tevent_req_is_in_progress(subreq)) { + /* + * This is a performance optimization, + * it avoids one tevent_loop iteration, + * which means we avoid one + * talloc_stackframe_pool/talloc_free pair. + */ + tevent_req_notify_callback(subreq); return NT_STATUS_OK; } -- 1.7.9.5 From fadc6c079ca6057f07c5398b58209e3eccf4ad47 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 16 Oct 2013 09:15:12 +0200 Subject: [PATCH 4/6] s3:smb2_server: allocate smbd_smb2_request on talloc_tos() This matches the behavior for smb1 requests and avoids an additional malloc() per request. Signed-off-by: Stefan Metzmacher Reviewed-by: David Disseldorp (cherry picked from commit 9d33a3f3e814e2924a423496ccc133c6c73fcd12) --- source3/smbd/smb2_server.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index 9b5503a..6aca5d1 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -269,7 +269,7 @@ static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx) /* Enable this to find subtle valgrind errors. */ mem_pool = talloc_init("smbd_smb2_request_allocate"); #else - mem_pool = talloc_pool(mem_ctx, 8192); + mem_pool = talloc_tos(); #endif if (mem_pool == NULL) { return NULL; @@ -281,7 +281,9 @@ static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx) return NULL; } talloc_reparent(mem_pool, mem_ctx, req); +#if 0 TALLOC_FREE(mem_pool); +#endif req->last_session_id = UINT64_MAX; req->last_tid = UINT32_MAX; -- 1.7.9.5 From f11e9afab15abe2df4176de5f5c9a5c5c2214f77 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 14 Oct 2013 14:18:26 +0200 Subject: [PATCH 5/6] s3:smb2_server: generate a header blob for the sendfile path We need to pass the NBT header, SMB2 header and SMB2 Read header as header blob to SMB_VFS_SENDFILE(). This allows the usage of MSG_SEND or other tricks to avoid multiple TCP packets on the wire. Signed-off-by: Stefan Metzmacher Reviewed-by: David Disseldorp (cherry picked from commit 36efaac2597d2d36826c02f23be15e7323b09784) --- source3/smbd/globals.h | 1 + source3/smbd/smb2_read.c | 10 ++++++---- source3/smbd/smb2_server.c | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index b008968..f1378f2 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -469,6 +469,7 @@ NTSTATUS smbXsrv_open_cleanup(uint64_t persistent_id); struct smbd_smb2_send_queue { struct smbd_smb2_send_queue *prev, *next; + DATA_BLOB *sendfile_header; struct iovec *vector; int count; diff --git a/source3/smbd/smb2_read.c b/source3/smbd/smb2_read.c index 41adb03..d6d3d90 100644 --- a/source3/smbd/smb2_read.c +++ b/source3/smbd/smb2_read.c @@ -166,6 +166,7 @@ struct smbd_smb2_read_state { uint32_t in_length; uint64_t in_offset; uint32_t in_minimum; + DATA_BLOB out_headers; DATA_BLOB out_data; uint32_t out_remaining; }; @@ -180,10 +181,10 @@ static int smb2_sendfile_send_data(struct smbd_smb2_read_state *state) ssize_t nread; nread = SMB_VFS_SENDFILE(fsp->conn->sconn->sock, - fsp, - NULL, - in_offset, - in_length); + fsp, + state->smb2req->queue_entry.sendfile_header, + 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) )); @@ -301,6 +302,7 @@ static NTSTATUS schedule_smb2_sendfile_read(struct smbd_smb2_request *smb2req, } *state_copy = *state; talloc_set_destructor(state_copy, smb2_sendfile_send_data); + state->smb2req->queue_entry.sendfile_header = &state_copy->out_headers; return NT_STATUS_OK; } diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index 6aca5d1..a46bb2f 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -2680,7 +2680,7 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn, do_encryption = true; } - state = talloc(sconn, struct smbd_smb2_send_oplock_break_state); + state = talloc_zero(sconn, struct smbd_smb2_send_oplock_break_state); if (state == NULL) { return NT_STATUS_NO_MEMORY; } @@ -2983,6 +2983,38 @@ static NTSTATUS smbd_smb2_flush_send_queue(struct smbd_server_connection *sconn) while (sconn->smb2.send_queue != NULL) { struct smbd_smb2_send_queue *e = sconn->smb2.send_queue; + if (e->sendfile_header != NULL) { + size_t size = 0; + size_t i = 0; + uint8_t *buf; + + for (i=0; i < e->count; i++) { + size += e->vector[i].iov_len; + } + + buf = talloc_array(e->mem_ctx, uint8_t, size); + if (buf == NULL) { + return NT_STATUS_NO_MEMORY; + } + + size = 0; + for (i=0; i < e->count; i++) { + memcpy(buf+size, + e->vector[i].iov_base, + e->vector[i].iov_len); + size += e->vector[i].iov_len; + } + + e->sendfile_header->data = buf; + e->sendfile_header->length = size; + e->count = 0; + + sconn->smb2.send_queue_len--; + DLIST_REMOVE(sconn->smb2.send_queue, e); + talloc_free(e->mem_ctx); + continue; + } + ret = writev(sconn->sock, e->vector, e->count); if (ret == 0) { /* propagate end of file */ -- 1.7.9.5 From 25b59622fd60fbae6606b0cae7802cd55babcf93 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 19 Nov 2013 05:21:05 +0100 Subject: [PATCH 6/6] s3:smb2_server: avoid calling set_current_user_info() for each request Signed-off-by: Stefan Metzmacher Reviewed-by: David Disseldorp Autobuild-User(master): Stefan Metzmacher Autobuild-Date(master): Wed Nov 27 16:31:44 CET 2013 on sn-devel-104 (cherry picked from commit 3cc0651d9feda00b6a04f84b76744b2acc3a0446) --- source3/smbd/globals.h | 2 +- source3/smbd/process.c | 5 ++--- source3/smbd/smb2_server.c | 9 ++++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index f1378f2..b5b7d8d 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -366,6 +366,7 @@ struct smbXsrv_connection { * this session_table is used for SMB1 and SMB2, */ struct smbXsrv_session_table *session_table; + uint64_t last_session_id; /* * this tcon_table is only used for SMB1. */ @@ -730,7 +731,6 @@ struct smbd_server_connection { * Set by us for CORE protocol. */ int max_send; - uint64_t last_session_tag; } sessions; struct smb_signing_state *signing_state; diff --git a/source3/smbd/process.c b/source3/smbd/process.c index 8add79d..3bf10de 100644 --- a/source3/smbd/process.c +++ b/source3/smbd/process.c @@ -1434,10 +1434,10 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req) } } - if (session_tag != sconn->smb1.sessions.last_session_tag) { + if (session_tag != sconn->conn->last_session_id) { struct user_struct *vuser = NULL; - sconn->smb1.sessions.last_session_tag = session_tag; + sconn->conn->last_session_id = session_tag; if (session) { vuser = session->compat; } @@ -3593,7 +3593,6 @@ void smbd_process(struct tevent_context *ev_ctx, sconn->smb1.sessions.done_sesssetup = false; sconn->smb1.sessions.max_send = BUFFER_SIZE; - sconn->smb1.sessions.last_session_tag = UID_FIELD_INVALID; if (!init_dptrs(sconn)) { exit_server("init_dptrs() failed"); diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index a46bb2f..b4c291e5 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -1749,9 +1749,12 @@ static NTSTATUS smbd_smb2_request_check_session(struct smbd_smb2_request *req) return NT_STATUS_INVALID_HANDLE; } - set_current_user_info(session_info->unix_info->sanitized_username, - session_info->unix_info->unix_name, - session_info->info->domain_name); + if (in_session_id != req->sconn->conn->last_session_id) { + req->sconn->conn->last_session_id = in_session_id; + set_current_user_info(session_info->unix_info->sanitized_username, + session_info->unix_info->unix_name, + session_info->info->domain_name); + } return NT_STATUS_OK; } -- 1.7.9.5