--- /dev/null
+/*
+ * libiscsi engine
+ *
+ * this engine read/write iscsi lun with libiscsi.
+ */
+
+
+#include "../fio.h"
+#include "../optgroup.h"
+
+#include <stdlib.h>
+#include <iscsi/iscsi.h>
+#include <iscsi/scsi-lowlevel.h>
+#include <poll.h>
+
+struct iscsi_lun;
+struct iscsi_info;
+
+struct iscsi_task {
+ struct scsi_task *scsi_task;
+ struct iscsi_lun *iscsi_lun;
+ struct io_u *io_u;
+};
+
+struct iscsi_lun {
+ struct iscsi_info *iscsi_info;
+ struct iscsi_context *iscsi;
+ struct iscsi_url *url;
+ int block_size;
+ uint64_t num_blocks;
+};
+
+struct iscsi_info {
+ struct iscsi_lun **luns;
+ int nr_luns;
+ struct pollfd *pfds;
+ struct iscsi_task **complete_events;
+ int nr_events;
+};
+
+struct iscsi_options {
+ void *pad;
+ char *initiator;
+};
+
+static struct fio_option options[] = {
+ {
+ .name = "initiator",
+ .lname = "initiator",
+ .type = FIO_OPT_STR_STORE,
+ .off1 = offsetof(struct iscsi_options, initiator),
+ .def = "iqn.2019-04.org.fio:fio",
+ .help = "initiator name",
+ .category = FIO_OPT_C_ENGINE,
+ .group = FIO_OPT_G_ISCSI,
+ },
+
+ {
+ .name = NULL,
+ },
+};
+
+static int fio_iscsi_setup_lun(struct iscsi_info *iscsi_info,
+ char *initiator, struct fio_file *f, int i)
+{
+ struct iscsi_lun *iscsi_lun = NULL;
+ struct scsi_task *task = NULL;
+ struct scsi_readcapacity16 *rc16 = NULL;
+ int ret = 0;
+
+ iscsi_lun = malloc(sizeof(struct iscsi_lun));
+ memset(iscsi_lun, 0, sizeof(struct iscsi_lun));
+
+ iscsi_lun->iscsi_info = iscsi_info;
+
+ iscsi_lun->url = iscsi_parse_full_url(NULL, f->file_name);
+ if (iscsi_lun->url == NULL) {
+ log_err("iscsi: failed to parse url: %s\n", f->file_name);
+ ret = EINVAL;
+ goto out;
+ }
+
+ iscsi_lun->iscsi = iscsi_create_context(initiator);
+ if (iscsi_lun->iscsi == NULL) {
+ log_err("iscsi: failed to create iscsi context.\n");
+ ret = 1;
+ goto out;
+ }
+
+ if (iscsi_set_targetname(iscsi_lun->iscsi, iscsi_lun->url->target)) {
+ log_err("iscsi: failed to set target name.\n");
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (iscsi_set_session_type(iscsi_lun->iscsi, ISCSI_SESSION_NORMAL) != 0) {
+ log_err("iscsi: failed to set session type.\n");
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (iscsi_set_header_digest(iscsi_lun->iscsi,
+ ISCSI_HEADER_DIGEST_NONE_CRC32C) != 0) {
+ log_err("iscsi: failed to set header digest.\n");
+ ret = EINVAL;
+ goto out;
+ }
+
+ if (iscsi_full_connect_sync(iscsi_lun->iscsi,
+ iscsi_lun->url->portal,
+ iscsi_lun->url->lun)) {
+ log_err("sicsi: failed to connect to LUN : %s\n",
+ iscsi_get_error(iscsi_lun->iscsi));
+ ret = EINVAL;
+ goto out;
+ }
+
+ task = iscsi_readcapacity16_sync(iscsi_lun->iscsi, iscsi_lun->url->lun);
+ if (task == NULL || task->status != SCSI_STATUS_GOOD) {
+ log_err("iscsi: failed to send readcapacity command\n");
+ ret = EINVAL;
+ goto out;
+ }
+
+ rc16 = scsi_datain_unmarshall(task);
+ if (rc16 == NULL) {
+ log_err("iscsi: failed to unmarshal readcapacity16 data.\n");
+ ret = EINVAL;
+ goto out;
+ }
+
+ iscsi_lun->block_size = rc16->block_length;
+ iscsi_lun->num_blocks = rc16->returned_lba + 1;
+
+ scsi_free_scsi_task(task);
+ task = NULL;
+
+ f->real_file_size = iscsi_lun->num_blocks * iscsi_lun->block_size;
+ f->engine_data = iscsi_lun;
+
+ iscsi_info->luns[i] = iscsi_lun;
+ iscsi_info->pfds[i].fd = iscsi_get_fd(iscsi_lun->iscsi);
+
+out:
+ if (task) {
+ scsi_free_scsi_task(task);
+ }
+
+ if (ret && iscsi_lun) {
+ if (iscsi_lun->iscsi != NULL) {
+ if (iscsi_is_logged_in(iscsi_lun->iscsi)) {
+ iscsi_logout_sync(iscsi_lun->iscsi);
+ }
+ iscsi_destroy_context(iscsi_lun->iscsi);
+ }
+ free(iscsi_lun);
+ }
+
+ return ret;
+}
+
+static int fio_iscsi_setup(struct thread_data *td)
+{
+ struct iscsi_options *options = td->eo;
+ struct iscsi_info *iscsi_info = NULL;
+ int ret = 0;
+ struct fio_file *f;
+ int i;
+
+ iscsi_info = malloc(sizeof(struct iscsi_info));
+ iscsi_info->nr_luns = td->o.nr_files;
+ iscsi_info->luns = calloc(iscsi_info->nr_luns, sizeof(struct iscsi_lun*));
+ iscsi_info->pfds = calloc(iscsi_info->nr_luns, sizeof(struct pollfd));
+
+ iscsi_info->nr_events = 0;
+ iscsi_info->complete_events = calloc(td->o.iodepth, sizeof(struct iscsi_task*));
+
+ td->io_ops_data = iscsi_info;
+
+ for_each_file(td, f, i) {
+ ret = fio_iscsi_setup_lun(iscsi_info, options->initiator, f, i);
+ if (ret < 0) break;
+ }
+
+ return ret;
+}
+
+static int fio_iscsi_init(struct thread_data *td) {
+ return 0;
+}
+
+static void fio_iscsi_cleanup_lun(struct iscsi_lun *iscsi_lun) {
+ if (iscsi_lun->iscsi != NULL) {
+ if (iscsi_is_logged_in(iscsi_lun->iscsi)) {
+ iscsi_logout_sync(iscsi_lun->iscsi);
+ }
+ iscsi_destroy_context(iscsi_lun->iscsi);
+ }
+ free(iscsi_lun);
+}
+
+static void fio_iscsi_cleanup(struct thread_data *td)
+{
+ struct iscsi_info *iscsi_info = td->io_ops_data;
+
+ for (int i = 0; i < iscsi_info->nr_luns; i++) {
+ if (iscsi_info->luns[i]) {
+ fio_iscsi_cleanup_lun(iscsi_info->luns[i]);
+ iscsi_info->luns[i] = NULL;
+ }
+ }
+
+ free(iscsi_info->luns);
+ free(iscsi_info->pfds);
+ free(iscsi_info->complete_events);
+ free(iscsi_info);
+}
+
+static int fio_iscsi_prep(struct thread_data *td, struct io_u *io_u)
+{
+ return 0;
+}
+
+static int fio_iscsi_open_file(struct thread_data *td, struct fio_file *f)
+{
+ return 0;
+}
+
+static int fio_iscsi_close_file(struct thread_data *td, struct fio_file *f)
+{
+ return 0;
+}
+
+static void iscsi_cb(struct iscsi_context *iscsi, int status,
+ void *command_data, void *private_data)
+{
+ struct iscsi_task *iscsi_task = (struct iscsi_task*)private_data;
+ struct iscsi_lun *iscsi_lun = iscsi_task->iscsi_lun;
+ struct iscsi_info *iscsi_info = iscsi_lun->iscsi_info;
+ struct io_u *io_u = iscsi_task->io_u;
+
+ if (status == SCSI_STATUS_GOOD) {
+ io_u->error = 0;
+ } else {
+ log_err("iscsi: request failed with error %s.\n",
+ iscsi_get_error(iscsi_lun->iscsi));
+
+ io_u->error = 1;
+ io_u->resid = io_u->xfer_buflen;
+ }
+
+ iscsi_info->complete_events[iscsi_info->nr_events] = iscsi_task;
+ iscsi_info->nr_events++;
+}
+
+static enum fio_q_status fio_iscsi_queue(struct thread_data *td,
+ struct io_u *io_u)
+{
+ struct iscsi_lun *iscsi_lun = io_u->file->engine_data;
+ struct scsi_task *scsi_task = NULL;
+ struct iscsi_task *iscsi_task = malloc(sizeof(struct iscsi_task));
+ int ret = -1;
+
+ if (io_u->ddir == DDIR_READ || io_u->ddir == DDIR_WRITE) {
+ if (io_u->offset % iscsi_lun->block_size != 0) {
+ log_err("iscsi: offset is not align to block size.\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (io_u->xfer_buflen % iscsi_lun->block_size != 0) {
+ log_err("iscsi: buflen is not align to block size.\n");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ if (io_u->ddir == DDIR_READ) {
+ scsi_task = scsi_cdb_read16(io_u->offset / iscsi_lun->block_size,
+ io_u->xfer_buflen,
+ iscsi_lun->block_size,
+ 0, 0, 0, 0, 0);
+ ret = scsi_task_add_data_in_buffer(scsi_task, io_u->xfer_buflen,
+ io_u->xfer_buf);
+ if (ret < 0) {
+ log_err("iscsi: failed to add data in buffer.\n");
+ goto out;
+ }
+ } else if (io_u->ddir == DDIR_WRITE) {
+ scsi_task = scsi_cdb_write16(io_u->offset / iscsi_lun->block_size,
+ io_u->xfer_buflen,
+ iscsi_lun->block_size,
+ 0, 0, 0, 0, 0);
+ ret = scsi_task_add_data_out_buffer(scsi_task, io_u->xfer_buflen,
+ io_u->xfer_buf);
+ if (ret < 0) {
+ log_err("iscsi: failed to add data out buffer.\n");
+ goto out;
+ }
+ } else if (ddir_sync(io_u->ddir)) {
+ scsi_task = scsi_cdb_synchronizecache16(
+ 0, iscsi_lun->num_blocks * iscsi_lun->block_size, 0, 0);
+ } else {
+ log_err("iscsi: invalid I/O operation: %d\n", io_u->ddir);
+ ret = EINVAL;
+ goto out;
+ }
+
+ iscsi_task->scsi_task = scsi_task;
+ iscsi_task->iscsi_lun = iscsi_lun;
+ iscsi_task->io_u = io_u;
+
+ ret = iscsi_scsi_command_async(iscsi_lun->iscsi, iscsi_lun->url->lun,
+ scsi_task, iscsi_cb, NULL, iscsi_task);
+ if (ret < 0) {
+ log_err("iscsi: failed to send scsi command.\n");
+ goto out;
+ }
+
+ return FIO_Q_QUEUED;
+
+out:
+ if (iscsi_task) {
+ free(iscsi_task);
+ }
+
+ if (scsi_task) {
+ scsi_free_scsi_task(scsi_task);
+ }
+
+ if (ret) {
+ io_u->error = ret;
+ }
+ return FIO_Q_COMPLETED;
+}
+
+static int fio_iscsi_getevents(struct thread_data *td, unsigned int min,
+ unsigned int max, const struct timespec *t)
+{
+ struct iscsi_info *iscsi_info = td->io_ops_data;
+ int ret = 0;
+
+ iscsi_info->nr_events = 0;
+
+ while (iscsi_info->nr_events < min) {
+ for (int i = 0; i < iscsi_info->nr_luns; i++) {
+ int events = iscsi_which_events(iscsi_info->luns[i]->iscsi);
+ iscsi_info->pfds[i].events = events;
+ }
+
+ ret = poll(iscsi_info->pfds, iscsi_info->nr_luns, -1);
+ if (ret < 0) {
+ log_err("iscsi: failed to poll events: %s.\n",
+ strerror(errno));
+ break;
+ }
+
+ for (int i = 0; i < iscsi_info->nr_luns; i++) {
+ ret = iscsi_service(iscsi_info->luns[i]->iscsi,
+ iscsi_info->pfds[i].revents);
+ assert(ret >= 0);
+ }
+ }
+
+ return ret < 0 ? ret : iscsi_info->nr_events;
+}
+
+static struct io_u *fio_iscsi_event(struct thread_data *td, int event)
+{
+ struct iscsi_info *iscsi_info = (struct iscsi_info*)td->io_ops_data;
+ struct iscsi_task *iscsi_task = iscsi_info->complete_events[event];
+ struct io_u *io_u = iscsi_task->io_u;
+
+ iscsi_info->complete_events[event] = NULL;
+
+ scsi_free_scsi_task(iscsi_task->scsi_task);
+ free(iscsi_task);
+
+ return io_u;
+}
+
+static struct ioengine_ops ioengine_iscsi = {
+ .name = "libiscsi",
+ .version = FIO_IOOPS_VERSION,
+ .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NODISKUTIL,
+ .setup = fio_iscsi_setup,
+ .init = fio_iscsi_init,
+ .prep = fio_iscsi_prep,
+ .queue = fio_iscsi_queue,
+ .getevents = fio_iscsi_getevents,
+ .event = fio_iscsi_event,
+ .cleanup = fio_iscsi_cleanup,
+ .open_file = fio_iscsi_open_file,
+ .close_file = fio_iscsi_close_file,
+ .option_struct_size = sizeof(struct iscsi_options),
+ .options = options,
+};
+
+static void fio_init fio_iscsi_register(void)
+{
+ register_ioengine(&ioengine_iscsi);
+}
+
+static void fio_exit fio_iscsi_unregister(void)
+{
+ unregister_ioengine(&ioengine_iscsi);
+}