/*****************************************************************************\
 *  io.c - definitions for connection I/O in connection manager
 *****************************************************************************
 *  Copyright (C) SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm 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 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#define _GNU_SOURCE
#include <limits.h>
#include <sys/uio.h>

#include "slurm/slurm_errno.h"

#include "src/common/fd.h"
#include "src/common/macros.h"
#include "src/common/pack.h"
#include "src/common/read_config.h"
#include "src/common/slurm_time.h"
#include "src/common/xmalloc.h"

#include "src/conmgr/conmgr.h"
#include "src/conmgr/mgr.h"

#define DEFAULT_READ_BYTES 512

/*
 * Default number of write()s to queue up using the stack instead of xmalloc().
 * Avoid the slow down from calling xmalloc() on a majority of the writev()s.
 */
#define IOV_STACK_COUNT 16
#define HANDLE_WRITEV_ARGS_MAGIC 0x1a4afb40
typedef struct {
	int magic; /* HANDLE_WRITEV_ARGS_MAGIC */
	int index;
	const int iov_count;
	conmgr_fd_t *con;
	struct iovec *iov;
	ssize_t wrote;
} handle_writev_args_t;

extern void resize_input_buffer(conmgr_callback_args_t conmgr_args, void *arg)
{
	int rc;
	uint64_t bytes = (uint64_t) arg;
	conmgr_fd_t *con = conmgr_args.con;

	if (conmgr_args.status == CONMGR_WORK_STATUS_CANCELLED)
		return;

	xassert(bytes > 0);
	xassert(bytes < MAX_MSG_SIZE);

	if (!(rc = try_grow_buf_remaining(con->in, bytes)))
		return;

	log_flag(NET, "%s: [%s] unable to increase buffer %"PRIu64" bytes for RPC message: %s",
		 __func__, con->name, bytes, slurm_strerror(rc));

	/* conmgr will be unable to read entire RPC -> close connection now */
	close_con(false, con);
}

static int _get_fd_readable(conmgr_fd_t *con)
{
	int readable = 0;

	if (fd_get_readable_bytes(con->input_fd, &readable, con->name) ||
	    !readable) {
		if (con->mss != NO_VAL)
			readable = con->mss;
		else
			readable = DEFAULT_READ_BYTES;
	}

	/*
	 * Limit read byte count to avoid creating huge buffers from a huge MSS
	 * on a loopback device or a buggy device driver.
	 */
	readable = MIN(readable, MAX_MSG_SIZE);

	/*
	 * Even if there are zero bytes to read, we want to make sure that we
	 * already try to do the read to avoid a shutdown(SHUT_RDWR) file
	 * descriptor never getting the final read()=0.
	 */
	readable = MAX(readable, DEFAULT_READ_BYTES);

	return readable;
}

extern void read_input(conmgr_fd_t *con, buf_t *buf, const char *what)
{
	ssize_t read_c;
	int rc, readable;

	con_unset_flag(con, FLAG_CAN_READ);
	xassert(con->magic == MAGIC_CON_MGR_FD);

	if (con->input_fd < 0) {
		log_flag(NET, "%s: [%s] called on closed connection",
			 __func__, con->name);
		return;
	}

	readable = _get_fd_readable(con);

	/* Grow buffer as needed to handle the incoming data */
	if ((rc = try_grow_buf_remaining(buf, readable))) {
		error("%s: [%s] unable to allocate larger %s: %s",
		      __func__, con->name, what, slurm_strerror(rc));
		close_con(false, con);
		return;
	}

	/* check for errors with a NULL read */
	if ((read_c = read(con->input_fd,
			   (get_buf_data(buf) + get_buf_offset(buf)),
			   readable)) < 0) {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			log_flag(NET, "%s: [%s] socket would block on read",
				 __func__, con->name);
			return;
		}

		log_flag(NET, "%s: [%s] error while reading: %m",
			 __func__, con->name);

		close_con(false, con);
		return;
	}

	/* Always update read timestamp on read() success */
	if (con_flag(con, FLAG_WATCH_READ_TIMEOUT))
		con->last_read = timespec_now();

	if (read_c == 0) {
		log_flag(NET, "%s: [%s] read EOF with %u bytes to process already in %s",
			 __func__, con->name, get_buf_offset(buf), what);

		slurm_mutex_lock(&mgr.mutex);
		/* lock to tell mgr that we are done */
		con_set_flag(con, FLAG_READ_EOF);
		slurm_mutex_unlock(&mgr.mutex);
	} else {
		log_flag(NET, "%s: [%s] read %zd bytes with %u bytes to process already in %s",
			 __func__, con->name, read_c, get_buf_offset(buf),
			 what);
		log_flag_hex(NET_RAW,
			     (get_buf_data(buf) + get_buf_offset(buf)),
			     read_c, "%s: [%s] read", __func__, con->name);

		set_buf_offset(buf, (get_buf_offset(buf) + read_c));
	}
}

extern void handle_read(conmgr_callback_args_t conmgr_args, void *arg)
{
	conmgr_fd_t *con = conmgr_args.con;

	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(!con_flag(con, FLAG_TLS_CLIENT) || !con->tls);
	xassert(!con_flag(con, FLAG_TLS_SERVER) || !con->tls);
	xassert(!con->tls);

	read_input(con, con->in, "input buffer");
}

static int _foreach_add_writev_iov(void *x, void *arg)
{
	buf_t *out = x;
	handle_writev_args_t *args = arg;
	struct iovec *iov = &args->iov[args->index];

	xassert(out->magic == BUF_MAGIC);
	xassert(args->magic == HANDLE_WRITEV_ARGS_MAGIC);

	if (args->index >= args->iov_count)
		return -1;

	iov->iov_base = ((void *) get_buf_data(out)) + get_buf_offset(out);
	iov->iov_len = remaining_buf(out);

	log_flag(CONMGR, "%s: [%s] queued writev[%d] %u/%u bytes to outgoing fd %u",
		 __func__, args->con->name, args->index, remaining_buf(out),
		 size_buf(out), args->con->output_fd);

	args->index++;
	return 0;
}

static int _foreach_writev_flush_bytes(void *x, void *arg)
{
	buf_t *out = x;
	handle_writev_args_t *args = arg;

	xassert(out->magic == BUF_MAGIC);
	xassert(args->magic == HANDLE_WRITEV_ARGS_MAGIC);
	xassert(args->wrote >= 0);

	if (!args->wrote)
		return 0;

	if (args->wrote >= remaining_buf(out)) {
		log_flag(NET, "%s: [%s] completed write[%d] of %u/%u bytes to outgoing fd %u",
			 __func__, args->con->name, args->index,
			 remaining_buf(out), size_buf(out),
			 args->con->output_fd);
		log_flag_hex_range(NET_RAW, get_buf_data(out), size_buf(out),
				   get_buf_offset(out), size_buf(out),
				   "%s: [%s] completed write[%d] of %u/%u bytes",
				   __func__, args->con->name, args->index,
				   remaining_buf(out), size_buf(out));

		args->wrote -= remaining_buf(out);
		args->index++;
		return 1;
	} else {
		log_flag(CONMGR, "%s: [%s] partial write[%d] of %zd/%u bytes to outgoing fd %u",
			 __func__, args->con->name, args->index,
			 args->wrote, size_buf(out), args->con->output_fd);
		log_flag_hex_range(NET_RAW, get_buf_data(out), size_buf(out),
				   get_buf_offset(out), args->wrote,
				   "%s: [%s] partial write[%d] of %zd/%u bytes",
				   __func__, args->con->name, args->index,
				   args->wrote, remaining_buf(out));

		set_buf_offset(out, get_buf_offset(out) + args->wrote);
		args->wrote = 0;
		args->index++;
		return 0;
	}
}

extern void write_output(conmgr_fd_t *con, const int out_count, list_t *out)
{
	const int iov_count = MIN(IOV_MAX, out_count);
	struct iovec iov_stack[IOV_STACK_COUNT];
	handle_writev_args_t args = {
		.magic = HANDLE_WRITEV_ARGS_MAGIC,
		.iov_count = iov_count,
		.con = con,
		.iov = iov_stack,
	};

	/* Try to use stack for small write counts when possible */
	if (iov_count > ARRAY_SIZE(iov_stack))
		args.iov = xcalloc(iov_count, sizeof(*args.iov));

	(void) list_for_each_ro(out, _foreach_add_writev_iov, &args);
	xassert(args.index == iov_count);

	args.wrote = writev(con->output_fd, args.iov, iov_count);

	if (args.wrote == -1) {
		if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
			log_flag(NET, "%s: [%s] retry write: %m",
				 __func__, con->name);
		} else {
			error("%s: [%s] writev(%d) failed: %m",
			      __func__, con->name, con->output_fd);
			/* drop outbound data on the floor */
			list_flush(out);
			close_con(false, con);
			close_con_output(false, con);
		}
	} else if (args.wrote == 0) {
		log_flag(NET, "%s: [%s] wrote 0 bytes", __func__, con->name);
	} else {
		log_flag(NET, "%s: [%s] wrote %zd bytes",
			 __func__, con->name, args.wrote);

		args.index = 0;
		(void) list_delete_all(out, _foreach_writev_flush_bytes,
				       &args);
		xassert(!args.wrote);

		if (con_flag(con, FLAG_WATCH_WRITE_TIMEOUT))
			con->last_write = timespec_now();
	}

	if (args.iov != iov_stack)
		xfree(args.iov);
}

extern void handle_write(conmgr_callback_args_t conmgr_args, void *arg)
{
	conmgr_fd_t *con = conmgr_args.con;
	int out_count;

	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(!con_flag(con, FLAG_TLS_CLIENT) || !con->tls);
	xassert(!con_flag(con, FLAG_TLS_SERVER));
	xassert(!con->tls);

	if (!(out_count = list_count(con->out)))
		log_flag(CONMGR, "%s: [%s] skipping attempt with zero writes",
			 __func__, con->name);
	else
		write_output(con, out_count, con->out);
}

extern void wrap_on_data(conmgr_callback_args_t conmgr_args, void *arg)
{
	conmgr_fd_t *con = conmgr_args.con;
	int rc = EINVAL;
	int (*callback)(conmgr_fd_t *con, void *arg) = NULL;
	const char *callback_string = NULL;
	void *con_arg = NULL;
	buf_t shadow_buffer = { 0 };
	buf_t *in = NULL, *buf = &shadow_buffer;
	const conmgr_events_t *events = NULL;
	conmgr_con_type_t type = CON_TYPE_INVALID;

	xassert(con->magic == MAGIC_CON_MGR_FD);

	slurm_mutex_lock(&mgr.mutex);

	xassert(con->in->magic == BUF_MAGIC);
	in = con->in;
	type = con->type;

	/*
	 * Create shadow buffer to point to the data owned by the con->in
	 * buffer. get_buf_offset(con->in) signifies how many of incoming bytes
	 * have been read(). The shadow buffer needs to have size_buf(buf)
	 * signify how much data has been read() and needs to be processed. The
	 * shadow buffer is temporarily swapped out with the con->in buffer to
	 * avoid having to change around logic handling processing incoming data
	 * by the on_data() callback while avoiding having to store another
	 * buffer (or a set of pointers or offsets) in conmgr_fd_t.
	 */
	*buf = (buf_t) {
		.magic = BUF_MAGIC,
		.head = get_buf_data(in),
		.size = get_buf_offset(con->in),
		.shadow = true,
	};

	con->in = buf;
	con_arg = con->arg;
	events = con->events;

	if (type == CON_TYPE_RAW) {
		callback = events->on_data;
		callback_string = XSTRINGIFY(events->on_data);
	} else if (type == CON_TYPE_RPC) {
		callback = on_rpc_connection_data;
		callback_string = XSTRINGIFY(on_rpc_connection_data);
	} else {
		fatal("%s: invalid type", __func__);
	}

	slurm_mutex_unlock(&mgr.mutex);

	log_flag(CONMGR, "%s: [%s] BEGIN func=%s(arg=0x%"PRIxPTR")@0x%"PRIxPTR,
		 __func__, con->name, callback_string, (uintptr_t) con_arg,
		 (uintptr_t) callback);

	rc = callback(con, con_arg);

	log_flag(CONMGR, "%s: [%s] END func=%s(arg=0x%"PRIxPTR")@0x%"PRIxPTR"=[%d]%s",
		 __func__, con->name, callback_string, (uintptr_t) con_arg,
		 (uintptr_t) callback, rc, slurm_strerror(rc));

	if (rc) {
		error("%s: [%s] on_data returned rc: %s",
		      __func__, con->name, slurm_strerror(rc));

		slurm_mutex_lock(&mgr.mutex);
		if (mgr.exit_on_error)
			mgr.shutdown_requested = true;

		if (!mgr.error)
			mgr.error = rc;

		/*
		 * processing data failed so drop any
		 * pending data on the floor
		 */
		log_flag(CONMGR, "%s: [%s] on_data callback failed. Purging the remaining %d bytes of pending input.",
			 __func__, con->name, get_buf_offset(buf));

		/* Return buffer to connection but empty it */
		xassert(con->in == buf);
		con->in = in;
		set_buf_offset(con->in, 0);

		close_con(true, con);
		slurm_mutex_unlock(&mgr.mutex);
		return;
	}

	/* check if shifting buffer data is required on partial read */
	if ((get_buf_offset(buf) > 0) &&
	    (get_buf_offset(buf) < size_buf(buf))) {
		/*
		 * Need to shift it to start of buffer but fix offsets when
		 * holding the lock
		 */
		(void) memmove(get_buf_data(buf),
			       (get_buf_data(buf) + get_buf_offset(buf)),
			       remaining_buf(buf));
	}

	slurm_mutex_lock(&mgr.mutex);

	/* Catch anything changing con->in's pointers */
	xassert(get_buf_data(buf) == get_buf_data(in));
	xassert(size_buf(buf) == get_buf_offset(in));
	xassert(get_buf_offset(buf) <= size_buf(buf));

	/*
	 * Catch if events callbacks or type changed during callback and read
	 * was not complete
	 */
	if (((events != con->events) || (type != con->type)) &&
	    (get_buf_offset(buf) < size_buf(buf)))
		callback = NULL;

	if (!get_buf_offset(buf)) {
		/* need more data for parser to read */
		log_flag(CONMGR, "%s: [%s] parser refused to read %u bytes. Waiting for more data",
			 __func__, con->name, size_buf(buf));

		/* Only set FLAG_ON_DATA_TRIED if not trying next callback */
		if (callback)
			con_set_flag(con, FLAG_ON_DATA_TRIED);
	} else if (get_buf_offset(buf) < size_buf(buf)) {
		log_flag(CONMGR, "%s: [%s] partial read of %u/%u bytes",
			 __func__, con->name, get_buf_offset(buf),
			 size_buf(buf));

		/* reset start of offset to end of previous data */
		set_buf_offset(in, remaining_buf(buf));
	} else {
		xassert(get_buf_offset(buf) == size_buf(buf));

		log_flag(CONMGR, "%s: [%s] read %u/%u bytes",
			 __func__, con->name, get_buf_offset(buf),
			 size_buf(buf));

		/* buffer completely read: reset it */
		set_buf_offset(in, 0);
	}

	/* Return buffer to connection */
	xassert(con->in == buf);
	con->in = in;

	slurm_mutex_unlock(&mgr.mutex);

	/*
	 * Call immediately to avoid waiting for another rotation of the work
	 * queue and _handle_connection() as change in events happened during
	 * the callback
	 */
	if (!callback)
		wrap_on_data(conmgr_args, arg);
}

/* Copy buffer into new buf_t */
static buf_t *_buf_clone(const void *buffer, const size_t bytes)
{
	buf_t *buf = NULL;

	xassert(bytes > 0);

	buf = init_buf(bytes);

	memmove(get_buf_data(buf), buffer, bytes);

	return buf;
}

/*
 * Append buffer to output queue
 * WARNING: caller must hold mgr.mutex lock
 * IN con - connection to queue outgoing buffer
 * IN buf - buffer to queue as output (takes ownership)
 * RET SLURM_SUCCESS or error
 */
static int _append_output(conmgr_fd_t *con, buf_t *buf)
{
	xassert(con->magic == MAGIC_CON_MGR_FD);

	log_flag(NET, "%s: [%s] write of %u bytes queued",
		 __func__, con->name, get_buf_offset(buf));

	log_flag_hex(NET_RAW, get_buf_data(buf), get_buf_offset(buf),
		     "%s: queuing up write", __func__);

	list_append(con->out, buf);

	if (con_flag(con, FLAG_WATCH_WRITE_TIMEOUT))
		con->last_write = timespec_now();

	EVENT_SIGNAL(&mgr.watch_sleep);

	return SLURM_SUCCESS;
}

static int _write_data(conmgr_fd_t *con, const void *buffer, const size_t bytes)
{
	int rc = EINVAL;
	buf_t *buf = NULL;

	xassert(con->magic == MAGIC_CON_MGR_FD);

	/* Ignore empty write requests */
	if (!bytes)
		return SLURM_SUCCESS;

	/* TODO: would be nice to avoid this copy */
	buf = _buf_clone(buffer, bytes);

	slurm_mutex_lock(&mgr.mutex);
	rc = _append_output(con, buf);
	slurm_mutex_unlock(&mgr.mutex);

	return rc;
}

extern int conmgr_queue_write_data(conmgr_fd_t *con, const void *buffer,
				   const size_t bytes)
{
	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(buffer || !bytes);

	return _write_data(con, buffer, bytes);
}

extern int conmgr_con_queue_write_data(conmgr_fd_ref_t *ref, const void *buffer,
				       const size_t bytes)
{
	if (!ref)
		return EINVAL;

	xassert(ref->magic == MAGIC_CON_MGR_FD_REF);
	xassert(ref->con->magic == MAGIC_CON_MGR_FD);
	xassert(buffer || !bytes);

	return _write_data(ref->con, buffer, bytes);
}

static int _get_input_buffer(const conmgr_fd_t *con, const void **data_ptr,
			     size_t *bytes_ptr)
{
	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(con_flag(con, FLAG_WORK_ACTIVE));

	if (!con->in)
		return ENOENT;

	if (data_ptr)
		*data_ptr = get_buf_data(con->in) + get_buf_offset(con->in);
	*bytes_ptr = size_buf(con->in);

	return SLURM_SUCCESS;
}

extern void conmgr_fd_get_in_buffer(const conmgr_fd_t *con,
				    const void **data_ptr, size_t *bytes_ptr)
{
	xassert(con->magic == MAGIC_CON_MGR_FD);

	(void) _get_input_buffer(con, data_ptr, bytes_ptr);
}

extern int conmgr_con_get_input_buffer(conmgr_fd_ref_t *ref,
				       const void **data_ptr, size_t *bytes_ptr)
{
	xassert(ref);
	xassert(data_ptr || bytes_ptr);
	xassert(ref->magic == MAGIC_CON_MGR_FD_REF);

	return _get_input_buffer(ref->con, data_ptr, bytes_ptr);
}

static int _con_get_shadow_in_buffer(const conmgr_fd_t *con, buf_t **buf_ptr)
{
	buf_t *buffer = NULL;
	void *data = NULL;
	size_t bytes = 0;

	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(con->type == CON_TYPE_RAW);
	xassert(con_flag(con, FLAG_WORK_ACTIVE));

	if (!con->in)
		return EEXIST;

	data = (get_buf_data(con->in) + con->in->processed);
	bytes = (size_buf(con->in) - con->in->processed);

	if (!(buffer = create_shadow_buf(data, bytes)))
		return ENOMEM;

	xassert(!*buf_ptr);
	*buf_ptr = buffer;
	return SLURM_SUCCESS;
}

extern buf_t *conmgr_fd_shadow_in_buffer(const conmgr_fd_t *con)
{
	buf_t *buffer = NULL;

	(void) _con_get_shadow_in_buffer(con, &buffer);

	return buffer;
}

extern int conmgr_con_shadow_in_buffer(conmgr_fd_ref_t *ref, buf_t **buf_ptr)
{
	xassert(ref);
	xassert(ref->magic == MAGIC_CON_MGR_FD_REF);

	return _con_get_shadow_in_buffer(ref->con, buf_ptr);
}

static int _mark_consumed_in_buffer(const conmgr_fd_t *con, size_t bytes)
{
	ssize_t offset = -1;

	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(con_flag(con, FLAG_WORK_ACTIVE));

	if (!con->in)
		return ENOENT;

	offset = get_buf_offset(con->in) + bytes;
	xassert(offset <= size_buf(con->in));

	set_buf_offset(con->in, offset);
	return SLURM_SUCCESS;
}

extern void conmgr_fd_mark_consumed_in_buffer(const conmgr_fd_t *con,
					      size_t bytes)
{
	(void) _mark_consumed_in_buffer(con, bytes);
}

extern int conmgr_con_mark_consumed_input_buffer(conmgr_fd_ref_t *ref,
						 const size_t bytes)
{
	xassert(ref);
	xassert(ref->magic == MAGIC_CON_MGR_FD_REF);

	return _mark_consumed_in_buffer(ref->con, bytes);
}

extern int conmgr_fd_xfer_in_buffer(const conmgr_fd_t *con,
				    buf_t **buffer_ptr)
{
	const void *data = (get_buf_data(con->in) + get_buf_offset(con->in));
	const size_t bytes = (size_buf(con->in) - get_buf_offset(con->in));
	buf_t *buf = NULL;
	int rc;

	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(con->type == CON_TYPE_RAW);
	xassert(con_flag(con, FLAG_WORK_ACTIVE));

	xassert(buffer_ptr);
	if (!buffer_ptr)
		return EINVAL;

	/*
	 * Create buffer if needed and size it size of the data to copy or the
	 * minimal buffer size to avoid multiple recalloc()s in the future.
	 */
	if (!*buffer_ptr &&
	    !(*buffer_ptr = init_buf(MAX(bytes, BUFFER_START_SIZE))))
		return ENOMEM;

	buf = *buffer_ptr;

	/* grow buffer to size to hold incoming data (if needed) */
	if ((rc = try_grow_buf_remaining(buf, bytes)))
		return rc;

	/* Append data to existing buffer */
	memcpy((get_buf_data(buf) + get_buf_offset(buf)), data, bytes);
	set_buf_offset(buf, (get_buf_offset(buf) + bytes));

	/* mark connection input buffer as fully consumed */
	set_buf_offset(con->in, size_buf(con->in));
	return SLURM_SUCCESS;
}

extern int conmgr_fd_xfer_out_buffer(conmgr_fd_t *con, buf_t *output)
{
	int rc;

	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(con->type == CON_TYPE_RAW);
	xassert(!output || (output->magic == BUF_MAGIC));

	if (!output || !size_buf(output) || !get_buf_offset(output))
		return SLURM_SUCCESS;

	xassert(size_buf(output) <= xsize(get_buf_data(output)));
	xassert(get_buf_offset(output) <= size_buf(output));

	rc = conmgr_queue_write_data(con, get_buf_data(output),
				     get_buf_offset(output));

	if (!rc)
		set_buf_offset(output, 0);

	return rc;
}

static int _fd_get_input_fd(conmgr_fd_t *con, int *input_fd_ptr)
{
	if (!con)
		return EINVAL;

	xassert(input_fd_ptr);
	xassert(*input_fd_ptr == -1);
	xassert(con->magic == MAGIC_CON_MGR_FD);

	slurm_mutex_lock(&mgr.mutex);

	*input_fd_ptr = con->input_fd;

	slurm_mutex_unlock(&mgr.mutex);

	return SLURM_SUCCESS;
}

extern int conmgr_fd_get_input_fd(conmgr_fd_t *con)
{
	int input_fd = -1;

	(void) _fd_get_input_fd(con, &input_fd);

	return input_fd;
}

extern int conmgr_con_get_input_fd(conmgr_fd_ref_t *ref, int *input_fd_ptr)
{
	xassert(input_fd_ptr);

	if (!ref || !ref->con)
		return EINVAL;

	xassert(ref->magic == MAGIC_CON_MGR_FD_REF);
	xassert(ref->con->magic == MAGIC_CON_MGR_FD);

	return _fd_get_input_fd(ref->con, input_fd_ptr);
}

static int _fd_get_output_fd(conmgr_fd_t *con, int *output_fd_ptr)
{
	if (!con)
		return EINVAL;

	xassert(output_fd_ptr);
	xassert(*output_fd_ptr == -1);
	xassert(con->magic == MAGIC_CON_MGR_FD);

	slurm_mutex_lock(&mgr.mutex);

	*output_fd_ptr = con->output_fd;

	slurm_mutex_unlock(&mgr.mutex);

	return SLURM_SUCCESS;
}

extern int conmgr_fd_get_output_fd(conmgr_fd_t *con)
{
	int output_fd = -1;

	(void) _fd_get_output_fd(con, &output_fd);

	return output_fd;
}

extern int conmgr_con_get_output_fd(conmgr_fd_ref_t *ref, int *output_fd_ptr)
{
	xassert(output_fd_ptr);

	if (!ref || !ref->con)
		return EINVAL;

	xassert(ref->magic == MAGIC_CON_MGR_FD_REF);
	xassert(ref->con->magic == MAGIC_CON_MGR_FD);

	return _fd_get_output_fd(ref->con, output_fd_ptr);
}
