/*
   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 <http://www.gnu.org/licenses/>.
*/

#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 <fcntl.h>

#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_nterror(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_nterror(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_nterror(req, map_nt_error_from_unix(state->sys_errno));
		return;
	}

	if (state->result.result_ssize_t == 0) {
		async_req_nterror(req, NT_STATUS_END_OF_FILE);
		return;
	}

	p->sent += state->result.result_ssize_t;
	if (p->sent > p->length) {
		async_req_nterror(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_ntstatus(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_nterror(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_nterror(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_nterror(req, map_nt_error_from_unix(state->sys_errno));
		return;
	}

	if (state->result.result_ssize_t == 0) {
		async_req_nterror(req, NT_STATUS_END_OF_FILE);
		return;
	}

	p->received += state->result.result_ssize_t;
	if (p->received > p->length) {
		async_req_nterror(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_ntstatus(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_ntstatus(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_nterror(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_nterror(req, &status)) {
		return status;
	}
	if (state->sys_errno == 0) {
		return NT_STATUS_OK;
	}
	return map_nt_error_from_unix(state->sys_errno);
}