Unify client/server argument

Add documentation as well for client/server.
Get rid of name hash for clients, just pass a cookie back and forth.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/README b/README
index 0eac41f..26b5909 100644
--- a/README
+++ b/README
@@ -133,23 +133,25 @@
 	--debug			Enable some debugging options (see below)
 	--output		Write output to file
 	--timeout		Runtime in seconds
-	--latency-log	Generate per-job latency logs
-	--bandwidth-log	Generate per-job bandwidth logs
+	--latency-log		Generate per-job latency logs
+	--bandwidth-log		Generate per-job bandwidth logs
 	--minimal		Minimal (terse) output
 	--version		Print version info and exit
 	--terse-version=type	Terse version output format
 	--help			Print this page
-	--cmdhelp=cmd	Print command help, "all" for all of them
+	--cmdhelp=cmd		Print command help, "all" for all of them
 	--showcmd		Turn a job file into command line options
 	--readonly		Turn on safety read-only checks, preventing
-					writes
+				writes
 	--eta=when		When ETA estimate should be printed
-					May be "always", "never" or "auto"
-	--section=name	Only run specified section in job file. Multiple
-				sections can be specified.
+				May be "always", "never" or "auto"
+	--section=name		Only run specified section in job file.
+				Multiple sections can be specified.
 	--alloc-size=kb	Set smalloc pool to this size in kb (def 1024)
 	--warnings-fatal Fio parser warnings are fatal
 	--max-jobs		Maximum number of threads/processes to support
+	--server=args		Start backend server. See Client/Server section.
+	--client=host		Connect to specified backend.
 
 
 Any parameters following the options will be assumed to be job files,
@@ -315,6 +317,59 @@
 
 
 
+Client/server
+------------
+
+Normally you would run fio as a stand-alone application on the machine
+where the IO workload should be generated. However, it is also possible to
+run the frontend and backend of fio separately. This makes it possible to
+have a fio server running on the machine(s) where the IO workload should
+be running, while controlling it from another machine.
+
+To start the server, you would do:
+
+fio --server=args
+
+on that machine, where args defines what fio listens to. The arguments
+are of the form 'type:hostname or IP:port'. 'type' is either 'ip' for
+TCP/IP, or 'sock' for a local unix domain socket. 'hostname' is either
+a hostname or IP address, and 'port' is the port to listen to (only valid
+for TCP/IP, not a local socket). Some examples:
+
+1) fio --server
+
+   Start a fio server, listening on all interfaces on the default port (8765).
+
+2) fio --server=ip:hostname:4444
+
+   Start a fio server, listening on IP belonging to hostname and on port 4444.
+
+3) fio --server=:4444
+
+   Start a fio server, listening on all interfaces on port 4444.
+
+4) fio --server=1.2.3.4
+
+   Start a fio server, listening on IP 1.2.3.4 on the default port.
+
+5) fio --server=sock:/tmp/fio.sock
+
+   Start a fio server, listening on the local socket /tmp/fio.sock.
+
+When a server is running, you can connect to it from a client. The client
+is run with:
+
+fio --local-args --client=server --remote-args <job file(s)>
+
+where --local-args are arguments that are local to the client where it is
+running, 'server' is the connect string, and --remote-args and <job file(s)>
+are sent to the server. The 'server' string follows the same format as it
+does on the server side, to allow IP/hostname/socket and port strings.
+You can connect to multiple clients as well, to do that you could run:
+
+fio --client=server2 --client=server2 <job file(s)>
+
+
 Platforms
 ---------
 
diff --git a/client.c b/client.c
index 10d41e5..ef23c3e 100644
--- a/client.c
+++ b/client.c
@@ -22,11 +22,11 @@
 
 struct fio_client {
 	struct flist_head list;
-	struct flist_head fd_hash_list;
-	struct flist_head name_hash_list;
+	struct flist_head hash_list;
 	struct sockaddr_in addr;
 	struct sockaddr_un addr_un;
 	char *hostname;
+	int port;
 	int fd;
 
 	int state;
@@ -50,47 +50,30 @@
 #define FIO_CLIENT_HASH_BITS	7
 #define FIO_CLIENT_HASH_SZ	(1 << FIO_CLIENT_HASH_BITS)
 #define FIO_CLIENT_HASH_MASK	(FIO_CLIENT_HASH_SZ - 1)
-static struct flist_head client_fd_hash[FIO_CLIENT_HASH_SZ];
-static struct flist_head client_name_hash[FIO_CLIENT_HASH_SZ];
+static struct flist_head client_hash[FIO_CLIENT_HASH_SZ];
 
 static int handle_client(struct fio_client *client);
 
-static void fio_client_add_fd_hash(struct fio_client *client)
+static void fio_client_add_hash(struct fio_client *client)
 {
 	int bucket = hash_long(client->fd, FIO_CLIENT_HASH_BITS);
 
 	bucket &= FIO_CLIENT_HASH_MASK;
-	flist_add(&client->fd_hash_list, &client_fd_hash[bucket]);
+	flist_add(&client->hash_list, &client_hash[bucket]);
 }
 
-static void fio_client_remove_fd_hash(struct fio_client *client)
+static void fio_client_remove_hash(struct fio_client *client)
 {
-	if (!flist_empty(&client->fd_hash_list))
-		flist_del_init(&client->fd_hash_list);
-}
-
-static void fio_client_add_name_hash(struct fio_client *client)
-{
-	int bucket = jhash(client->hostname, strlen(client->hostname), 0);
-
-	bucket &= FIO_CLIENT_HASH_MASK;
-	flist_add(&client->name_hash_list, &client_name_hash[bucket]);
-}
-
-static void fio_client_remove_name_hash(struct fio_client *client)
-{
-	if (!flist_empty(&client->name_hash_list))
-		flist_del_init(&client->name_hash_list);
+	if (!flist_empty(&client->hash_list))
+		flist_del_init(&client->hash_list);
 }
 
 static void fio_init fio_client_hash_init(void)
 {
 	int i;
 
-	for (i = 0; i < FIO_CLIENT_HASH_SZ; i++) {
-		INIT_FLIST_HEAD(&client_fd_hash[i]);
-		INIT_FLIST_HEAD(&client_name_hash[i]);
-	}
+	for (i = 0; i < FIO_CLIENT_HASH_SZ; i++)
+		INIT_FLIST_HEAD(&client_hash[i]);
 }
 
 static struct fio_client *find_client_by_fd(int fd)
@@ -99,8 +82,8 @@
 	struct fio_client *client;
 	struct flist_head *entry;
 
-	flist_for_each(entry, &client_fd_hash[bucket]) {
-		client = flist_entry(entry, struct fio_client, fd_hash_list);
+	flist_for_each(entry, &client_hash[bucket]) {
+		client = flist_entry(entry, struct fio_client, hash_list);
 
 		if (client->fd == fd)
 			return client;
@@ -109,29 +92,12 @@
 	return NULL;
 }
 
-static struct fio_client *find_client_by_name(const char *name)
-{
-	int bucket = jhash(name, strlen(name), 0) & FIO_CLIENT_HASH_BITS;
-	struct fio_client *client;
-	struct flist_head *entry;
-
-	flist_for_each(entry, &client_name_hash[bucket]) {
-		client = flist_entry(entry, struct fio_client, name_hash_list);
-
-		if (!strcmp(name, client->hostname))
-			return client;
-	}
-
-	return NULL;
-}
-
 static void remove_client(struct fio_client *client)
 {
 	dprint(FD_NET, "client: removed <%s>\n", client->hostname);
 	flist_del(&client->list);
 
-	fio_client_remove_fd_hash(client);
-	fio_client_remove_name_hash(client);
+	fio_client_remove_hash(client);
 
 	free(client->hostname);
 	if (client->argv)
@@ -159,48 +125,42 @@
 	return 0;
 }
 
-int fio_client_add_cmd_option(const char *hostname, const char *opt)
+int fio_client_add_cmd_option(void *cookie, const char *opt)
 {
-	struct fio_client *client;
+	struct fio_client *client = cookie;
 
-	if (!hostname || !opt)
+	if (!client || !opt)
 		return 0;
 
-	client = find_client_by_name(hostname);
-	if (!client) {
-		log_err("fio: unknown client %s\n", hostname);
-		return 1;
-	}
-
 	return __fio_client_add_cmd_option(client, opt);
 }
 
-void fio_client_add(const char *hostname)
+int fio_client_add(const char *hostname, void **cookie)
 {
 	struct fio_client *client;
 
-	dprint(FD_NET, "client: added  <%s>\n", hostname);
 	client = malloc(sizeof(*client));
 	memset(client, 0, sizeof(*client));
 
 	INIT_FLIST_HEAD(&client->list);
-	INIT_FLIST_HEAD(&client->fd_hash_list);
-	INIT_FLIST_HEAD(&client->name_hash_list);
+	INIT_FLIST_HEAD(&client->hash_list);
 
-	if (!strncmp(hostname, "sock:", 5)) {
-		client->hostname = strdup(hostname + 5);
-		client->is_sock = 1;
-	} else
-		client->hostname = strdup(hostname);
+	if (fio_server_parse_string(hostname, &client->hostname,
+					&client->is_sock, &client->port,
+					&client->addr.sin_addr))
+		return -1;
+
+	printf("%s %d %d\n", client->hostname, client->is_sock, client->port);
 
 	client->fd = -1;
 
-	fio_client_add_name_hash(client);
-
 	__fio_client_add_cmd_option(client, "fio");
 
 	flist_add(&client->list, &client_list);
 	nr_clients++;
+	dprint(FD_NET, "client: added <%s>\n", client->hostname);
+	*cookie = client;
+	return 0;
 }
 
 static int fio_client_connect_ip(struct fio_client *client)
@@ -208,21 +168,7 @@
 	int fd;
 
 	client->addr.sin_family = AF_INET;
-	client->addr.sin_port = htons(fio_net_port);
-
-	if (inet_aton(client->hostname, &client->addr.sin_addr) != 1) {
-		struct hostent *hent;
-
-		hent = gethostbyname(client->hostname);
-		if (!hent) {
-			log_err("fio: gethostbyname: %s\n", strerror(errno));
-			log_err("fio: failed looking up hostname %s\n",
-					client->hostname);
-			return -1;
-		}
-
-		memcpy(&client->addr.sin_addr, hent->h_addr, 4);
-	}
+	client->addr.sin_port = htons(client->port);
 
 	fd = socket(AF_INET, SOCK_STREAM, 0);
 	if (fd < 0) {
@@ -283,7 +229,7 @@
 		return 1;
 
 	client->fd = fd;
-	fio_client_add_fd_hash(client);
+	fio_client_add_hash(client);
 	client->state = Client_connected;
 	return 0;
 }
@@ -304,7 +250,7 @@
 
 static void sig_int(int sig)
 {
-	dprint(FD_NET, "client: got sign %d\n", sig);
+	dprint(FD_NET, "client: got signal %d\n", sig);
 	fio_clients_terminate();
 }
 
diff --git a/init.c b/init.c
index 486b743..849bcf9 100644
--- a/init.c
+++ b/init.c
@@ -196,11 +196,6 @@
 		.val		= 'D',
 	},
 	{
-		.name		= (char *) "net-port",
-		.has_arg	= required_argument,
-		.val		= 'P',
-	},
-	{
 		.name		= (char *) "client",
 		.has_arg	= required_argument,
 		.val		= 'C',
@@ -1123,9 +1118,8 @@
 		" (def 1024)\n");
 	printf("\t--warnings-fatal Fio parser warnings are fatal\n");
 	printf("\t--max-jobs\tMaximum number of threads/processes to support\n");
-	printf("\t--server\tStart a backend fio server\n");
+	printf("\t--server=args\tStart a backend fio server\n");
 	printf("\t--client=hostname Talk to remove backend fio server at hostname\n");
-	printf("\t--net-port=port\tUse specified port for client/server connection\n");
 	printf("\nFio was written by Jens Axboe <jens.axboe@oracle.com>");
 	printf("\n                   Jens Axboe <jaxboe@fusionio.com>\n");
 }
@@ -1247,7 +1241,7 @@
 	return 0;
 }
 
-int parse_cmd_client(char *client, char *opt)
+int parse_cmd_client(void *client, char *opt)
 {
 	return fio_client_add_cmd_option(client, opt);
 }
@@ -1258,7 +1252,7 @@
 	int c, ini_idx = 0, lidx, ret = 0, do_exit = 0, exit_val = 0;
 	char *ostr = cmd_optstr;
 	int daemonize_server = 0;
-	char *cur_client = NULL;
+	void *cur_client;
 	int backend = 0;
 
 	/*
@@ -1411,16 +1405,13 @@
 				break;
 			}
 			if (optarg)
-				fio_server_add_arg(optarg);
+				fio_server_set_arg(optarg);
 			is_backend = 1;
 			backend = 1;
 			break;
 		case 'D':
 			daemonize_server = 1;
 			break;
-		case 'P':
-			fio_net_port = atoi(optarg);
-			break;
 		case 'C':
 			if (is_backend) {
 				log_err("fio: can't be both client and server\n");
@@ -1428,8 +1419,12 @@
 				exit_val = 1;
 				break;
 			}
-			fio_client_add(optarg);
-			cur_client = optarg;
+			if (fio_client_add(optarg, &cur_client)) {
+				log_err("fio: failed adding client %s\n", optarg);
+				do_exit++;
+				exit_val = 1;
+				break;
+			}
 			break;
 		default:
 			do_exit++;
diff --git a/server.c b/server.c
index 553eb82..524c814 100644
--- a/server.c
+++ b/server.c
@@ -666,7 +666,6 @@
 #endif
 
 	saddr_in.sin_family = AF_INET;
-	saddr_in.sin_port = htons(fio_net_port);
 
 	if (bind(sk, (struct sockaddr *) &saddr_in, sizeof(saddr_in)) < 0) {
 		log_err("fio: bind: %s\n", strerror(errno));
@@ -711,6 +710,7 @@
 
 static int fio_init_server_connection(void)
 {
+	char bind_str[128];
 	int sk;
 
 	dprint(FD_NET, "starting server\n");
@@ -723,6 +723,13 @@
 	if (sk < 0)
 		return sk;
 
+	if (!bind_sock)
+		sprintf(bind_str, "%s:%u", inet_ntoa(saddr_in.sin_addr), fio_net_port);
+	else
+		strcpy(bind_str, bind_sock);
+
+	log_info("fio: server listening on %s\n", bind_str);
+
 	if (listen(sk, 1) < 0) {
 		log_err("fio: listen: %s\n", strerror(errno));
 		return -1;
@@ -731,6 +738,81 @@
 	return sk;
 }
 
+int fio_server_parse_string(const char *str, char **ptr, int *is_sock,
+			    int *port, struct in_addr *inp)
+{
+	*ptr = NULL;
+	*is_sock = 0;
+	*port = 0;
+
+	if (!strncmp(str, "sock:", 5)) {
+		*ptr = strdup(str + 5);
+		*is_sock = 1;
+	} else {
+		const char *host = str;
+		char *portp;
+		int lport = 0;
+
+		/*
+		 * Is it ip:<ip or host>:port
+		 */
+		if (!strncmp(host, "ip:", 3))
+			host += 3;
+		else if (host[0] == ':') {
+			/* String is :port */
+			host++;
+			lport = atoi(host);
+			if (!lport || lport > 65535) {
+				log_err("fio: bad server port %u\n", port);
+				return 1;
+			}
+			/* no hostname given, we are done */
+			*port = lport;
+			return 0;
+		}
+
+		/*
+		 * If no port seen yet, check if there's a last ':' at the end
+		 */
+		if (!lport) {
+			portp = strchr(host, ':');
+			if (portp) {
+				*portp = '\0';
+				portp++;
+				lport = atoi(portp);
+				if (!lport || lport > 65535) {
+					log_err("fio: bad server port %u\n", port);
+					return 1;
+				}
+			}
+		}
+
+		if (lport)
+			*port = lport;
+
+		*ptr = strdup(host);
+
+		if (inet_aton(host, inp) != 1) {
+			struct hostent *hent;
+
+			hent = gethostbyname(host);
+			if (!hent) {
+				printf("FAIL\n");
+				free(*ptr);
+				*ptr = NULL;
+				return 1;
+			}
+
+			memcpy(inp, hent->h_addr, 4);
+		}
+	}
+
+	if (*port == 0)
+		*port = fio_net_port;
+
+	return 0;
+}
+
 /*
  * Server arg should be one of:
  *
@@ -745,28 +827,16 @@
  */
 static int fio_handle_server_arg(void)
 {
+	int unused;
+
 	saddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+	saddr_in.sin_port = htons(fio_net_port);
 
 	if (!fio_server_arg)
 		return 0;
-	if (!strncmp(fio_server_arg, "sock:", 5)) {
-		bind_sock = fio_server_arg + 5;
-		return 0;
-	} else {
-		char *host = fio_server_arg;
 
-		if (!strncmp(host, "ip:", 3))
-			host += 3;
-
-		if (inet_aton(host, &saddr_in.sin_addr) != 1) {
-			struct hostent *hent;
-
-			hent = gethostbyname(host);
-			if (hent)
-				memcpy(&saddr_in.sin_addr, hent->h_addr, 4);
-		}
-		return 0;
-	}
+	return fio_server_parse_string(fio_server_arg, &bind_sock, &unused,
+					&fio_net_port, &saddr_in.sin_addr);
 }
 
 static int fio_server(void)
@@ -790,6 +860,8 @@
 		free(fio_server_arg);
 		fio_server_arg = NULL;
 	}
+	if (bind_sock)
+		free(bind_sock);
 
 	return ret;
 }
@@ -842,7 +914,7 @@
 	return fio_server();
 }
 
-void fio_server_add_arg(const char *arg)
+void fio_server_set_arg(const char *arg)
 {
 	fio_server_arg = strdup(arg);
 }
diff --git a/server.h b/server.h
index 0e35fde..6a6ac33 100644
--- a/server.h
+++ b/server.h
@@ -76,7 +76,8 @@
 extern int fio_server_log(const char *format, ...);
 extern int fio_net_send_cmd(int, uint16_t, const void *, off_t);
 extern int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t serial);
-extern void fio_server_add_arg(const char *);
+extern void fio_server_set_arg(const char *);
+extern int fio_server_parse_string(const char *, char **, int *, int *, struct in_addr *);
 
 struct thread_stat;
 struct group_run_stats;
@@ -88,8 +89,8 @@
 extern int fio_clients_connect(void);
 extern int fio_clients_send_ini(const char *);
 extern int fio_handle_clients(void);
-extern void fio_client_add(const char *);
-extern int fio_client_add_cmd_option(const char *, const char *);
+extern int fio_client_add(const char *, void **);
+extern int fio_client_add_cmd_option(void *, const char *);
 
 extern int fio_recv_data(int sk, void *p, unsigned int len);
 extern int fio_send_data(int sk, const void *p, unsigned int len);