summaryrefslogtreecommitdiffstats
path: root/src/spdk/app/spdk_dd
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/spdk/app/spdk_dd
parentInitial commit. (diff)
downloadceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz
ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/app/spdk_dd')
-rw-r--r--src/spdk/app/spdk_dd/.gitignore1
-rw-r--r--src/spdk/app/spdk_dd/Makefile47
-rw-r--r--src/spdk/app/spdk_dd/spdk_dd.c1174
3 files changed, 1222 insertions, 0 deletions
diff --git a/src/spdk/app/spdk_dd/.gitignore b/src/spdk/app/spdk_dd/.gitignore
new file mode 100644
index 000000000..8810437a9
--- /dev/null
+++ b/src/spdk/app/spdk_dd/.gitignore
@@ -0,0 +1 @@
+spdk_dd
diff --git a/src/spdk/app/spdk_dd/Makefile b/src/spdk/app/spdk_dd/Makefile
new file mode 100644
index 000000000..3bd99f639
--- /dev/null
+++ b/src/spdk/app/spdk_dd/Makefile
@@ -0,0 +1,47 @@
+#
+# BSD LICENSE
+#
+# Copyright (c) Intel Corporation.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Intel Corporation nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..)
+include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
+include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
+
+APP = spdk_dd
+
+C_SRCS := spdk_dd.c
+
+SPDK_LIB_LIST = $(ALL_MODULES_LIST)
+SPDK_LIB_LIST += event_sock event_bdev event_accel event_vmd
+SPDK_LIB_LIST += bdev accel event thread util conf trace \
+ log jsonrpc json rpc sock notify
+
+include $(SPDK_ROOT_DIR)/mk/spdk.app.mk
diff --git a/src/spdk/app/spdk_dd/spdk_dd.c b/src/spdk/app/spdk_dd/spdk_dd.c
new file mode 100644
index 000000000..c9f2dd117
--- /dev/null
+++ b/src/spdk/app/spdk_dd/spdk_dd.c
@@ -0,0 +1,1174 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright (c) Intel Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "spdk/stdinc.h"
+
+#include "spdk/bdev.h"
+#include "spdk/event.h"
+#include "spdk/fd.h"
+#include "spdk/string.h"
+#include "spdk/vmd.h"
+
+#include <libaio.h>
+
+#ifdef SPDK_CONFIG_URING
+#include <liburing.h>
+#endif
+
+#define DD_NSEC_SINCE_X(time_now, time_x) ((1000000000 * time_now.tv_sec + time_now.tv_nsec) \
+ - (1000000000 * time_x.tv_sec + time_x.tv_nsec))
+
+struct spdk_dd_opts {
+ char *input_file;
+ char *output_file;
+ char *input_file_flags;
+ char *output_file_flags;
+ char *input_bdev;
+ char *output_bdev;
+ uint64_t input_offset;
+ uint64_t output_offset;
+ int64_t io_unit_size;
+ int64_t io_unit_count;
+ uint32_t queue_depth;
+ bool aio;
+};
+
+static struct spdk_dd_opts g_opts = {
+ .io_unit_size = 4096,
+ .queue_depth = 2,
+};
+
+enum dd_submit_type {
+ DD_POPULATE,
+ DD_READ,
+ DD_WRITE,
+};
+
+struct dd_io {
+ uint64_t offset;
+ uint64_t length;
+ struct iocb iocb;
+ enum dd_submit_type type;
+#ifdef SPDK_CONFIG_URING
+ struct iovec iov;
+#endif
+ void *buf;
+};
+
+enum dd_target_type {
+ DD_TARGET_TYPE_FILE,
+ DD_TARGET_TYPE_BDEV,
+};
+
+struct dd_target {
+ enum dd_target_type type;
+
+ union {
+ struct {
+ struct spdk_bdev *bdev;
+ struct spdk_bdev_desc *desc;
+ struct spdk_io_channel *ch;
+ } bdev;
+
+#ifdef SPDK_CONFIG_URING
+ struct {
+ int fd;
+ struct io_uring ring;
+ struct spdk_poller *poller;
+ } uring;
+#endif
+ struct {
+ int fd;
+ io_context_t io_ctx;
+ struct spdk_poller *poller;
+ } aio;
+ } u;
+
+ /* Block size of underlying device. */
+ uint32_t block_size;
+
+ /* Position of next I/O in bytes */
+ uint64_t pos;
+
+ /* Total size of target in bytes */
+ uint64_t total_size;
+
+ bool open;
+};
+
+struct dd_job {
+ struct dd_target input;
+ struct dd_target output;
+
+ struct dd_io *ios;
+
+ uint32_t outstanding;
+ uint64_t copy_size;
+};
+
+struct dd_flags {
+ char *name;
+ int flag;
+};
+
+static struct dd_flags g_flags[] = {
+ {"append", O_APPEND},
+ {"direct", O_DIRECT},
+ {"directory", O_DIRECTORY},
+ {"dsync", O_DSYNC},
+ {"noatime", O_NOATIME},
+ {"noctty", O_NOCTTY},
+ {"nofollow", O_NOFOLLOW},
+ {"nonblock", O_NONBLOCK},
+ {"sync", O_SYNC},
+ {NULL, 0}
+};
+
+static struct dd_job g_job = {};
+static int g_error = 0;
+static struct timespec g_start_time;
+static bool g_interrupt;
+
+static void dd_target_populate_buffer(struct dd_io *io);
+
+static void
+dd_exit(int rc)
+{
+ if (g_job.input.type == DD_TARGET_TYPE_FILE) {
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ spdk_poller_unregister(&g_job.input.u.uring.poller);
+ close(g_job.input.u.uring.fd);
+ } else
+#endif
+ {
+ spdk_poller_unregister(&g_job.input.u.aio.poller);
+ io_destroy(g_job.input.u.aio.io_ctx);
+ close(g_job.input.u.aio.fd);
+ }
+ } else if (g_job.input.type == DD_TARGET_TYPE_BDEV && g_job.input.open) {
+ spdk_put_io_channel(g_job.input.u.bdev.ch);
+ spdk_bdev_close(g_job.input.u.bdev.desc);
+ }
+
+ if (g_job.output.type == DD_TARGET_TYPE_FILE) {
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ spdk_poller_unregister(&g_job.output.u.uring.poller);
+ close(g_job.output.u.uring.fd);
+ } else
+#endif
+ {
+ spdk_poller_unregister(&g_job.output.u.aio.poller);
+ io_destroy(g_job.output.u.aio.io_ctx);
+ close(g_job.output.u.aio.fd);
+ }
+ } else if (g_job.output.type == DD_TARGET_TYPE_BDEV && g_job.output.open) {
+ spdk_put_io_channel(g_job.output.u.bdev.ch);
+ spdk_bdev_close(g_job.output.u.bdev.desc);
+ }
+
+ spdk_app_stop(rc);
+}
+
+static void
+dd_show_progress(uint64_t offset, uint64_t length, bool finish)
+{
+ char *unit_str[5] = {"", "k", "M", "G", "T"};
+ char *speed_type_str[2] = {"", "average "};
+ char *size_unit_str = "";
+ char *speed_unit_str = "";
+ char *speed_type = "";
+ uint64_t size = g_job.copy_size;
+ uint64_t size_unit = 1;
+ uint64_t speed_unit = 1;
+ uint64_t speed, tmp_speed;
+ static struct timespec g_time_last = {.tv_nsec = 0};
+ static uint64_t g_data_last = 0;
+ struct timespec time_now;
+ int i = 0;
+
+ clock_gettime(CLOCK_REALTIME, &time_now);
+
+ if (((time_now.tv_sec == g_time_last.tv_sec && offset + length != g_job.copy_size) ||
+ (offset < g_data_last)) && !finish) {
+ /* refresh every one second */
+ return;
+ }
+
+ /* Find the rigth unit for size displaying (B vs kB vs MB vs GB vs TB) */
+ while (size > 1024 * 10) {
+ size >>= 10;
+ size_unit <<= 10;
+ size_unit_str = unit_str[++i];
+ if (i == 4) {
+ break;
+ }
+ }
+
+ if (!finish) {
+ speed_type = speed_type_str[0];
+ tmp_speed = speed = (offset - g_data_last) * 1000000000 / DD_NSEC_SINCE_X(time_now, g_time_last);
+ } else {
+ speed_type = speed_type_str[1];
+ tmp_speed = speed = offset * 1000000000 / DD_NSEC_SINCE_X(time_now, g_start_time);
+ }
+
+ i = 0;
+
+ /* Find the rigth unit for speed displaying (Bps vs kBps vs MBps vs GBps vs TBps) */
+ while (tmp_speed > 1024) {
+ tmp_speed >>= 10;
+ speed_unit <<= 10;
+ speed_unit_str = unit_str[++i];
+ if (i == 4) {
+ break;
+ }
+ }
+
+ printf("\33[2K\rCopying: %" PRIu64 "/%" PRIu64 " [%sB] (%s%" PRIu64 " %sBps)",
+ (offset + length) / size_unit, g_job.copy_size / size_unit, size_unit_str, speed_type,
+ speed / speed_unit, speed_unit_str);
+ fflush(stdout);
+
+ g_data_last = offset;
+ g_time_last = time_now;
+}
+
+#ifdef SPDK_CONFIG_URING
+static void
+dd_uring_submit(struct dd_io *io, struct dd_target *target, uint64_t length, uint64_t offset)
+{
+ struct io_uring_sqe *sqe;
+
+ io->iov.iov_base = io->buf;
+ io->iov.iov_len = length;
+ sqe = io_uring_get_sqe(&target->u.uring.ring);
+ if (io->type == DD_READ || io->type == DD_POPULATE) {
+ io_uring_prep_readv(sqe, target->u.uring.fd, &io->iov, 1, offset);
+ } else {
+ io_uring_prep_writev(sqe, target->u.uring.fd, &io->iov, 1, offset);
+ }
+ io_uring_sqe_set_data(sqe, io);
+ io_uring_submit(&target->u.uring.ring);
+}
+#endif
+
+static void
+_dd_write_bdev_done(struct spdk_bdev_io *bdev_io,
+ bool success,
+ void *cb_arg)
+{
+ struct dd_io *io = cb_arg;
+
+ assert(g_job.outstanding > 0);
+ g_job.outstanding--;
+ spdk_bdev_free_io(bdev_io);
+ dd_target_populate_buffer(io);
+}
+
+static void
+dd_target_write(struct dd_io *io)
+{
+ struct dd_target *target = &g_job.output;
+ uint64_t length = SPDK_CEIL_DIV(io->length, target->block_size) * target->block_size;
+ uint64_t read_region_start = g_opts.input_offset * g_opts.io_unit_size;
+ uint64_t read_offset = io->offset - read_region_start;
+ uint64_t write_region_start = g_opts.output_offset * g_opts.io_unit_size;
+ uint64_t write_offset = write_region_start + read_offset;
+ int rc = 0;
+
+ if (g_error != 0 || g_interrupt == true) {
+ if (g_job.outstanding == 0) {
+ if (g_error == 0) {
+ dd_show_progress(io->offset, io->length, true);
+ printf("\n\n");
+ }
+ dd_exit(g_error);
+ }
+ return;
+ }
+
+ dd_show_progress(read_offset, io->length, false);
+
+ g_job.outstanding++;
+ io->type = DD_WRITE;
+
+ if (target->type == DD_TARGET_TYPE_FILE) {
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ dd_uring_submit(io, target, length, write_offset);
+ } else
+#endif
+ {
+ struct iocb *iocb = &io->iocb;
+
+ io_prep_pwrite(iocb, target->u.aio.fd, io->buf, length, write_offset);
+ iocb->data = io;
+ if (io_submit(target->u.aio.io_ctx, 1, &iocb) < 0) {
+ rc = -errno;
+ }
+ }
+ } else if (target->type == DD_TARGET_TYPE_BDEV) {
+ rc = spdk_bdev_write(target->u.bdev.desc, target->u.bdev.ch, io->buf, write_offset, length,
+ _dd_write_bdev_done, io);
+ }
+
+ if (rc != 0) {
+ SPDK_ERRLOG("%s\n", strerror(-rc));
+ assert(g_job.outstanding > 0);
+ g_job.outstanding--;
+ g_error = rc;
+ if (g_job.outstanding == 0) {
+ dd_exit(rc);
+ }
+ return;
+ }
+}
+
+static void
+_dd_read_bdev_done(struct spdk_bdev_io *bdev_io,
+ bool success,
+ void *cb_arg)
+{
+ struct dd_io *io = cb_arg;
+
+ spdk_bdev_free_io(bdev_io);
+
+ assert(g_job.outstanding > 0);
+ g_job.outstanding--;
+ dd_target_write(io);
+}
+
+static void
+dd_target_read(struct dd_io *io)
+{
+ struct dd_target *target = &g_job.input;
+ int rc = 0;
+
+ if (g_error != 0 || g_interrupt == true) {
+ if (g_job.outstanding == 0) {
+ dd_exit(g_error);
+ }
+ return;
+ }
+
+ g_job.outstanding++;
+ io->type = DD_READ;
+
+ if (target->type == DD_TARGET_TYPE_FILE) {
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ dd_uring_submit(io, target, io->length, io->offset);
+ } else
+#endif
+ {
+ struct iocb *iocb = &io->iocb;
+
+ io_prep_pread(iocb, target->u.aio.fd, io->buf, io->length, io->offset);
+ iocb->data = io;
+ if (io_submit(target->u.aio.io_ctx, 1, &iocb) < 0) {
+ rc = -errno;
+ }
+ }
+ } else if (target->type == DD_TARGET_TYPE_BDEV) {
+ rc = spdk_bdev_read(target->u.bdev.desc, target->u.bdev.ch, io->buf, io->offset, io->length,
+ _dd_read_bdev_done, io);
+ }
+
+ if (rc != 0) {
+ SPDK_ERRLOG("%s\n", strerror(-rc));
+ assert(g_job.outstanding > 0);
+ g_job.outstanding--;
+ g_error = rc;
+ if (g_job.outstanding == 0) {
+ dd_exit(rc);
+ }
+ return;
+ }
+}
+
+static void
+_dd_target_populate_buffer_done(struct spdk_bdev_io *bdev_io,
+ bool success,
+ void *cb_arg)
+{
+ struct dd_io *io = cb_arg;
+
+ assert(g_job.outstanding > 0);
+ g_job.outstanding--;
+ spdk_bdev_free_io(bdev_io);
+ dd_target_read(io);
+}
+
+static void
+dd_target_populate_buffer(struct dd_io *io)
+{
+ struct dd_target *target = &g_job.output;
+ uint64_t read_region_start = g_opts.input_offset * g_opts.io_unit_size;
+ uint64_t read_offset = g_job.input.pos - read_region_start;
+ uint64_t write_region_start = g_opts.output_offset * g_opts.io_unit_size;
+ uint64_t write_offset = write_region_start + read_offset;
+ uint64_t length;
+ int rc = 0;
+
+ io->offset = g_job.input.pos;
+ io->length = spdk_min((uint64_t)g_opts.io_unit_size, g_job.copy_size - read_offset);
+
+ if (io->length == 0 || g_error != 0 || g_interrupt == true) {
+ if (g_job.outstanding == 0) {
+ if (g_error == 0) {
+ dd_show_progress(read_offset, io->length, true);
+ printf("\n\n");
+ }
+ dd_exit(g_error);
+ }
+ return;
+ }
+
+ g_job.input.pos += io->length;
+
+ if ((io->length % target->block_size) == 0) {
+ dd_target_read(io);
+ return;
+ }
+
+ /* Read whole blocks from output to combine buffers later */
+ g_job.outstanding++;
+ io->type = DD_POPULATE;
+
+ length = SPDK_CEIL_DIV(io->length, target->block_size) * target->block_size;
+
+ if (target->type == DD_TARGET_TYPE_FILE) {
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ dd_uring_submit(io, target, length, write_offset);
+ } else
+#endif
+ {
+ struct iocb *iocb = &io->iocb;
+
+ io_prep_pread(iocb, target->u.aio.fd, io->buf, length, write_offset);
+ iocb->data = io;
+ if (io_submit(target->u.aio.io_ctx, 1, &iocb) < 0) {
+ rc = -errno;
+ }
+ }
+ } else if (target->type == DD_TARGET_TYPE_BDEV) {
+ rc = spdk_bdev_read(target->u.bdev.desc, target->u.bdev.ch, io->buf, write_offset, length,
+ _dd_target_populate_buffer_done, io);
+ }
+
+ if (rc != 0) {
+ SPDK_ERRLOG("%s\n", strerror(-rc));
+ assert(g_job.outstanding > 0);
+ g_job.outstanding--;
+ g_error = rc;
+ if (g_job.outstanding == 0) {
+ dd_exit(rc);
+ }
+ return;
+ }
+}
+
+static void
+dd_complete_poll(struct dd_io *io)
+{
+ assert(g_job.outstanding > 0);
+ g_job.outstanding--;
+
+ switch (io->type) {
+ case DD_POPULATE:
+ dd_target_read(io);
+ break;
+ case DD_READ:
+ dd_target_write(io);
+ break;
+ case DD_WRITE:
+ dd_target_populate_buffer(io);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+#ifdef SPDK_CONFIG_URING
+static int
+dd_uring_poll(void *ctx)
+{
+ struct dd_target *target = ctx;
+ struct io_uring_cqe *cqe;
+ struct dd_io *io;
+ int rc = 0;
+ int i;
+
+ for (i = 0; i < (int)g_opts.queue_depth; i++) {
+ rc = io_uring_peek_cqe(&target->u.uring.ring, &cqe);
+ if (rc == 0) {
+ if (cqe->res == -EAGAIN) {
+ continue;
+ } else if (cqe->res < 0) {
+ SPDK_ERRLOG("%s\n", strerror(-cqe->res));
+ g_error = cqe->res;
+ }
+
+ io = io_uring_cqe_get_data(cqe);
+ io_uring_cqe_seen(&target->u.uring.ring, cqe);
+
+ dd_complete_poll(io);
+ } else if (rc != - EAGAIN) {
+ SPDK_ERRLOG("%s\n", strerror(-rc));
+ g_error = rc;
+ }
+ }
+
+ return rc;
+}
+#endif
+
+static int
+dd_aio_poll(io_context_t io_ctx)
+{
+ struct io_event events[32];
+ int rc = 0;
+ int i;
+ struct timespec timeout;
+ struct dd_io *io;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+
+ rc = io_getevents(io_ctx, 0, 32, events, &timeout);
+
+ if (rc < 0) {
+ SPDK_ERRLOG("%s\n", strerror(-rc));
+ dd_exit(rc);
+ }
+
+ for (i = 0; i < rc; i++) {
+ io = events[i].data;
+ if (events[i].res != io->length) {
+ g_error = rc = -ENOSPC;
+ }
+
+ dd_complete_poll(io);
+ }
+
+ return rc;
+}
+
+static int
+dd_input_poll(void *ctx)
+{
+ int rc = 0;
+
+ assert(g_job.input.type == DD_TARGET_TYPE_FILE);
+
+ rc = dd_aio_poll(g_job.input.u.aio.io_ctx);
+ if (rc == -ENOSPC) {
+ SPDK_ERRLOG("No more file content to read\n");
+ }
+
+ return rc;
+}
+
+static int
+dd_output_poll(void *ctx)
+{
+ int rc = 0;
+
+ assert(g_job.output.type == DD_TARGET_TYPE_FILE);
+
+ rc = dd_aio_poll(g_job.output.u.aio.io_ctx);
+ if (rc == -ENOSPC) {
+ SPDK_ERRLOG("No space left on device\n");
+ }
+
+ return rc;
+}
+
+static int
+dd_open_file(struct dd_target *target, const char *fname, int flags, uint64_t skip_blocks,
+ bool input)
+{
+ int *fd;
+
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ fd = &target->u.uring.fd;
+ } else
+#endif
+ {
+ fd = &target->u.aio.fd;
+ }
+
+ flags |= O_RDWR;
+
+ if (input == false && ((flags & O_DIRECTORY) == 0)) {
+ flags |= O_CREAT;
+ }
+
+ if (input == false && ((flags & O_APPEND) == 0)) {
+ flags |= O_TRUNC;
+ }
+
+#ifdef SPDK_CONFIG_URING
+ /* io_uring does not work correctly with O_NONBLOCK flag */
+ if (flags & O_NONBLOCK && g_opts.aio == false) {
+ flags &= ~O_NONBLOCK;
+ SPDK_WARNLOG("Skipping 'nonblock' flag due to existing issue with uring implementation and this flag\n");
+ }
+#endif
+
+ target->type = DD_TARGET_TYPE_FILE;
+ *fd = open(fname, flags, 0600);
+ if (*fd < 0) {
+ SPDK_ERRLOG("Could not open file %s: %s\n", fname, strerror(errno));
+ return *fd;
+ }
+
+ target->block_size = spdk_max(spdk_fd_get_blocklen(*fd), 1);
+ target->total_size = spdk_fd_get_size(*fd);
+
+ if (input == true) {
+ g_opts.queue_depth = spdk_min(g_opts.queue_depth,
+ (target->total_size / g_opts.io_unit_size) - skip_blocks + 1);
+ }
+
+ if (g_opts.io_unit_count != 0) {
+ g_opts.queue_depth = spdk_min(g_opts.queue_depth, g_opts.io_unit_count);
+ }
+
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ io_uring_queue_init(g_opts.queue_depth, &target->u.uring.ring, 0);
+ target->open = true;
+ return 0;
+ } else
+#endif
+ {
+ return io_setup(g_opts.queue_depth, &target->u.aio.io_ctx);
+ }
+}
+
+static int
+dd_open_bdev(struct dd_target *target, const char *bdev_name, uint64_t skip_blocks)
+{
+ int rc;
+
+ target->type = DD_TARGET_TYPE_BDEV;
+ target->u.bdev.bdev = spdk_bdev_get_by_name(bdev_name);
+ if (target->u.bdev.bdev == NULL) {
+ SPDK_ERRLOG("Could not find bdev %s\n", bdev_name);
+ return -EINVAL;
+ }
+
+ target->block_size = spdk_bdev_get_block_size(target->u.bdev.bdev);
+ target->total_size = spdk_bdev_get_num_blocks(target->u.bdev.bdev) * target->block_size;
+
+ rc = spdk_bdev_open(target->u.bdev.bdev, true, NULL, NULL, &target->u.bdev.desc);
+ if (rc < 0) {
+ SPDK_ERRLOG("Could not open bdev %s: %s\n", bdev_name, strerror(-rc));
+ return rc;
+ }
+
+ target->open = true;
+
+ target->u.bdev.ch = spdk_bdev_get_io_channel(target->u.bdev.desc);
+ if (target->u.bdev.ch == NULL) {
+ spdk_bdev_close(target->u.bdev.desc);
+ SPDK_ERRLOG("Could not get I/O channel: %s\n", strerror(ENOMEM));
+ return -ENOMEM;
+ }
+
+ g_opts.queue_depth = spdk_min(g_opts.queue_depth,
+ (target->total_size / g_opts.io_unit_size) - skip_blocks + 1);
+
+ if (g_opts.io_unit_count != 0) {
+ g_opts.queue_depth = spdk_min(g_opts.queue_depth, g_opts.io_unit_count);
+ }
+
+ return 0;
+}
+
+static void dd_finish(void)
+{
+ /* Interrupt operation */
+ g_interrupt = true;
+}
+
+static int
+parse_flags(char *file_flags)
+{
+ char *input_flag;
+ int flags = 0;
+ int i;
+ bool found = false;
+
+ /* Translate input flags to file open flags */
+ while ((input_flag = strsep(&file_flags, ","))) {
+ for (i = 0; g_flags[i].name != NULL; i++) {
+ if (!strcmp(input_flag, g_flags[i].name)) {
+ flags |= g_flags[i].flag;
+ found = true;
+ break;
+ }
+ }
+
+ if (found == false) {
+ SPDK_ERRLOG("Unknown file flag: %s\n", input_flag);
+ return -EINVAL;
+ }
+
+ found = false;
+ }
+
+ return flags;
+}
+
+static void
+dd_run(void *arg1)
+{
+ uint64_t write_size;
+ uint32_t i;
+ int rc, flags = 0;
+
+ if (g_opts.input_file) {
+ if (g_opts.input_file_flags) {
+ flags = parse_flags(g_opts.input_file_flags);
+ }
+
+ if (dd_open_file(&g_job.input, g_opts.input_file, flags, g_opts.input_offset, true) < 0) {
+ SPDK_ERRLOG("%s: %s\n", g_opts.input_file, strerror(errno));
+ dd_exit(-errno);
+ return;
+ }
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ g_job.input.u.uring.poller = spdk_poller_register(dd_uring_poll, &g_job.input, 0);
+ } else
+#endif
+ {
+ g_job.input.u.aio.poller = spdk_poller_register(dd_input_poll, NULL, 0);
+ }
+ } else if (g_opts.input_bdev) {
+ rc = dd_open_bdev(&g_job.input, g_opts.input_bdev, g_opts.input_offset);
+ if (rc < 0) {
+ SPDK_ERRLOG("%s: %s\n", g_opts.input_bdev, strerror(-rc));
+ dd_exit(rc);
+ return;
+ }
+ }
+
+ write_size = g_opts.io_unit_count * g_opts.io_unit_size;
+ g_job.input.pos = g_opts.input_offset * g_opts.io_unit_size;
+
+ /* We cannot check write size for input files because /dev/zeros, /dev/random, etc would not work.
+ * We will handle that during copying */
+ if (g_opts.input_bdev && g_job.input.pos > g_job.input.total_size) {
+ SPDK_ERRLOG("--skip value too big (%" PRIu64 ") - only %" PRIu64 " blocks available in input\n",
+ g_opts.input_offset, g_job.input.total_size / g_opts.io_unit_size);
+ dd_exit(-ENOSPC);
+ return;
+ }
+
+ if (g_opts.io_unit_count != 0 && g_opts.input_bdev &&
+ write_size + g_job.input.pos > g_job.input.total_size) {
+ SPDK_ERRLOG("--count value too big (%" PRIu64 ") - only %" PRIu64 " blocks available from input\n",
+ g_opts.io_unit_count, (g_job.input.total_size - g_job.input.pos) / g_opts.io_unit_size);
+ dd_exit(-ENOSPC);
+ return;
+ }
+
+ if (g_opts.io_unit_count != 0) {
+ g_job.copy_size = write_size;
+ } else {
+ g_job.copy_size = g_job.input.total_size - g_job.input.pos;
+ }
+
+ g_job.output.pos = g_opts.output_offset * g_opts.io_unit_size;
+
+ if (g_opts.output_file) {
+ flags = 0;
+
+ if (g_opts.output_file_flags) {
+ flags = parse_flags(g_opts.output_file_flags);
+ }
+
+ if (dd_open_file(&g_job.output, g_opts.output_file, flags, g_opts.output_offset, false) < 0) {
+ SPDK_ERRLOG("%s: %s\n", g_opts.output_file, strerror(errno));
+ dd_exit(-errno);
+ return;
+ }
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ g_job.output.u.uring.poller = spdk_poller_register(dd_uring_poll, &g_job.output, 0);
+ } else
+#endif
+ {
+ g_job.output.u.aio.poller = spdk_poller_register(dd_output_poll, NULL, 0);
+ }
+ } else if (g_opts.output_bdev) {
+ rc = dd_open_bdev(&g_job.output, g_opts.output_bdev, g_opts.output_offset);
+ if (rc < 0) {
+ SPDK_ERRLOG("%s: %s\n", g_opts.output_bdev, strerror(-rc));
+ dd_exit(rc);
+ return;
+ }
+
+ if (g_job.output.pos > g_job.output.total_size) {
+ SPDK_ERRLOG("--seek value too big (%" PRIu64 ") - only %" PRIu64 " blocks available in output\n",
+ g_opts.output_offset, g_job.output.total_size / g_opts.io_unit_size);
+ dd_exit(-ENOSPC);
+ return;
+ }
+
+ if (g_opts.io_unit_count != 0 && write_size + g_job.output.pos > g_job.output.total_size) {
+ SPDK_ERRLOG("--count value too big (%" PRIu64 ") - only %" PRIu64 " blocks available in output\n",
+ g_opts.io_unit_count, (g_job.output.total_size - g_job.output.pos) / g_opts.io_unit_size);
+ dd_exit(-ENOSPC);
+ return;
+ }
+ }
+
+ if ((g_job.output.block_size > g_opts.io_unit_size) ||
+ (g_job.input.block_size > g_opts.io_unit_size)) {
+ SPDK_ERRLOG("--bs value cannot be less than input (%d) neither output (%d) native block size\n",
+ g_job.input.block_size, g_job.output.block_size);
+ dd_exit(-EINVAL);
+ return;
+ }
+
+ g_job.ios = calloc(g_opts.queue_depth, sizeof(struct dd_io));
+ if (g_job.ios == NULL) {
+ SPDK_ERRLOG("%s\n", strerror(ENOMEM));
+ dd_exit(-ENOMEM);
+ return;
+ }
+
+ for (i = 0; i < g_opts.queue_depth; i++) {
+ g_job.ios[i].buf = spdk_malloc(g_opts.io_unit_size, 0x1000, NULL, 0, SPDK_MALLOC_DMA);
+ if (g_job.ios[i].buf == NULL) {
+ SPDK_ERRLOG("%s - try smaller block size value\n", strerror(ENOMEM));
+ dd_exit(-ENOMEM);
+ return;
+ }
+ }
+
+ clock_gettime(CLOCK_REALTIME, &g_start_time);
+
+ for (i = 0; i < g_opts.queue_depth; i++) {
+ dd_target_populate_buffer(&g_job.ios[i]);
+ }
+
+}
+
+enum dd_cmdline_opts {
+ DD_OPTION_IF = 0x1000,
+ DD_OPTION_OF,
+ DD_OPTION_IFLAGS,
+ DD_OPTION_OFLAGS,
+ DD_OPTION_IB,
+ DD_OPTION_OB,
+ DD_OPTION_SKIP,
+ DD_OPTION_SEEK,
+ DD_OPTION_BS,
+ DD_OPTION_QD,
+ DD_OPTION_COUNT,
+ DD_OPTION_AIO,
+};
+
+static struct option g_cmdline_opts[] = {
+ {
+ .name = "if",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_IF,
+ },
+ {
+ .name = "of",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_OF,
+ },
+ {
+ .name = "iflag",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_IFLAGS,
+ },
+ {
+ .name = "oflag",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_OFLAGS,
+ },
+ {
+ .name = "ib",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_IB,
+ },
+ {
+ .name = "ob",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_OB,
+ },
+ {
+ .name = "skip",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_SKIP,
+ },
+ {
+ .name = "seek",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_SEEK,
+ },
+ {
+ .name = "bs",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_BS,
+ },
+ {
+ .name = "qd",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_QD,
+ },
+ {
+ .name = "count",
+ .has_arg = 1,
+ .flag = NULL,
+ .val = DD_OPTION_COUNT,
+ },
+ {
+ .name = "aio",
+ .has_arg = 0,
+ .flag = NULL,
+ .val = DD_OPTION_AIO,
+ },
+ {
+ .name = NULL
+ }
+};
+
+static void
+usage(void)
+{
+ printf("[--------- DD Options ---------]\n");
+ printf(" --if Input file. Must specify either --if or --ib.\n");
+ printf(" --ib Input bdev. Must specifier either --if or --ib\n");
+ printf(" --of Output file. Must specify either --of or --ob.\n");
+ printf(" --ob Output bdev. Must specify either --of or --ob.\n");
+ printf(" --iflag Input file flags.\n");
+ printf(" --oflag Onput file flags.\n");
+ printf(" --bs I/O unit size (default: %" PRId64 ")\n", g_opts.io_unit_size);
+ printf(" --qd Queue depth (default: %d)\n", g_opts.queue_depth);
+ printf(" --count I/O unit count. The number of I/O units to copy. (default: all)\n");
+ printf(" --skip Skip this many I/O units at start of input. (default: 0)\n");
+ printf(" --seek Skip this many I/O units at start of output. (default: 0)\n");
+ printf(" --aio Force usage of AIO. (by default io_uring is used if available)\n");
+ printf(" Available iflag and oflag values:\n");
+ printf(" append - append mode\n");
+ printf(" direct - use direct I/O for data\n");
+ printf(" directory - fail unless a directory\n");
+ printf(" dsync - use synchronized I/O for data\n");
+ printf(" noatime - do not update access time\n");
+ printf(" noctty - do not assign controlling terminal from file\n");
+ printf(" nofollow - do not follow symlinks\n");
+ printf(" nonblock - use non-blocking I/O\n");
+ printf(" sync - use synchronized I/O for data and metadata\n");
+}
+
+static int
+parse_args(int argc, char *argv)
+{
+ switch (argc) {
+ case DD_OPTION_IF:
+ g_opts.input_file = strdup(argv);
+ break;
+ case DD_OPTION_OF:
+ g_opts.output_file = strdup(argv);
+ break;
+ case DD_OPTION_IFLAGS:
+ g_opts.input_file_flags = strdup(argv);
+ break;
+ case DD_OPTION_OFLAGS:
+ g_opts.output_file_flags = strdup(argv);
+ break;
+ case DD_OPTION_IB:
+ g_opts.input_bdev = strdup(argv);
+ break;
+ case DD_OPTION_OB:
+ g_opts.output_bdev = strdup(argv);
+ break;
+ case DD_OPTION_SKIP:
+ g_opts.input_offset = spdk_strtol(optarg, 10);
+ break;
+ case DD_OPTION_SEEK:
+ g_opts.output_offset = spdk_strtol(optarg, 10);
+ break;
+ case DD_OPTION_BS:
+ g_opts.io_unit_size = spdk_strtol(optarg, 10);
+ break;
+ case DD_OPTION_QD:
+ g_opts.queue_depth = spdk_strtol(optarg, 10);
+ break;
+ case DD_OPTION_COUNT:
+ g_opts.io_unit_count = spdk_strtol(optarg, 10);
+ break;
+ case DD_OPTION_AIO:
+ g_opts.aio = true;
+ break;
+ default:
+ usage();
+ return 1;
+ }
+ return 0;
+}
+
+static void
+dd_free(void)
+{
+ uint32_t i;
+
+ free(g_opts.input_file);
+ free(g_opts.output_file);
+ free(g_opts.input_bdev);
+ free(g_opts.output_bdev);
+ free(g_opts.input_file_flags);
+ free(g_opts.output_file_flags);
+
+#ifdef SPDK_CONFIG_URING
+ if (g_opts.aio == false) {
+ if (g_job.input.type == DD_TARGET_TYPE_FILE && g_job.input.open == true) {
+ io_uring_queue_exit(&g_job.input.u.uring.ring);
+ }
+
+ if (g_job.output.type == DD_TARGET_TYPE_FILE && g_job.output.open == true) {
+ io_uring_queue_exit(&g_job.output.u.uring.ring);
+ }
+ }
+#endif
+
+ if (g_job.ios) {
+ for (i = 0; i < g_opts.queue_depth; i++) {
+ spdk_free(g_job.ios[i].buf);
+ }
+
+ free(g_job.ios);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ struct spdk_app_opts opts = {};
+ int rc = 1;
+
+ spdk_app_opts_init(&opts);
+ opts.name = "spdk_dd";
+ opts.reactor_mask = "0x1";
+ opts.shutdown_cb = dd_finish;
+ rc = spdk_app_parse_args(argc, argv, &opts, "", g_cmdline_opts, parse_args, usage);
+ if (rc == SPDK_APP_PARSE_ARGS_FAIL) {
+ SPDK_ERRLOG("Invalid arguments\n");
+ goto end;
+ } else if (rc == SPDK_APP_PARSE_ARGS_HELP) {
+ goto end;
+ }
+
+ if (g_opts.input_file != NULL && g_opts.input_bdev != NULL) {
+ SPDK_ERRLOG("You may specify either --if or --ib, but not both.\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ if (g_opts.output_file != NULL && g_opts.output_bdev != NULL) {
+ SPDK_ERRLOG("You may specify either --of or --ob, but not both.\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ if (g_opts.input_file == NULL && g_opts.input_bdev == NULL) {
+ SPDK_ERRLOG("You must specify either --if or --ib\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ if (g_opts.output_file == NULL && g_opts.output_bdev == NULL) {
+ SPDK_ERRLOG("You must specify either --of or --ob\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ if (g_opts.io_unit_size <= 0) {
+ SPDK_ERRLOG("Invalid --bs value\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ if (g_opts.io_unit_count < 0) {
+ SPDK_ERRLOG("Invalid --count value\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ if (g_opts.output_file == NULL && g_opts.output_file_flags != NULL) {
+ SPDK_ERRLOG("--oflags may be used only with --of\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ if (g_opts.input_file == NULL && g_opts.input_file_flags != NULL) {
+ SPDK_ERRLOG("--iflags may be used only with --if\n");
+ rc = EINVAL;
+ goto end;
+ }
+
+ rc = spdk_app_start(&opts, dd_run, NULL);
+ if (rc) {
+ SPDK_ERRLOG("Error occured while performing copy\n");
+ }
+
+ dd_free();
+ spdk_app_fini();
+
+end:
+ return rc;
+}