[PATCH] Simple support for networked IO

Adds a new ioengine, net. Use with ioengine=net, it supports only strict
reading or writing (no mixed reads/writes) to/from a single host.
The filename given must contain the host and port to connect to (or
listen from).

Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
diff --git a/HOWTO b/HOWTO
index f4ebac9..38f5dce 100644
--- a/HOWTO
+++ b/HOWTO
@@ -203,7 +203,9 @@
 filename=str	Fio normally makes up a filename based on the job name,
 		thread number, and file number. If you want to share
 		files between threads in a job or several jobs, specify
-		a filename for each of them to override the default.
+		a filename for each of them to override the default. If
+		the ioengine used is 'net', the filename is the host and
+		port to connect to in the format of =host:port.
 
 rw=str		Type of io pattern. Accepted values are:
 
@@ -278,6 +280,12 @@
 				to. This is mainly used to exercise fio
 				itself and for debugging/testing purposes.
 
+			net	Transfer over the network to given host:port.
+				'filename' must be set appropriately to
+				filename=host:port regardless of send
+				or receive, if the latter only the port
+				argument is used.
+
 iodepth=int	This defines how many io units to keep in flight against
 		the file. The default is 1 for each file defined in this
 		job, can be overridden with a larger value for higher
diff --git a/Makefile b/Makefile
index 3b919d5..fe9a8cd 100644
--- a/Makefile
+++ b/Makefile
@@ -15,6 +15,7 @@
 OBJS += engines/splice.o
 OBJS += engines/sync.o
 OBJS += engines/null.o
+OBJS += engines/net.o
 
 INSTALL = install
 prefix = /usr/local
diff --git a/Makefile.FreeBSD b/Makefile.FreeBSD
index b862820..5c85a0c 100644
--- a/Makefile.FreeBSD
+++ b/Makefile.FreeBSD
@@ -10,6 +10,7 @@
 OBJS += engines/posixaio.o
 OBJS += engines/sync.o
 OBJS += engines/null.o
+OBJS += engines/net.o
 
 all: depend $(PROGS) $(SCRIPTS)
 
diff --git a/Makefile.solaris b/Makefile.solaris
index 8d8bec3..4373abf 100644
--- a/Makefile.solaris
+++ b/Makefile.solaris
@@ -10,6 +10,7 @@
 OBJS += engines/posixaio.o
 OBJS += engines/sync.o
 OBJS += engines/null.o
+OBJS += engines/net.o
 
 all: depend $(PROGS) $(SCRIPTS)
 
diff --git a/README b/README
index be32af4..f7d7457 100644
--- a/README
+++ b/README
@@ -101,11 +101,11 @@
 	ioengine=x	'x' may be: aio/libaio/linuxaio for Linux aio,
 			posixaio for POSIX aio, sync for regular read/write io,
 			mmap for mmap'ed io, splice for using splice/vmsplice,
-			or sgio for direct SG_IO io. The latter only works on
-			Linux on SCSI (or SCSI-like devices, such as
-			usb-storage or sata/libata driven) devices. Fio also
-			has a null io engine, which is mainly used for testing
-			fio itself.
+			sgio for direct SG_IO io, or net for network io. sgio
+			only works on Linux on SCSI (or SCSI-like devices,
+			such as usb-storage or sata/libata driven) devices.
+			Fio also has a null io engine, which is mainly used
+			for testing fio itself.
 	iodepth=x	For async io, allow 'x' ios in flight
 	overwrite=x	If 'x', layout a write file first.
 	nrfiles=x	Spread io load over 'x' number of files per job,
diff --git a/engines/net.c b/engines/net.c
new file mode 100644
index 0000000..8ef7811
--- /dev/null
+++ b/engines/net.c
@@ -0,0 +1,273 @@
+/*
+ * Transfer data over the net. Pretty basic setup, will only support
+ * 1 file per thread/job.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "../fio.h"
+#include "../os.h"
+
+struct net_data {
+	int send_to_net;
+	struct io_u *last_io_u;
+};
+
+static int fio_netio_getevents(struct thread_data *td, int fio_unused min,
+				int max, struct timespec fio_unused *t)
+{
+	assert(max <= 1);
+
+	/*
+	 * we can only have one finished io_u for sync io, since the depth
+	 * is always 1
+	 */
+	if (list_empty(&td->io_u_busylist))
+		return 0;
+
+	return 1;
+}
+
+static struct io_u *fio_netio_event(struct thread_data *td, int event)
+{
+	struct net_data *nd = td->io_ops->data;
+
+	assert(event == 0);
+
+	return nd->last_io_u;
+}
+
+static int fio_netio_prep(struct thread_data *td, struct io_u *io_u)
+{
+	struct net_data *nd = td->io_ops->data;
+	struct fio_file *f = io_u->file;
+
+	if (nd->send_to_net) {
+		if (io_u->ddir == DDIR_READ) {
+			td_verror(td, EINVAL);
+			return 1;
+		}
+	} else {
+		if (io_u->ddir == DDIR_WRITE) {
+			td_verror(td, EINVAL);
+			return 1;
+		}
+	}
+
+	if (io_u->ddir == DDIR_SYNC)
+		return 0;
+	if (io_u->offset == f->last_completed_pos)
+		return 0;
+
+	td_verror(td, EINVAL);
+	return 1;
+}
+
+static int fio_netio_queue(struct thread_data *td, struct io_u *io_u)
+{
+	struct net_data *nd = td->io_ops->data;
+	struct fio_file *f = io_u->file;
+	unsigned int ret = 0;
+
+	if (io_u->ddir == DDIR_WRITE)
+		ret = write(f->fd, io_u->buf, io_u->buflen);
+	else if (io_u->ddir == DDIR_READ)
+		ret = read(f->fd, io_u->buf, io_u->buflen);
+
+	if (ret != io_u->buflen) {
+		if (ret > 0) {
+			io_u->resid = io_u->buflen - ret;
+			io_u->error = EIO;
+		} else
+			io_u->error = errno;
+	}
+
+	if (!io_u->error)
+		nd->last_io_u = io_u;
+
+	return io_u->error;
+}
+
+static int fio_netio_setup_connect(struct thread_data *td, const char *host,
+				   const char *port)
+{
+	struct sockaddr_in addr;
+	struct fio_file *f;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(atoi(port));
+
+	if (inet_aton(host, &addr.sin_addr) != 1) {
+		struct hostent *hent = gethostbyname(host);
+
+		if (!hent) {
+			td_vmsg(td, errno, "gethostbyname");
+			return 1;
+		}
+
+		memcpy(&addr.sin_addr, hent->h_addr, 4);
+	}
+
+	f = &td->files[0];
+
+	f->fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (f->fd < 0) {
+		td_vmsg(td, errno, "socket");
+		return 1;
+	}
+
+	if (connect(f->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		td_vmsg(td, errno, "connect");
+		return 1;
+	}
+
+	return 0;
+
+}
+
+static int fio_netio_setup_listen(struct thread_data *td, const char *port)
+{
+	struct sockaddr_in addr;
+	socklen_t socklen;
+	int fd, opt;
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (fd < 0) {
+		td_vmsg(td, errno, "socket");
+		return 1;
+	}
+
+	opt = 1;
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+		td_vmsg(td, errno, "setsockopt");
+		return 1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl(INADDR_ANY);
+	addr.sin_port = htons(atoi(port));
+
+	if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		td_vmsg(td, errno, "bind");
+		return 1;
+	}
+	if (listen(fd, 1) < 0) {
+		td_vmsg(td, errno, "listen");
+		return 1;
+	}
+
+	socklen = sizeof(addr);
+	td->files[0].fd = accept(fd, (struct sockaddr *) &addr, &socklen);
+	if (td->files[0].fd < 0) {
+		td_vmsg(td, errno, "accept");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int fio_netio_setup(struct thread_data *td)
+{
+	char host[64], port[64], buf[128];
+	struct net_data *nd;
+	char *sep;
+	int ret;
+
+	/*
+	 * work around for late init call
+	 */
+	if (td->io_ops->init(td))
+		return 1;
+
+	nd = td->io_ops->data;
+
+	if (td->iomix) {
+		log_err("fio: network connections must be read OR write\n");
+		return 1;
+	}
+	if (td->nr_files > 1) {
+		log_err("fio: only one file supported for network\n");
+		return 1;
+	}
+
+	strcpy(buf, td->filename);
+
+	sep = strchr(buf, ':');
+	if (!sep) {
+		log_err("fio: bad network host:port <<%s>>\n", td->filename);
+		return 1;
+	}
+
+	*sep = '\0';
+	sep++;
+	strcpy(host, buf);
+	strcpy(port, sep);
+
+	if (td->ddir == READ) {
+		nd->send_to_net = 0;
+		ret = fio_netio_setup_listen(td, port);
+	} else {
+		nd->send_to_net = 1;
+		ret = fio_netio_setup_connect(td, host, port);
+	}
+
+	if (!ret) {
+		td->io_size = td->total_file_size;
+		td->total_io_size = td->io_size;
+		td->files[0].real_file_size = td->io_size;
+	}
+
+	return ret;
+}
+
+static void fio_netio_cleanup(struct thread_data *td)
+{
+	if (td->io_ops->data) {
+		free(td->io_ops->data);
+		td->io_ops->data = NULL;
+	}
+}
+
+static int fio_netio_init(struct thread_data *td)
+{
+	struct net_data *nd;
+
+	if (td->io_ops->data)
+		return 0;
+
+	nd  = malloc(sizeof(*nd));
+	nd->last_io_u = NULL;
+	td->io_ops->data = nd;
+	return 0;
+}
+
+static struct ioengine_ops ioengine = {
+	.name		= "net",
+	.version	= FIO_IOOPS_VERSION,
+	.init		= fio_netio_init,
+	.prep		= fio_netio_prep,
+	.queue		= fio_netio_queue,
+	.getevents	= fio_netio_getevents,
+	.event		= fio_netio_event,
+	.cleanup	= fio_netio_cleanup,
+	.setup		= fio_netio_setup,
+	.flags		= FIO_SYNCIO | FIO_NETIO,
+};
+
+static void fio_init fio_netio_register(void)
+{
+	register_ioengine(&ioengine);
+}
+
+static void fio_exit fio_netio_unregister(void)
+{
+	unregister_ioengine(&ioengine);
+}
diff --git a/filesetup.c b/filesetup.c
index 2d8193e..610981b 100644
--- a/filesetup.c
+++ b/filesetup.c
@@ -330,6 +330,8 @@
 {
 	int flags = 0;
 
+	if (td->io_ops->flags & FIO_NETIO)
+		return 0;
 	if (td->odirect)
 		flags |= OS_O_DIRECT;
 	if (td->sync_io)
diff --git a/fio.c b/fio.c
index cc54c83..bce13a7 100644
--- a/fio.c
+++ b/fio.c
@@ -841,7 +841,6 @@
 
 	signal(SIGINT, sig_handler);
 	signal(SIGALRM, sig_handler);
-	signal(SIGSEGV, sig_handler);
 
 	todo = thread_number;
 	nr_running = 0;
diff --git a/fio.h b/fio.h
index c6fb6a1..d635e99 100644
--- a/fio.h
+++ b/fio.h
@@ -143,6 +143,7 @@
 	FIO_CPUIO	= 1 << 1,
 	FIO_MMAPIO	= 1 << 2,
 	FIO_RAWIO	= 1 << 3,
+	FIO_NETIO	= 1 << 4,
 };
 
 struct fio_file {
diff --git a/init.c b/init.c
index 32e22db..7f2747a 100644
--- a/init.c
+++ b/init.c
@@ -81,7 +81,7 @@
 		.help	= "IO engine to use",
 		.def	= "sync",
 		.posval	= { "sync", "libaio", "posixaio", "mmap", "splice",
-				"sg", "null", },
+				"sg", "null", "net", },
 	},
 	{
 		.name	= "iodepth",
diff --git a/parse.c b/parse.c
index 29e2ff1..cab6ca7 100644
--- a/parse.c
+++ b/parse.c
@@ -312,7 +312,7 @@
 	 * Do this before parsing the first round, to check if we should
 	 * copy set 1 options to set 2.
 	 */
-	if (ptr) {
+	if (ptr && (o->type != FIO_OPT_STR_STORE)) {
 		ptr2 = strchr(ptr, ',');
 		if (!ptr2)
 			ptr2 = strchr(ptr, ':');
diff --git a/stat.c b/stat.c
index 8ea1b8a..a98539f 100644
--- a/stat.c
+++ b/stat.c
@@ -205,7 +205,7 @@
 	dev_t dev;
 	char *p;
 
-	if (!td->do_disk_util)
+	if (!td->do_disk_util || (td->io_ops->flags & FIO_NETIO))
 		return;
 
 	/*