+static int server_fd = -1;
+static char *fio_server_arg;
+static char *bind_sock;
+static struct sockaddr_in saddr_in;
+static struct sockaddr_in6 saddr_in6;
+static int first_cmd_check;
+static int use_ipv6;
+
+static const char *fio_server_ops[FIO_NET_CMD_NR] = {
+ "",
+ "QUIT",
+ "EXIT",
+ "JOB",
+ "JOBLINE",
+ "TEXT",
+ "TS",
+ "GS",
+ "SEND_ETA",
+ "ETA",
+ "PROBE",
+ "START",
+ "STOP",
+ "DISK_UTIL",
+ "RUN",
+};
+
+const char *fio_server_op(unsigned int op)
+{
+ static char buf[32];
+
+ if (op < FIO_NET_CMD_NR)
+ return fio_server_ops[op];
+
+ sprintf(buf, "UNKNOWN/%d", op);
+ return buf;
+}
+
+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;
+ else
+ break;
+ } 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;
+ else
+ break;
+ } 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 = fio_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->tag = le64_to_cpu(cmd->tag);
+ 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 if (cmdret->opcode != cmd.opcode) {
+ log_err("fio: fragment opcode mismatch (%d != %d)\n",
+ cmdret->opcode, cmd.opcode);
+ ret = 1;
+ break;
+ }
+
+ 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 = fio_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(fio_crc16(cmd, FIO_NET_CMD_CRC_SZ));
+
+ pdu_len = le32_to_cpu(cmd->pdu_len);
+ if (pdu_len)
+ cmd->pdu_crc16 = __cpu_to_le16(fio_crc16(cmd->payload, pdu_len));
+}
+
+int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size,
+ uint64_t tag)
+{
+ struct fio_net_cmd *cmd = NULL;
+ size_t this_len, cur_len = 0;
+ int ret;
+
+ do {
+ this_len = size;
+ if (this_len > FIO_SERVER_MAX_PDU)
+ this_len = FIO_SERVER_MAX_PDU;
+
+ if (!cmd || cur_len < sizeof(*cmd) + this_len) {
+ if (cmd)
+ free(cmd);
+
+ cur_len = sizeof(*cmd) + this_len;
+ cmd = malloc(cur_len);
+ }
+
+ fio_init_net_cmd(cmd, opcode, buf, this_len, tag);
+
+ 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);
+ size -= this_len;
+ buf += this_len;
+ } while (!ret && size);
+
+ if (cmd)
+ free(cmd);
+
+ return ret;
+}
+
+static int fio_net_send_simple_stack_cmd(int sk, uint16_t opcode, uint64_t tag)
+{
+ struct fio_net_cmd cmd;
+
+ fio_init_net_cmd(&cmd, opcode, NULL, 0, tag);
+ fio_net_cmd_crc(&cmd);
+
+ return fio_send_data(sk, &cmd, sizeof(cmd));
+}
+
+/*
+ * If 'list' is non-NULL, then allocate and store the sent command for
+ * later verification.
+ */
+int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t tag,
+ struct flist_head *list)
+{
+ struct fio_net_int_cmd *cmd;
+ int ret;
+
+ if (!list)
+ return fio_net_send_simple_stack_cmd(sk, opcode, tag);
+
+ cmd = malloc(sizeof(*cmd));
+
+ fio_init_net_cmd(&cmd->cmd, opcode, NULL, 0, (uintptr_t) cmd);
+ fio_net_cmd_crc(&cmd->cmd);
+
+ INIT_FLIST_HEAD(&cmd->list);
+ fio_gettime(&cmd->tv, NULL);
+ cmd->saved_tag = tag;
+
+ ret = fio_send_data(sk, &cmd->cmd, sizeof(cmd->cmd));
+ if (ret) {
+ free(cmd);
+ return ret;
+ }
+
+ flist_add_tail(&cmd->list, list);
+ return 0;
+}
+
+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, NULL);
+}
+
+static int handle_job_cmd(struct fio_net_cmd *cmd)
+{
+ char *buf = (char *) cmd->payload;
+ struct cmd_start_pdu spdu;
+ struct cmd_end_pdu epdu;
+ int ret;
+
+ if (parse_jobs_ini(buf, 1, 0)) {
+ fio_server_send_quit_cmd();
+ return -1;
+ }
+
+ spdu.jobs = cpu_to_le32(thread_number);
+ spdu.stat_outputs = cpu_to_le32(stat_number);
+ fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), 0);
+
+ ret = fio_backend();
+
+ epdu.error = ret;
+ fio_net_send_cmd(server_fd, FIO_NET_CMD_STOP, &epdu, sizeof(epdu), 0);
+
+ 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, NULL);
+
+ ret = fio_backend();
+ 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;
+
+ dprint(FD_NET, "server: sending probe reply\n");
+
+ memset(&probe, 0, sizeof(probe));
+ gethostname((char *) probe.hostname, sizeof(probe.hostname));
+#ifdef FIO_BIG_ENDIAN
+ probe.bigendian = 1;
+#endif
+ strncpy((char *) probe.fio_version, fio_version_string, sizeof(probe.fio_version));
+
+ probe.os = FIO_OS;
+ probe.arch = FIO_ARCH;
+
+ probe.bpp = sizeof(void *);
+
+ return fio_net_send_cmd(server_fd, FIO_NET_CMD_PROBE, &probe, sizeof(probe), cmd->tag);
+}
+
+static int handle_send_eta_cmd(struct fio_net_cmd *cmd)
+{
+ struct jobs_eta *je;
+ size_t size;
+ int i;
+
+ if (!thread_number)
+ return 0;
+
+ size = sizeof(*je) + thread_number * sizeof(char) + 1;
+ je = malloc(size);
+ memset(je, 0, size);
+
+ if (!calc_thread_status(je, 1)) {
+ free(je);
+ return 0;
+ }
+
+ dprint(FD_NET, "server sending status\n");
+
+ je->nr_running = cpu_to_le32(je->nr_running);
+ je->nr_ramp = cpu_to_le32(je->nr_ramp);
+ je->nr_pending = cpu_to_le32(je->nr_pending);
+ je->files_open = cpu_to_le32(je->files_open);
+ je->m_rate = cpu_to_le32(je->m_rate);
+ je->t_rate = cpu_to_le32(je->t_rate);
+ je->m_iops = cpu_to_le32(je->m_iops);
+ je->t_iops = cpu_to_le32(je->t_iops);
+
+ for (i = 0; i < 2; i++) {
+ je->rate[i] = cpu_to_le32(je->rate[i]);
+ je->iops[i] = cpu_to_le32(je->iops[i]);
+ }
+
+ je->elapsed_sec = cpu_to_le64(je->elapsed_sec);
+ je->eta_sec = cpu_to_le64(je->eta_sec);
+ je->is_pow2 = cpu_to_le32(je->is_pow2);
+
+ fio_net_send_cmd(server_fd, FIO_NET_CMD_ETA, je, size, cmd->tag);
+ free(je);
+ return 0;
+}
+
+static int handle_command(struct fio_net_cmd *cmd)
+{
+ int ret;
+
+ dprint(FD_NET, "server: got op [%s], pdu=%u, tag=%lx\n",
+ fio_server_op(cmd->opcode), cmd->pdu_len, cmd->tag);
+
+ 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;
+ case FIO_NET_CMD_SEND_ETA:
+ ret = handle_send_eta_cmd(cmd);
+ break;
+ default:
+ log_err("fio: unknown opcode: %s\n",fio_server_op(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 (!first_cmd_check)
+ fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_RUN, 0, NULL);
+ if (server_fd != -1)
+ handle_connection(server_fd, 0);
+}
+