From ab63bad94bfc378aca08d3314692de90e1aa1bf3 Mon Sep 17 00:00:00 2001 From: Kai Blin Date: Sat, 24 Jan 2009 10:00:13 +0100 Subject: async_sock: Move to top level --- lib/async_req/async_sock.c | 703 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 703 insertions(+) create mode 100644 lib/async_req/async_sock.c (limited to 'lib/async_req/async_sock.c') diff --git a/lib/async_req/async_sock.c b/lib/async_req/async_sock.c new file mode 100644 index 0000000000..7febc54fd4 --- /dev/null +++ b/lib/async_req/async_sock.c @@ -0,0 +1,703 @@ +/* + Unix SMB/CIFS implementation. + async socket syscalls + Copyright (C) Volker Lendecke 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "includes.h" +#include "lib/talloc/talloc.h" +#include "lib/tevent/tevent.h" +#include "lib/async_req/async_req.h" +#include "lib/async_req/async_sock.h" +#include + +#ifndef TALLOC_FREE +#define TALLOC_FREE(ctx) do { talloc_free(ctx); ctx=NULL; } while(0) +#endif + +/** + * Discriminator for async_syscall_state + */ +enum async_syscall_type { + ASYNC_SYSCALL_SEND, + ASYNC_SYSCALL_SENDALL, + ASYNC_SYSCALL_RECV, + ASYNC_SYSCALL_RECVALL, + ASYNC_SYSCALL_CONNECT +}; + +/** + * Holder for syscall arguments and the result + */ + +struct async_syscall_state { + enum async_syscall_type syscall_type; + struct tevent_fd *fde; + + union { + struct param_send { + int fd; + const void *buffer; + size_t length; + int flags; + } param_send; + struct param_sendall { + int fd; + const void *buffer; + size_t length; + int flags; + size_t sent; + } param_sendall; + struct param_recv { + int fd; + void *buffer; + size_t length; + int flags; + } param_recv; + struct param_recvall { + int fd; + void *buffer; + size_t length; + int flags; + size_t received; + } param_recvall; + struct param_connect { + /** + * connect needs to be done on a nonblocking + * socket. Keep the old flags around + */ + long old_sockflags; + int fd; + const struct sockaddr *address; + socklen_t address_len; + } param_connect; + } param; + + union { + ssize_t result_ssize_t; + size_t result_size_t; + int result_int; + } result; + int sys_errno; +}; + +/** + * @brief Create a new async syscall req + * @param[in] mem_ctx The memory context to hang the result off + * @param[in] ev The event context to work from + * @param[in] type Which syscall will this be + * @param[in] pstate Where to put the newly created private_data state + * @retval The new request + * + * This is a helper function to prepare a new struct async_req with an + * associated struct async_syscall_state. The async_syscall_state will be put + * into the async_req as private_data. + */ + +static struct async_req *async_syscall_new(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + enum async_syscall_type type, + struct async_syscall_state **pstate) +{ + struct async_req *result; + struct async_syscall_state *state; + + if (!async_req_setup(mem_ctx, &result, &state, + struct async_syscall_state)) { + return NULL; + } + state->syscall_type = type; + + result->private_data = state; + + *pstate = state; + + return result; +} + +/** + * @brief Create a new async syscall req based on a fd + * @param[in] mem_ctx The memory context to hang the result off + * @param[in] ev The event context to work from + * @param[in] type Which syscall will this be + * @param[in] fd The file descriptor we work on + * @param[in] fde_flags TEVENT_FD_READ/WRITE -- what are we interested in? + * @param[in] fde_cb The callback function for the file descriptor event + * @param[in] pstate Where to put the newly created private_data state + * @retval The new request + * + * This is a helper function to prepare a new struct async_req with an + * associated struct async_syscall_state and an associated file descriptor + * event. + */ + +static struct async_req *async_fde_syscall_new( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + enum async_syscall_type type, + int fd, + uint16_t fde_flags, + void (*fde_cb)(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv), + struct async_syscall_state **pstate) +{ + struct async_req *result; + struct async_syscall_state *state; + + result = async_syscall_new(mem_ctx, ev, type, &state); + if (result == NULL) { + return NULL; + } + + state->fde = tevent_add_fd(ev, state, fd, fde_flags, fde_cb, result); + if (state->fde == NULL) { + TALLOC_FREE(result); + return NULL; + } + *pstate = state; + return result; +} + +/** + * Retrieve a ssize_t typed result from an async syscall + * @param[in] req The syscall that has just finished + * @param[out] perrno Where to put the syscall's errno + * @retval The return value from the asynchronously called syscall + */ + +ssize_t async_syscall_result_ssize_t(struct async_req *req, int *perrno) +{ + struct async_syscall_state *state = talloc_get_type_abort( + req->private_data, struct async_syscall_state); + + *perrno = state->sys_errno; + return state->result.result_ssize_t; +} + +/** + * Retrieve a size_t typed result from an async syscall + * @param[in] req The syscall that has just finished + * @param[out] perrno Where to put the syscall's errno + * @retval The return value from the asynchronously called syscall + */ + +size_t async_syscall_result_size_t(struct async_req *req, int *perrno) +{ + struct async_syscall_state *state = talloc_get_type_abort( + req->private_data, struct async_syscall_state); + + *perrno = state->sys_errno; + return state->result.result_size_t; +} + +/** + * Retrieve a int typed result from an async syscall + * @param[in] req The syscall that has just finished + * @param[out] perrno Where to put the syscall's errno + * @retval The return value from the asynchronously called syscall + */ + +int async_syscall_result_int(struct async_req *req, int *perrno) +{ + struct async_syscall_state *state = talloc_get_type_abort( + req->private_data, struct async_syscall_state); + + *perrno = state->sys_errno; + return state->result.result_int; +} + +/** + * fde event handler for the "send" syscall + * @param[in] ev The event context that sent us here + * @param[in] fde The file descriptor event associated with the send + * @param[in] flags Can only be TEVENT_FD_WRITE here + * @param[in] priv private data, "struct async_req *" in this case + */ + +static void async_send_callback(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv) +{ + struct async_req *req = talloc_get_type_abort( + priv, struct async_req); + struct async_syscall_state *state = talloc_get_type_abort( + req->private_data, struct async_syscall_state); + struct param_send *p = &state->param.param_send; + + if (state->syscall_type != ASYNC_SYSCALL_SEND) { + async_req_error(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + state->result.result_ssize_t = send(p->fd, p->buffer, p->length, + p->flags); + state->sys_errno = errno; + + TALLOC_FREE(state->fde); + + async_req_done(req); +} + +/** + * Async version of send(2) + * @param[in] mem_ctx The memory context to hang the result off + * @param[in] ev The event context to work from + * @param[in] fd The socket to send to + * @param[in] buffer The buffer to send + * @param[in] length How many bytes to send + * @param[in] flags flags passed to send(2) + * + * This function is a direct counterpart of send(2) + */ + +struct async_req *async_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + int fd, const void *buffer, size_t length, + int flags) +{ + struct async_req *result; + struct async_syscall_state *state; + + result = async_fde_syscall_new( + mem_ctx, ev, ASYNC_SYSCALL_SEND, + fd, TEVENT_FD_WRITE, async_send_callback, + &state); + if (result == NULL) { + return NULL; + } + + state->param.param_send.fd = fd; + state->param.param_send.buffer = buffer; + state->param.param_send.length = length; + state->param.param_send.flags = flags; + + return result; +} + +/** + * fde event handler for the "sendall" syscall group + * @param[in] ev The event context that sent us here + * @param[in] fde The file descriptor event associated with the send + * @param[in] flags Can only be TEVENT_FD_WRITE here + * @param[in] priv private data, "struct async_req *" in this case + */ + +static void async_sendall_callback(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv) +{ + struct async_req *req = talloc_get_type_abort( + priv, struct async_req); + struct async_syscall_state *state = talloc_get_type_abort( + req->private_data, struct async_syscall_state); + struct param_sendall *p = &state->param.param_sendall; + + if (state->syscall_type != ASYNC_SYSCALL_SENDALL) { + async_req_error(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + state->result.result_ssize_t = send(p->fd, + (const char *)p->buffer + p->sent, + p->length - p->sent, p->flags); + state->sys_errno = errno; + + if (state->result.result_ssize_t == -1) { + async_req_error(req, map_nt_error_from_unix(state->sys_errno)); + return; + } + + if (state->result.result_ssize_t == 0) { + async_req_error(req, NT_STATUS_END_OF_FILE); + return; + } + + p->sent += state->result.result_ssize_t; + if (p->sent > p->length) { + async_req_error(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + if (p->sent == p->length) { + TALLOC_FREE(state->fde); + async_req_done(req); + } +} + +/** + * @brief Send all bytes to a socket + * @param[in] mem_ctx The memory context to hang the result off + * @param[in] ev The event context to work from + * @param[in] fd The socket to send to + * @param[in] buffer The buffer to send + * @param[in] length How many bytes to send + * @param[in] flags flags passed to send(2) + * + * async_sendall calls send(2) as long as it is necessary to send all of the + * "length" bytes + */ + +struct async_req *sendall_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + int fd, const void *buffer, size_t length, + int flags) +{ + struct async_req *result; + struct async_syscall_state *state; + + result = async_fde_syscall_new( + mem_ctx, ev, ASYNC_SYSCALL_SENDALL, + fd, TEVENT_FD_WRITE, async_sendall_callback, + &state); + if (result == NULL) { + return NULL; + } + + state->param.param_sendall.fd = fd; + state->param.param_sendall.buffer = buffer; + state->param.param_sendall.length = length; + state->param.param_sendall.flags = flags; + state->param.param_sendall.sent = 0; + + return result; +} + +NTSTATUS sendall_recv(struct async_req *req) +{ + return async_req_simple_recv(req); +} + +/** + * fde event handler for the "recv" syscall + * @param[in] ev The event context that sent us here + * @param[in] fde The file descriptor event associated with the recv + * @param[in] flags Can only be TEVENT_FD_READ here + * @param[in] priv private data, "struct async_req *" in this case + */ + +static void async_recv_callback(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv) +{ + struct async_req *req = talloc_get_type_abort( + priv, struct async_req); + struct async_syscall_state *state = talloc_get_type_abort( + req->private_data, struct async_syscall_state); + struct param_recv *p = &state->param.param_recv; + + if (state->syscall_type != ASYNC_SYSCALL_RECV) { + async_req_error(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + state->result.result_ssize_t = recv(p->fd, p->buffer, p->length, + p->flags); + state->sys_errno = errno; + + TALLOC_FREE(state->fde); + + async_req_done(req); +} + +/** + * Async version of recv(2) + * @param[in] mem_ctx The memory context to hang the result off + * @param[in] ev The event context to work from + * @param[in] fd The socket to recv from + * @param[in] buffer The buffer to recv into + * @param[in] length How many bytes to recv + * @param[in] flags flags passed to recv(2) + * + * This function is a direct counterpart of recv(2) + */ + +struct async_req *async_recv(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + int fd, void *buffer, size_t length, + int flags) +{ + struct async_req *result; + struct async_syscall_state *state; + + result = async_fde_syscall_new( + mem_ctx, ev, ASYNC_SYSCALL_RECV, + fd, TEVENT_FD_READ, async_recv_callback, + &state); + + if (result == NULL) { + return NULL; + } + + state->param.param_recv.fd = fd; + state->param.param_recv.buffer = buffer; + state->param.param_recv.length = length; + state->param.param_recv.flags = flags; + + return result; +} + +/** + * fde event handler for the "recvall" syscall group + * @param[in] ev The event context that sent us here + * @param[in] fde The file descriptor event associated with the recv + * @param[in] flags Can only be TEVENT_FD_READ here + * @param[in] priv private data, "struct async_req *" in this case + */ + +static void async_recvall_callback(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv) +{ + struct async_req *req = talloc_get_type_abort( + priv, struct async_req); + struct async_syscall_state *state = talloc_get_type_abort( + req->private_data, struct async_syscall_state); + struct param_recvall *p = &state->param.param_recvall; + + if (state->syscall_type != ASYNC_SYSCALL_RECVALL) { + async_req_error(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + state->result.result_ssize_t = recv(p->fd, + (char *)p->buffer + p->received, + p->length - p->received, p->flags); + state->sys_errno = errno; + + if (state->result.result_ssize_t == -1) { + async_req_error(req, map_nt_error_from_unix(state->sys_errno)); + return; + } + + if (state->result.result_ssize_t == 0) { + async_req_error(req, NT_STATUS_END_OF_FILE); + return; + } + + p->received += state->result.result_ssize_t; + if (p->received > p->length) { + async_req_error(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + if (p->received == p->length) { + TALLOC_FREE(state->fde); + async_req_done(req); + } +} + +/** + * Receive a specified number of bytes from a socket + * @param[in] mem_ctx The memory context to hang the result off + * @param[in] ev The event context to work from + * @param[in] fd The socket to recv from + * @param[in] buffer The buffer to recv into + * @param[in] length How many bytes to recv + * @param[in] flags flags passed to recv(2) + * + * async_recvall will call recv(2) until "length" bytes are received + */ + +struct async_req *recvall_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + int fd, void *buffer, size_t length, + int flags) +{ + struct async_req *result; + struct async_syscall_state *state; + + result = async_fde_syscall_new( + mem_ctx, ev, ASYNC_SYSCALL_RECVALL, + fd, TEVENT_FD_READ, async_recvall_callback, + &state); + if (result == NULL) { + return NULL; + } + + state->param.param_recvall.fd = fd; + state->param.param_recvall.buffer = buffer; + state->param.param_recvall.length = length; + state->param.param_recvall.flags = flags; + state->param.param_recvall.received = 0; + + return result; +} + +NTSTATUS recvall_recv(struct async_req *req) +{ + return async_req_simple_recv(req); +} + +struct async_connect_state { + int fd; + int result; + int sys_errno; + long old_sockflags; +}; + +static void async_connect_connected(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv); + +/** + * @brief async version of connect(2) + * @param[in] mem_ctx The memory context to hang the result off + * @param[in] ev The event context to work from + * @param[in] fd The socket to recv from + * @param[in] address Where to connect? + * @param[in] address_len Length of *address + * @retval The async request + * + * This function sets the socket into non-blocking state to be able to call + * connect in an async state. This will be reset when the request is finished. + */ + +struct async_req *async_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, const struct sockaddr *address, + socklen_t address_len) +{ + struct async_req *result; + struct async_connect_state *state; + struct tevent_fd *fde; + NTSTATUS status; + + if (!async_req_setup(mem_ctx, &result, &state, + struct async_connect_state)) { + return NULL; + } + + /** + * We have to set the socket to nonblocking for async connect(2). Keep + * the old sockflags around. + */ + + state->fd = fd; + state->sys_errno = 0; + + state->old_sockflags = fcntl(fd, F_GETFL, 0); + if (state->old_sockflags == -1) { + goto post_errno; + } + + set_blocking(fd, false); + + state->result = connect(fd, address, address_len); + if (state->result == 0) { + state->sys_errno = 0; + status = NT_STATUS_OK; + goto post_status; + } + + /** + * A number of error messages show that something good is progressing + * and that we have to wait for readability. + * + * If none of them are present, bail out. + */ + + if (!(errno == EINPROGRESS || errno == EALREADY || +#ifdef EISCONN + errno == EISCONN || +#endif + errno == EAGAIN || errno == EINTR)) { + goto post_errno; + } + + fde = tevent_add_fd(ev, state, fd, TEVENT_FD_READ | TEVENT_FD_WRITE, + async_connect_connected, result); + if (fde == NULL) { + status = NT_STATUS_NO_MEMORY; + goto post_status; + } + return result; + + post_errno: + state->sys_errno = errno; + status = map_nt_error_from_unix(state->sys_errno); + post_status: + fcntl(fd, F_SETFL, state->old_sockflags); + if (!async_post_status(result, ev, status)) { + goto fail; + } + return result; + fail: + TALLOC_FREE(result); + return NULL; +} + +/** + * fde event handler for connect(2) + * @param[in] ev The event context that sent us here + * @param[in] fde The file descriptor event associated with the connect + * @param[in] flags Indicate read/writeability of the socket + * @param[in] priv private data, "struct async_req *" in this case + */ + +static void async_connect_connected(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv) +{ + struct async_req *req = talloc_get_type_abort( + priv, struct async_req); + struct async_connect_state *state = talloc_get_type_abort( + req->private_data, struct async_connect_state); + + TALLOC_FREE(fde); + + /* + * Stevens, Network Programming says that if there's a + * successful connect, the socket is only writable. Upon an + * error, it's both readable and writable. + */ + if ((flags & (TEVENT_FD_READ|TEVENT_FD_WRITE)) + == (TEVENT_FD_READ|TEVENT_FD_WRITE)) { + int sockerr; + socklen_t err_len = sizeof(sockerr); + + if (getsockopt(state->fd, SOL_SOCKET, SO_ERROR, + (void *)&sockerr, &err_len) == 0) { + errno = sockerr; + } + + state->sys_errno = errno; + + DEBUG(10, ("connect returned %s\n", strerror(errno))); + + fcntl(state->fd, F_SETFL, state->old_sockflags); + async_req_error(req, map_nt_error_from_unix(state->sys_errno)); + return; + } + + state->sys_errno = 0; + async_req_done(req); +} + +NTSTATUS async_connect_recv(struct async_req *req, int *perrno) +{ + struct async_connect_state *state = talloc_get_type_abort( + req->private_data, struct async_connect_state); + NTSTATUS status; + + fcntl(state->fd, F_SETFL, state->old_sockflags); + + *perrno = state->sys_errno; + + if (async_req_is_error(req, &status)) { + return status; + } + if (state->sys_errno == 0) { + return NT_STATUS_OK; + } + return map_nt_error_from_unix(state->sys_errno); +} -- cgit