/*
 * Unix SMB/CIFS implementation.
 * SMB parameters and setup
 * Copyright (C) Andrew Tridgell 1992-1998 Modified by Jeremy Allison 1995.
 *
 * 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 "replace.h"
#include "lib/util/util_file.h"
#include "source3/lib/util_file.h"
#include "lib/util/debug.h"
#include "lib/util/samba_util.h"
#include "lib/util/sys_rw.h"
#include "lib/util/sys_popen.h"
#include "lib/async_req/async_sock.h"
#include "lib/util/tevent_unix.h"

struct file_ploadv_state {
	struct tevent_context *ev;
	struct tevent_req *subreq;
	size_t maxsize;
	int fd;
	uint8_t *buf;
};

static void file_ploadv_cleanup_fn(
	struct tevent_req *req, enum tevent_req_state req_state);
static void file_ploadv_readable(struct tevent_req *subreq);

struct tevent_req *file_ploadv_send(TALLOC_CTX *mem_ctx,
				   struct tevent_context *ev,
				   char * const argl[], size_t maxsize)
{
	struct tevent_req *req = NULL;
	struct file_ploadv_state *state = NULL;

	req = tevent_req_create(mem_ctx, &state, struct file_ploadv_state);
	if (req == NULL) {
		return NULL;
	}
	state->ev = ev;
	state->maxsize = maxsize;

	state->fd = sys_popenv(argl);
	if (state->fd == -1) {
		tevent_req_error(req, errno);
		return tevent_req_post(req, ev);
	}
	tevent_req_set_cleanup_fn(req, file_ploadv_cleanup_fn);

	state->subreq = wait_for_read_send(state, state->ev, state->fd, false);
	if (tevent_req_nomem(state->subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(state->subreq, file_ploadv_readable, req);
	return req;
}

static void file_ploadv_cleanup_fn(
	struct tevent_req *req, enum tevent_req_state req_state)
{
	struct file_ploadv_state *state = tevent_req_data(
		req, struct file_ploadv_state);

	TALLOC_FREE(state->subreq);
	if (state->fd != -1) {
		sys_pclose(state->fd);
		state->fd = -1;
	}
}

static void file_ploadv_readable(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct file_ploadv_state *state = tevent_req_data(
		req, struct file_ploadv_state);
	uint8_t buf[1024];
	uint8_t *tmp;
	ssize_t nread;
	size_t bufsize;
	int err;
	bool ok;

	ok = wait_for_read_recv(subreq, &err);
	TALLOC_FREE(subreq);
	state->subreq = NULL;
	if (!ok) {
		tevent_req_error(req, err);
		return;
	}

	nread = sys_read(state->fd, buf, sizeof(buf));
	if (nread == -1) {
		tevent_req_error(req, errno);
		return;
	}
	if (nread == 0) {
		tevent_req_done(req);
		return;
	}

	bufsize = talloc_get_size(state->buf);
	if (bufsize > 0) {
		/*
		 * Last round we've added the trailing '\0'. Remove it
		 * for this round.
		 */
		bufsize -= 1;
	}

	if (((bufsize + nread) < bufsize) ||
	    ((bufsize + nread + 1) < bufsize)) {
		/* overflow */
		tevent_req_error(req, EMSGSIZE);
		return;
	}

	if ((state->maxsize != 0) && ((bufsize + nread) > state->maxsize)) {
		tevent_req_error(req, EMSGSIZE);
		return;
	}

	tmp = talloc_realloc(state, state->buf, uint8_t, bufsize + nread + 1);
	if (tevent_req_nomem(tmp, req)) {
		return;
	}
	state->buf = tmp;

	memcpy(state->buf + bufsize, buf, nread);
	state->buf[bufsize+nread] = '\0';

	state->subreq = wait_for_read_send(state, state->ev, state->fd, false);
	if (tevent_req_nomem(state->subreq, req)) {
		return;
	}
	tevent_req_set_callback(state->subreq, file_ploadv_readable, req);
}

int file_ploadv_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
		    uint8_t **buf)
{
	struct file_ploadv_state *state = tevent_req_data(
		req, struct file_ploadv_state);
	int err;

	if (tevent_req_is_unix_error(req, &err)) {
		return err;
	}
	*buf = talloc_move(mem_ctx, &state->buf);

	tevent_req_received(req);

	return 0;
}
