/* vifm
 * Copyright (C) 2011 xaizek.
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "ipc.h"

#ifdef ENABLE_REMOTE_CMDS

#if defined(_WIN32) || defined(__CYGWIN__)
/* Named pipes don't work very well on Cygwin neither directly nor indirectly
 * (using //./pipe/ paths with POSIX API), so use Win32 API on Cygwin. */
# define WIN32_PIPE_READ
#endif

#ifndef WIN32_PIPE_READ
# include <sys/types.h>
# include <sys/select.h> /* FD_* select() */
#else
# define O_NONBLOCK 0
# include <windows.h>
# ifndef PIPE_REJECT_REMOTE_CLIENTS
#  define PIPE_REJECT_REMOTE_CLIENTS 1
# endif
# ifdef KEY_EVENT
#  undef KEY_EVENT /* curses also defines a macro with such name. */
# endif
#endif

#include <sys/stat.h> /* mkfifo() stat() */
#include <dirent.h> /* DIR closedir() opendir() readdir() */
#include <fcntl.h>
#include <unistd.h> /* close() open() select() unlink() usleep() */

#include <errno.h> /* EACCES EEXIST EDQUOT ENOSPC ENXIO errno */
#include <stddef.h> /* NULL size_t ssize_t */
#include <stdio.h> /* FILE fclose() fdopen() fread() fwrite() */
#include <stdlib.h> /* free() malloc() snprintf() */
#include <string.h> /* strcmp() strcpy() strlen() */

#include "compat/fs_limits.h"
#include "compat/os.h"
#include "engine/text_buffer.h"
#include "utils/fs.h"
#include "utils/log.h"
#include "utils/macros.h"
#include "utils/path.h"
#include "utils/str.h"
#include "utils/string_array.h"
#include "utils/utils.h"
#include "status.h"

/**
 * When IPC is enabled, it exchanges packets between instances which are just
 * arrays of strings.  Each package looks like this:
 *
 *     "version:{...}" -\
 *     "from:{name}"   --\
 *     "body:{type}"   ---\
 *                         some sort of a header that ends on `body:`
 *     "string #1"     -\
 *     "string #2"     --\
 *     {...}           ---\
 *     "string #N"     ----\
 *                          payload (might be prepended by this unit)
 *     "\0"            -\
 *                       terminator
 *
 * Or in sequential form:
 *     "version:{...}\0body:{type}\0string #1\0string #2\0{...}string #N\0\0"
 *
 * {name} is a name of another instance.
 *
 * {type} can be:
 *  - "args" to pass list of arguments, in which case body is prepended with CWD
 *    unconditionally;
 *  - "eval" to pass an expression for evaluation in a single line;
 *  - "eval-result" to communicate result of successful evaluation in a single
 *    line;
 *  - "eval-error" to communicate failure of evaluation with no strings.
 *
 * On version mismatch or unknown field name, packet is discarded which is
 * logged.
 */

/* Prefix for names of all pipes to distinguish them from other pipes. */
#define PREFIX "vifm-ipc-"

#ifndef WIN32_PIPE_READ
typedef FILE *read_pipe_t;
#define NULL_READ_PIPE NULL
#else
typedef HANDLE read_pipe_t;
#define NULL_READ_PIPE INVALID_HANDLE_VALUE
#endif

/* Holds list information for add_to_list(). */
typedef struct
{
	char **lst;          /* List of strings. */
	size_t len;          /* Number of items. */
	const char *ipc_dir; /* Root of IPC objects. */
	const ipc_t *ipc;    /* Handle to an IPC instance or NULL. */
}
list_data_t;

/* Storage of data of an instance. */
struct ipc_t
{
	/* Stores callback to report received messages. */
	ipc_args_cb args_cb;
	/* Stores callback used for evaluation of expressions. */
	ipc_eval_cb eval_cb;
	/* Whether this IPC instance should ignore check requests from outside. */
	int locked;
	/* Path to the pipe used by this instance. */
	char pipe_path[PATH_MAX + 1];
	/* Opened file of the pipe. */
	read_pipe_t pipe_file;
	/* Holds result of expression evaluation or NULL on evaluation error. */
	char *eval_result;
};

static read_pipe_t create_pipe(const char name[], char path_buf[], size_t len);
static char * receive_pkg(ipc_t *ipc, int *len);
static read_pipe_t try_use_pipe(const char path[], int *fatal);
static void handle_pkg(ipc_t *ipc, const char pkg[], const char *end);
static void handle_args(ipc_t *ipc, char ***array, int len);
static void handle_expr(ipc_t *ipc, const char from[], char *array[], int len);
static void handle_eval_result(ipc_t *ipc, char *array[], int len);
static int format_and_send(ipc_t *ipc, const char whom[], char *data[],
		const char type[]);
static int send_pkg(ipc_t *ipc, const char whom[], const char what[],
		size_t len);
static char * get_the_only_target(const ipc_t *ipc);
static char ** list_servers(const ipc_t *ipc, int *len);
static int add_to_list(const char name[], const void *data, void *param);
static const char * get_ipc_dir(void);
#ifndef WIN32_PIPE_READ
static int pipe_is_in_use(const char path[]);
#endif

/* Current version string. */
static const char IPC_VERSION[] = "version:1";
/* Request to process remote arguments. */
static const char ARGS_TYPE[] = "args";
/* Request to process remote expression. */
static const char EVAL_TYPE[] = "eval";
/* Reply to remote expression with the result after successful evaluation. */
static const char EVAL_RESULT_TYPE[] = "eval-result";
/* Reply to remote expression on error. */
static const char EVAL_ERROR_TYPE[] = "eval-error";

int
ipc_enabled(void)
{
	return 1;
}

ipc_t *
ipc_init(const char name[], ipc_args_cb args_cb, ipc_eval_cb eval_cb)
{
	ipc_t *const ipc = malloc(sizeof(*ipc));
	if(ipc == NULL)
	{
		return NULL;
	}

	ipc->args_cb = args_cb;
	ipc->eval_cb = eval_cb;
	ipc->locked = 0;

	if(name == NULL)
	{
		name = "vifm";
	}

	ipc->pipe_file = create_pipe(name, ipc->pipe_path, sizeof(ipc->pipe_path));
	if(ipc->pipe_file == NULL_READ_PIPE)
	{
		free(ipc);
		return NULL;
	}

	return ipc;
}

void
ipc_free(ipc_t *ipc)
{
	if(ipc == NULL)
	{
		return;
	}

#ifndef WIN32_PIPE_READ
	fclose(ipc->pipe_file);
	unlink(ipc->pipe_path);
#else
	CloseHandle(ipc->pipe_file);
#endif
	free(ipc);
}

const char *
ipc_get_name(const ipc_t *ipc)
{
	return get_last_path_component(ipc->pipe_path) + (sizeof(PREFIX) - 1U);
}

int
ipc_check(ipc_t *ipc)
{
	int len;
	char *pkg;

	if(ipc->locked)
	{
		return 0;
	}

	pkg = receive_pkg(ipc, &len);
	if(pkg != NULL)
	{
		handle_pkg(ipc, pkg, pkg + len);
		free(pkg);
		return 1;
	}
	return 0;
}

/* Receives message addressed to this instance.  Returns NULL if there was no
 * message or on failure to read it, otherwise newly allocated string is
 * returned. */
static char *
receive_pkg(ipc_t *ipc, int *len)
{
#ifndef WIN32_PIPE_READ
	uint32_t size;
	char *pkg;
	char *p;

	fd_set ready;
	int max_fd;
	struct timeval ts = { .tv_sec = 0, .tv_usec = 10000 };

	/* At least on OS X pipe might get into EOF state, so reset it.  This will
	 * also reset any errors, which is fine with us. */
	clearerr(ipc->pipe_file);

	if(fread(&size, sizeof(size), 1U, ipc->pipe_file) != 1U ||
			size >= 4294967294U)
	{
		return NULL;
	}

	pkg = malloc(size + 1U);
	if(pkg == NULL)
	{
		LOG_ERROR_MSG("Failed to allocate memory: %lu", (unsigned long)(size + 1));
		return NULL;
	}

	max_fd = fileno(ipc->pipe_file);
	FD_ZERO(&ready);
	FD_SET(max_fd, &ready);

	p = pkg;
	while(size != 0U && select(max_fd + 1, &ready, NULL, NULL, &ts) > 0)
	{
		const size_t nread = fread(p, 1U, size, ipc->pipe_file);
		size -= nread;
		p += nread;

		if(nread == 0U)
		{
			break;
		}

		ts.tv_sec = 0;
		ts.tv_usec = 10000;
	}

	if(size != 0U)
	{
		free(pkg);
		LOG_ERROR_MSG("Failed to read whole packet, left: %lu",
				(unsigned long)size);
		return NULL;
	}

	/* Make sure we have a trailing zero. */
	*p = '\0';
	*len = p - pkg;

	return pkg;
#else
	uint32_t size;
	char *pkg;
	char *p;
	DWORD nread;

	if(ReadFile(ipc->pipe_file, &size, sizeof(size), &nread, NULL) == FALSE ||
			size >= 4294967294U)
	{
		return NULL;
	}

	pkg = malloc(size + 1U);
	if(pkg == NULL)
	{
		return NULL;
	}

	p = pkg;
	while(size != 0U)
	{
		/* TODO: maybe use OVERLAPPED I/O on Windows instead, it's just so
		 *       inconvenient... */
		usleep(10000);

		if(ReadFile(ipc->pipe_file, p, size, &nread, NULL) == FALSE || nread == 0U)
		{
			break;
		}

		size -= nread;
		p += nread;
	}

	/* Weird requirement for named pipes, need to break and set connection every
	 * time. */
	DisconnectNamedPipe(ipc->pipe_file);
	ConnectNamedPipe(ipc->pipe_file, NULL);

	if(size != 0U)
	{
		free(pkg);
		return NULL;
	}

	/* Make sure we have a trailing zero. */
	*p = '\0';
	*len = p - pkg;

	return pkg;
#endif
}

/* Tries to open a pipe for communication.  Returns NULL_READ_PIPE on error or
 * opened file descriptor otherwise. */
static read_pipe_t
create_pipe(const char name[], char path_buf[], size_t len)
{
	unsigned int id = 0U;
	read_pipe_t rp;
	int fatal;

	/* Try to use name as is at first. */
	snprintf(path_buf, len, "%s/" PREFIX "%s", get_ipc_dir(), name);
	rp = try_use_pipe(path_buf, &fatal);
	while(rp == NULL_READ_PIPE && !fatal)
	{
		snprintf(path_buf, len, "%s/" PREFIX "%s%u", get_ipc_dir(), name, ++id);

		if(id == 0)
		{
			return NULL_READ_PIPE;
		}

		rp = try_use_pipe(path_buf, &fatal);
	}

	return rp;
}

/* Either creates a pipe or reused previously abandoned one.  Returns
 * NULL_READ_PIPE on failure (with *fatal set to non-zero if further tries don't
 * make any sense) or valid file descriptor otherwise. */
static read_pipe_t
try_use_pipe(const char path[], int *fatal)
{
#ifndef WIN32_PIPE_READ
	FILE *f;
	int fd;

	*fatal = 0;

	/* Try to create a pipe. */
	if(mkfifo(path, 0600) == 0)
	{
		/* Open if created. */
		const int fd = open(path, O_RDONLY | O_NONBLOCK);
		if(fd == -1)
		{
			if(errno == EACCES)
			{
				/* If we created a file that we can't open, remove it and assume that
				 * something is fundamentally wrong with the system setup. */
				(void)unlink(path);
				*fatal = 1;
			}
			return NULL_READ_PIPE;
		}
		return fdopen(fd, "r");
	}

	/* Fail fast if the error is not related to existence of the file or pipe
	 * exists and is in use. */
	if(errno != EEXIST || pipe_is_in_use(path))
	{
		/* No retries if file-system is unable to create more files. */
		*fatal = (errno == EDQUOT || errno == ENOSPC);
		return NULL_READ_PIPE;
	}

	/* Use this file if we're the only one willing to read from it. */
	fd = open(path, O_RDONLY | O_NONBLOCK);

	if(fd == -1)
	{
		return NULL_READ_PIPE;
	}

	f = fdopen(fd, "r");
	if(f == NULL_READ_PIPE)
	{
		close(fd);
	}
	return f;
#else
	*fatal = 0;
	return CreateNamedPipeA(path,
			PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
			PIPE_TYPE_BYTE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS,
			PIPE_UNLIMITED_INSTANCES, 4096, 4096, 10, NULL);
#endif
}

/* Parses pkg into array of strings and invokes callback. */
static void
handle_pkg(ipc_t *ipc, const char pkg[], const char *end)
{
	char **array = NULL;
	size_t len = 0U;
	int in_body = 0;
	const char *type = NULL;
	const char *from = NULL;

	while(pkg != end)
	{
		if(in_body)
		{
			len = add_to_string_array(&array, len, pkg);
		}
		else if(starts_with_lit(pkg, "version:"))
		{
			if(strcmp(pkg, IPC_VERSION) != 0)
			{
				break;
			}
		}
		else if(starts_with_lit(pkg, "from:"))
		{
			from = after_first(pkg, ':');
		}
		else if(starts_with_lit(pkg, "body:"))
		{
			type = after_first(pkg, ':');
			in_body = 1;
		}
		else
		{
			break;
		}
		pkg += strlen(pkg) + 1;
	}

	if(pkg != end)
	{
		LOG_ERROR_MSG("Discarded remote package due to field: `%s`", pkg);
	}
	else if(from == NULL)
	{
		LOG_ERROR_MSG("Discarded remote package due to missing from field");
	}
	else if(type == NULL)
	{
		LOG_ERROR_MSG("Discarded remote package due to missing body field");
	}
	else if(strcmp(type, ARGS_TYPE) == 0)
	{
		handle_args(ipc, &array, len);
	}
	else if(strcmp(type, EVAL_TYPE) == 0)
	{
		handle_expr(ipc, from, array, len);
	}
	else if(strcmp(type, EVAL_RESULT_TYPE) == 0)
	{
		handle_eval_result(ipc, array, len);
	}
	else if(strcmp(type, EVAL_ERROR_TYPE) == 0)
	{
		ipc->eval_result = NULL;
	}
	else
	{
		LOG_ERROR_MSG("Discarded remote package due to unknown type: `%s`", type);
	}

	free_string_array(array, len);
}

/* Handles received message with arguments. */
static void
handle_args(ipc_t *ipc, char ***array, int len)
{
	if(len == 0U)
	{
		return;
	}

	if(put_into_string_array(array, len, NULL) == len + 1)
	{
		ipc->locked = 1;
		ipc->args_cb(*array);
		ipc->locked = 0;
	}
}

/* Handles received message with expression to evaluate. */
static void
handle_expr(ipc_t *ipc, const char from[], char *array[], int len)
{
	char *result;

	if(len != 1U)
	{
		LOG_ERROR_MSG("Incorrect number of lines in expr packet: %d", len);
		return;
	}

	ipc->locked = 1;
	result = ipc->eval_cb(array[0]);
	ipc->locked = 0;
	if(result == NULL)
	{
		char *data[] = { NULL };
		if(format_and_send(ipc, from, data, EVAL_ERROR_TYPE) != 0)
		{
			LOG_ERROR_MSG("Failed to report evaluation failure");
		}
	}
	else
	{
		char *data[] = { result, NULL };
		if(format_and_send(ipc, from, data, EVAL_RESULT_TYPE) != 0)
		{
			LOG_ERROR_MSG("Failed to report evaluation result");
		}
		free(result);
	}
}

/* Handles answer about successful evaluation of expression. */
static void
handle_eval_result(ipc_t *ipc, char *array[], int len)
{
	if(len == 1U)
	{
		ipc->eval_result = array[0];
		array[0] = NULL;
	}
}

int
ipc_send(ipc_t *ipc, const char whom[], char *data[])
{
	return format_and_send(ipc, whom, data, ARGS_TYPE);
}

char *
ipc_eval(ipc_t *ipc, const char whom[], const char expr[])
{
	enum { MAX_USEC = 1000000, MAX_REPEATS = 20 };
	int repeats;

	char *data[] = { (char *)expr, NULL };
	if(format_and_send(ipc, whom, data, EVAL_TYPE) != 0)
	{
		LOG_ERROR_MSG("Failed to send expression");
		return NULL;
	}

	/* Using sleep is just easier than doing read with timeout due to differences
	 * between platforms... */
	repeats = 0;
	while(!ipc_check(ipc))
	{
		if(++repeats > MAX_REPEATS)
		{
			LOG_ERROR_MSG("Timed out on waiting for --remote-expr response");
			return NULL;
		}
		usleep(MAX_USEC/MAX_REPEATS);
	}

	return ipc->eval_result;
}

/* Formats and sends a message of specified type.  The data array should be NULL
 * terminated.  Returns zero on successful send and non-zero otherwise. */
static int
format_and_send(ipc_t *ipc, const char whom[], char *data[], const char type[])
{
	vle_textbuf *pkg = vle_tb_create();
	if(pkg == NULL)
	{
		return -1;
	}

	/* Compose "header". */
	vle_tb_appendf(pkg, "%s%c", IPC_VERSION, '\0');
	vle_tb_appendf(pkg, "from:%s%c", ipc_get_name(ipc), '\0');
	vle_tb_appendf(pkg, "body:%s%c", type, '\0');

	if(strcmp(type, ARGS_TYPE) == 0)
	{
		char cwd[PATH_MAX + 1];
		if(get_cwd(cwd, sizeof(cwd)) == NULL)
		{
			vle_tb_free(pkg);
			LOG_ERROR_MSG("Can't get working directory");
			return 1;
		}
		vle_tb_appendf(pkg, "%s%c", cwd, '\0');
	}

	while(*data != NULL)
	{
		vle_tb_appendf(pkg, "%s%c", *data++, '\0');
	}

	char *name = NULL;
	if(whom == NULL)
	{
		name = get_the_only_target(ipc);
		if(name == NULL)
		{
			vle_tb_free(pkg);
			return 1;
		}
		whom = name;
	}

	int ret = send_pkg(ipc, whom, vle_tb_get_data(pkg), vle_tb_get_len(pkg));
	vle_tb_free(pkg);

	free(name);
	return ret;
}

/* Performs actual sending of package to another instance.  Returns zero on
 * success and non-zero otherwise. */
static int
send_pkg(ipc_t *ipc, const char whom[], const char what[], size_t len)
{
	if(stroscmp(ipc_get_name(ipc), whom) == 0)
	{
		LOG_SERROR_MSG(errno, "Won't send IPC message to myself");
		return 1;
	}

#ifndef WIN32_PIPE_READ
	char path[PATH_MAX + 1];
	int fd;
	FILE *dst;
	uint32_t size;

	snprintf(path, sizeof(path), "%s/" PREFIX "%s", get_ipc_dir(), whom);

	fd = open(path, O_WRONLY);
	if(fd == -1)
	{
		LOG_SERROR_MSG(errno, "Failed to open destination pipe");
		return 1;
	}

	dst = fdopen(fd, "w");
	if(dst == NULL)
	{
		LOG_SERROR_MSG(errno, "Failed to turn file descriptor into a FILE");
		close(fd);
		return 1;
	}

	size = len;
	if(fwrite(&size, sizeof(size), 1U, dst) != 1U ||
			fwrite(what, len, 1U, dst) != 1U)
	{
		LOG_SERROR_MSG(errno, "Failed to write into a pipe");
		(void)fclose(dst);
		return 1;
	}

	if(fclose(dst) != 0)
	{
		LOG_SERROR_MSG(errno, "Failure on close a pipe");
	}
	return 0;
#else
	char path[PATH_MAX + 1];
	HANDLE h;
	uint32_t size;
	DWORD nwritten;

	snprintf(path, sizeof(path), "%s/" PREFIX "%s", get_ipc_dir(), whom);

	h = CreateFileA(path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
			0, NULL);
	if(h == INVALID_HANDLE_VALUE)
	{
		return 1;
	}

	size = len;
	if(WriteFile(h, &size, sizeof(size), &nwritten, NULL) == FALSE ||
			nwritten != sizeof(size) ||
			WriteFile(h, what, len, &nwritten, NULL) == FALSE || nwritten != len)
	{
		CloseHandle(h);
		return 1;
	}

	CloseHandle(h);
	return 0;
#endif
}

/* Automatically picks target instance to send data to.  Returns newly allocated
 * string or NULL on error (no other instances or memory allocation failure). */
static char *
get_the_only_target(const ipc_t *ipc)
{
	int len;
	char *name;
	char **list = list_servers(ipc, &len);

	if(len == 0)
	{
		return NULL;
	}

	name = list[0];
	list[0] = NULL;
	free_string_array(list, len);

	return name;
}

char **
ipc_list(int *len)
{
	return list_servers(NULL, len);
}

/* Retrieves list with names of servers available for IPC excluding the one
 * specified by the ipc parameter, which can be NULL.  Returns the list which is
 * of the *len length. */
static char **
list_servers(const ipc_t *ipc, int *len)
{
	list_data_t data = { .ipc_dir = get_ipc_dir(), .ipc = ipc };

#ifndef WIN32_PIPE_READ
	if(enum_dir_content(data.ipc_dir, &add_to_list, &data) != 0)
	{
		*len = 0;
		return NULL;
	}
#else
	{
		char find_pat[PATH_MAX + 1];
		HANDLE hfind;
		WIN32_FIND_DATAA ffd;

		snprintf(find_pat, sizeof(find_pat), "%s/*", data.ipc_dir);
		hfind = FindFirstFileA(find_pat, &ffd);

		if(hfind == INVALID_HANDLE_VALUE)
		{
			*len = 0;
			return NULL;
		}

		do
		{
			if(add_to_list(ffd.cFileName, &ffd, &data) != 0)
			{
				break;
			}
		}
		while(FindNextFileA(hfind, &ffd));
		FindClose(hfind);
	}
#endif

	safe_qsort(data.lst, data.len, sizeof(*data.lst), &strsorter);

	*len = data.len;
	return data.lst;
}

/* Analyzes pipe and adds it to the list of pipes.  Returns zero on success or
 * non-zero on error. */
static int
add_to_list(const char name[], const void *data, void *param)
{
	list_data_t *const list_data = param;
	const ipc_t *ipc = list_data->ipc;

	if(!starts_with_lit(name, PREFIX))
	{
		return 0;
	}

	/* Skip ourself. */
	if(ipc != NULL &&
			stroscmp(name, get_last_path_component(ipc->pipe_path)) == 0)
	{
		return 0;
	}

	/* On Windows it's guaranteed to be a valid pipe. */
#ifndef WIN32_PIPE_READ
	{
		char path[PATH_MAX + 1];
		struct stat statbuf;
		snprintf(path, sizeof(path), "%s/%s", list_data->ipc_dir, name);
		if(stat(path, &statbuf) != 0 || !S_ISFIFO(statbuf.st_mode) ||
				!pipe_is_in_use(path) || os_access(path, R_OK) != 0)
		{
			return 0;
		}
	}
#endif

	list_data->len = add_to_string_array(&list_data->lst, list_data->len,
			name + strlen(PREFIX));
	return 0;
}

/* Retrieves directory where FIFO objects are created.  Returns the path. */
static const char *
get_ipc_dir(void)
{
#ifndef WIN32_PIPE_READ
	return get_tmpdir();
#else
	return "//./pipe";
#endif
}

#ifndef WIN32_PIPE_READ

/* Tries to open a pipe to check whether it has any readers or it's
 * abandoned.  Returns non-zero if somebody is reading from the pipe and zero
 * otherwise. */
static int
pipe_is_in_use(const char path[])
{
	const int fd = open(path, O_WRONLY | O_NONBLOCK);
	if(fd != -1 || errno != ENXIO)
	{
		if(fd != -1)
		{
			close(fd);
		}
		return 1;
	}
	return 0;
}

#endif

#else

#include <stddef.h> /* NULL */

int
ipc_enabled(void)
{
	return 0;
}

char **
ipc_list(int *len)
{
	*len = 0;
	return NULL;
}

ipc_t *
ipc_init(const char name[], ipc_args_cb args_cb, ipc_eval_cb eval_cb)
{
	return NULL;
}

void
ipc_free(ipc_t *ipc)
{
}

const char *
ipc_get_name(const ipc_t *ipc)
{
	return "";
}

int
ipc_check(ipc_t *ipc)
{
	return 0;
}

int
ipc_send(ipc_t *ipc, const char whom[], char *data[])
{
	return 1;
}

char *
ipc_eval(ipc_t *ipc, const char whom[], const char expr[])
{
	return NULL;
}

#endif

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
