+static int server_fd = -1;
+static char *fio_server_arg;
+static char *bind_sock;
+static struct sockaddr_in saddr_in;
+
+int fio_send_data(int sk, const void *p, unsigned int len)
+{
+ assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_PDU);
+
+ do {
+ int ret = send(sk, p, len, 0);
+
+ if (ret > 0) {
+ len -= ret;
+ if (!len)
+ break;
+ p += ret;
+ continue;
+ } else if (!ret)
+ break;
+ else if (errno == EAGAIN || errno == EINTR)
+ continue;
+ } while (!exit_backend);
+
+ if (!len)
+ return 0;
+
+ return 1;
+}
+
+int fio_recv_data(int sk, void *p, unsigned int len)
+{
+ do {
+ int ret = recv(sk, p, len, MSG_WAITALL);
+
+ if (ret > 0) {
+ len -= ret;
+ if (!len)
+ break;
+ p += ret;
+ continue;
+ } else if (!ret)
+ break;
+ else if (errno == EAGAIN || errno == EINTR)
+ continue;
+ } while (!exit_backend);
+
+ if (!len)
+ return 0;
+
+ return -1;
+}
+
+static int verify_convert_cmd(struct fio_net_cmd *cmd)
+{
+ uint16_t crc;
+
+ cmd->cmd_crc16 = le16_to_cpu(cmd->cmd_crc16);
+ cmd->pdu_crc16 = le16_to_cpu(cmd->pdu_crc16);
+
+ crc = crc16(cmd, FIO_NET_CMD_CRC_SZ);
+ if (crc != cmd->cmd_crc16) {
+ log_err("fio: server bad crc on command (got %x, wanted %x)\n",
+ cmd->cmd_crc16, crc);
+ return 1;
+ }
+
+ cmd->version = le16_to_cpu(cmd->version);
+ cmd->opcode = le16_to_cpu(cmd->opcode);
+ cmd->flags = le32_to_cpu(cmd->flags);
+ cmd->serial = le64_to_cpu(cmd->serial);
+ cmd->pdu_len = le32_to_cpu(cmd->pdu_len);
+
+ switch (cmd->version) {
+ case FIO_SERVER_VER:
+ break;
+ default:
+ log_err("fio: bad server cmd version %d\n", cmd->version);
+ return 1;
+ }
+
+ if (cmd->pdu_len > FIO_SERVER_MAX_PDU) {
+ log_err("fio: command payload too large: %u\n", cmd->pdu_len);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Read (and defragment, if necessary) incoming commands
+ */
+struct fio_net_cmd *fio_net_recv_cmd(int sk)
+{
+ struct fio_net_cmd cmd, *cmdret = NULL;
+ size_t cmd_size = 0, pdu_offset = 0;
+ uint16_t crc;
+ int ret, first = 1;
+ void *pdu = NULL;
+
+ do {
+ ret = fio_recv_data(sk, &cmd, sizeof(cmd));
+ if (ret)
+ break;
+
+ /* We have a command, verify it and swap if need be */
+ ret = verify_convert_cmd(&cmd);
+ if (ret)
+ break;
+
+ if (first) {
+ /* if this is text, add room for \0 at the end */
+ cmd_size = sizeof(cmd) + cmd.pdu_len + 1;
+ assert(!cmdret);
+ } else
+ cmd_size += cmd.pdu_len;
+
+ cmdret = realloc(cmdret, cmd_size);
+
+ if (first)
+ memcpy(cmdret, &cmd, sizeof(cmd));
+ else
+ assert(cmdret->opcode == cmd.opcode);
+
+ if (!cmd.pdu_len)
+ break;
+
+ /* There's payload, get it */
+ pdu = (void *) cmdret->payload + pdu_offset;
+ ret = fio_recv_data(sk, pdu, cmd.pdu_len);
+ if (ret)
+ break;
+
+ /* Verify payload crc */
+ crc = crc16(pdu, cmd.pdu_len);
+ if (crc != cmd.pdu_crc16) {
+ log_err("fio: server bad crc on payload ");
+ log_err("(got %x, wanted %x)\n", cmd.pdu_crc16, crc);
+ ret = 1;
+ break;
+ }
+
+ pdu_offset += cmd.pdu_len;
+ if (!first)
+ cmdret->pdu_len += cmd.pdu_len;
+ first = 0;
+ } while (cmd.flags & FIO_NET_CMD_F_MORE);
+
+ if (ret) {
+ free(cmdret);
+ cmdret = NULL;
+ } else if (cmdret) {
+ /* zero-terminate text input */
+ if (cmdret->pdu_len && (cmdret->opcode == FIO_NET_CMD_TEXT ||
+ cmdret->opcode == FIO_NET_CMD_JOB)) {
+ char *buf = (char *) cmdret->payload;
+
+ buf[cmdret->pdu_len ] = '\0';
+ }
+ /* frag flag is internal */
+ cmdret->flags &= ~FIO_NET_CMD_F_MORE;
+ }
+
+ return cmdret;
+}
+
+void fio_net_cmd_crc(struct fio_net_cmd *cmd)
+{
+ uint32_t pdu_len;
+
+ cmd->cmd_crc16 = __cpu_to_le16(crc16(cmd, FIO_NET_CMD_CRC_SZ));
+
+ pdu_len = le32_to_cpu(cmd->pdu_len);
+ if (pdu_len)
+ cmd->pdu_crc16 = __cpu_to_le16(crc16(cmd->payload, pdu_len));
+}
+
+int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size)
+{
+ struct fio_net_cmd *cmd;
+ size_t this_len;
+ int ret;
+
+ do {
+ this_len = size;
+ if (this_len > FIO_SERVER_MAX_PDU)
+ this_len = FIO_SERVER_MAX_PDU;
+
+ cmd = malloc(sizeof(*cmd) + this_len);
+
+ fio_init_net_cmd(cmd, opcode, buf, this_len);
+
+ if (this_len < size)
+ cmd->flags = __cpu_to_le32(FIO_NET_CMD_F_MORE);
+
+ fio_net_cmd_crc(cmd);
+
+ ret = fio_send_data(fd, cmd, sizeof(*cmd) + this_len);
+ free(cmd);
+ size -= this_len;
+ buf += this_len;
+ } while (!ret && size);
+
+ return ret;
+}
+
+int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t serial)
+{
+ struct fio_net_cmd cmd;
+
+ fio_init_net_cmd(&cmd, opcode, NULL, 0);
+ fio_net_cmd_crc(&cmd);
+
+ return fio_send_data(sk, &cmd, sizeof(cmd));
+}
+
+static int fio_server_send_quit_cmd(void)
+{
+ dprint(FD_NET, "server: sending quit\n");
+ return fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_QUIT, 0);
+}
+
+static int handle_job_cmd(struct fio_net_cmd *cmd)
+{
+ char *buf = (char *) cmd->payload;
+ int ret;
+
+ if (parse_jobs_ini(buf, 1, 0)) {
+ fio_server_send_quit_cmd();
+ return -1;
+ }
+
+ fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_START, 0);
+
+ ret = exec_run();
+ fio_server_send_quit_cmd();
+ reset_fio_state();
+ return ret;
+}
+
+static int handle_jobline_cmd(struct fio_net_cmd *cmd)
+{
+ void *pdu = cmd->payload;
+ struct cmd_single_line_pdu *cslp;
+ struct cmd_line_pdu *clp;
+ unsigned long offset;
+ char **argv;
+ int ret, i;
+
+ clp = pdu;
+ clp->lines = le16_to_cpu(clp->lines);
+ argv = malloc(clp->lines * sizeof(char *));
+ offset = sizeof(*clp);
+
+ dprint(FD_NET, "server: %d command line args\n", clp->lines);
+
+ for (i = 0; i < clp->lines; i++) {
+ cslp = pdu + offset;
+ argv[i] = (char *) cslp->text;
+
+ offset += sizeof(*cslp) + le16_to_cpu(cslp->len);
+ dprint(FD_NET, "server: %d: %s\n", i, argv[i]);
+ }
+
+ if (parse_cmd_line(clp->lines, argv)) {
+ fio_server_send_quit_cmd();
+ free(argv);
+ return -1;
+ }
+
+ free(argv);
+
+ fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_START, 0);
+
+ ret = exec_run();
+ fio_server_send_quit_cmd();
+ reset_fio_state();
+ return ret;
+}
+
+static int handle_probe_cmd(struct fio_net_cmd *cmd)
+{
+ struct cmd_probe_pdu probe;
+
+ memset(&probe, 0, sizeof(probe));
+ gethostname((char *) probe.hostname, sizeof(probe.hostname));
+#ifdef FIO_BIG_ENDIAN
+ probe.bigendian = 1;
+#endif
+ probe.fio_major = FIO_MAJOR;
+ probe.fio_minor = FIO_MINOR;
+ probe.fio_patch = FIO_PATCH;
+
+ probe.os = FIO_OS;
+ probe.arch = FIO_ARCH;
+
+ return fio_net_send_cmd(server_fd, FIO_NET_CMD_PROBE, &probe, sizeof(probe));
+}
+
+static int handle_command(struct fio_net_cmd *cmd)
+{
+ int ret;
+
+ dprint(FD_NET, "server: got opcode %d\n", cmd->opcode);
+
+ switch (cmd->opcode) {
+ case FIO_NET_CMD_QUIT:
+ fio_terminate_threads(TERMINATE_ALL);
+ return -1;
+ case FIO_NET_CMD_EXIT:
+ exit_backend = 1;
+ return -1;
+ case FIO_NET_CMD_JOB:
+ ret = handle_job_cmd(cmd);
+ break;
+ case FIO_NET_CMD_JOBLINE:
+ ret = handle_jobline_cmd(cmd);
+ break;
+ case FIO_NET_CMD_PROBE:
+ ret = handle_probe_cmd(cmd);
+ break;
+ default:
+ log_err("fio: unknown opcode: %d\n", cmd->opcode);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int handle_connection(int sk, int block)
+{
+ struct fio_net_cmd *cmd = NULL;
+ int ret = 0;
+
+ /* read forever */
+ while (!exit_backend) {
+ struct pollfd pfd = {
+ .fd = sk,
+ .events = POLLIN,
+ };
+
+ ret = 0;
+ do {
+ ret = poll(&pfd, 1, 100);
+ if (ret < 0) {
+ if (errno == EINTR)
+ break;
+ log_err("fio: poll: %s\n", strerror(errno));
+ break;
+ } else if (!ret) {
+ if (!block)
+ return 0;
+ continue;
+ }
+
+ if (pfd.revents & POLLIN)
+ break;
+ if (pfd.revents & (POLLERR|POLLHUP)) {
+ ret = 1;
+ break;
+ }
+ } while (!exit_backend);
+
+ if (ret < 0)
+ break;
+
+ cmd = fio_net_recv_cmd(sk);
+ if (!cmd) {
+ ret = -1;
+ break;
+ }
+
+ ret = handle_command(cmd);
+ if (ret)
+ break;
+
+ free(cmd);
+ cmd = NULL;
+ }
+
+ if (cmd)
+ free(cmd);
+
+ return ret;
+}
+
+void fio_server_idle_loop(void)
+{
+ if (server_fd != -1)
+ handle_connection(server_fd, 0);
+}
+