Spdk/app/spdk_dd/spdk_dd.c
Jim Harris 488570ebd4 Replace most BSD 3-clause license text with SPDX identifier.
Many open source projects have moved to using SPDX identifiers
to specify license information, reducing the amount of
boilerplate code in every source file.  This patch replaces
the bulk of SPDK .c, .cpp and Makefiles with the BSD-3-Clause
identifier.

Almost all of these files share the exact same license text,
and this patch only modifies the files that contain the
most common license text.  There can be slight variations
because the third clause contains company names - most say
"Intel Corporation", but there are instances for Nvidia,
Samsung, Eideticom and even "the copyright holder".

Used a bash script to automate replacement of the license text
with SPDX identifier which is checked into scripts/spdx.sh.

Signed-off-by: Jim Harris <james.r.harris@intel.com>
Change-Id: Iaa88ab5e92ea471691dc298cfe41ebfb5d169780
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/12904
Community-CI: Broadcom CI <spdk-ci.pdl@broadcom.com>
Community-CI: Mellanox Build Bot
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Aleksey Marchuk <alexeymar@nvidia.com>
Reviewed-by: Changpeng Liu <changpeng.liu@intel.com>
Reviewed-by: Dong Yi <dongx.yi@intel.com>
Reviewed-by: Konrad Sztyber <konrad.sztyber@intel.com>
Reviewed-by: Paul Luse <paul.e.luse@intel.com>
Reviewed-by: <qun.wan@intel.com>
2022-06-09 07:35:12 +00:00

1135 lines
25 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (c) Intel Corporation.
* All rights reserved.
*/
#include "spdk/stdinc.h"
#include "spdk/bdev.h"
#include "spdk/event.h"
#include "spdk/fd.h"
#include "spdk/string.h"
#include "spdk/util.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;
} uring;
#endif
struct {
int fd;
} 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;
union {
#ifdef SPDK_CONFIG_URING
struct {
struct io_uring ring;
bool active;
struct spdk_poller *poller;
} uring;
#endif
struct {
io_context_t io_ctx;
struct spdk_poller *poller;
} aio;
} u;
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) {
close(g_job.input.u.uring.fd);
} else
#endif
{
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) {
close(g_job.output.u.uring.fd);
} else
#endif
{
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);
}
if (g_job.input.type == DD_TARGET_TYPE_FILE || g_job.output.type == DD_TARGET_TYPE_FILE) {
#ifdef SPDK_CONFIG_URING
if (g_opts.aio == false) {
spdk_poller_unregister(&g_job.u.uring.poller);
} else
#endif
{
spdk_poller_unregister(&g_job.u.aio.poller);
}
}
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 right 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 right 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(&g_job.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(&g_job.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(g_job.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(g_job.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(g_job.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 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(&g_job.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(&g_job.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(void *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(g_job.u.aio.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_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 (target->total_size == 0) {
target->total_size = g_opts.io_unit_size * g_opts.io_unit_count;
}
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);
}
return 0;
}
static void
dd_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev,
void *event_ctx)
{
SPDK_NOTICELOG("Unsupported bdev event: type %d\n", type);
}
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;
rc = spdk_bdev_open_ext(bdev_name, true, dd_bdev_event_cb, NULL, &target->u.bdev.desc);
if (rc < 0) {
SPDK_ERRLOG("Could not open bdev %s: %s\n", bdev_name, strerror(-rc));
return rc;
}
target->u.bdev.bdev = spdk_bdev_desc_get_bdev(target->u.bdev.desc);
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;
}
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;
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;
}
} 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;
}
} 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;
}
}
if (g_opts.input_file || g_opts.output_file) {
#ifdef SPDK_CONFIG_URING
if (g_opts.aio == false) {
g_job.u.uring.poller = spdk_poller_register(dd_uring_poll, NULL, 0);
rc = io_uring_queue_init(g_opts.queue_depth * 2, &g_job.u.uring.ring, IORING_SETUP_SQPOLL);
if (rc) {
SPDK_ERRLOG("Failed to create io_uring: %d (%s)\n", rc, spdk_strerror(-rc));
dd_exit(rc);
return;
}
g_job.u.uring.active = true;
} else
#endif
{
g_job.u.aio.poller = spdk_poller_register(dd_aio_poll, NULL, 0);
io_setup(g_opts.queue_depth, &g_job.u.aio.io_ctx);
}
}
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 Output 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);
if (g_job.input.type == DD_TARGET_TYPE_FILE || g_job.output.type == DD_TARGET_TYPE_FILE) {
#ifdef SPDK_CONFIG_URING
if (g_opts.aio == false) {
if (g_job.u.uring.active) {
io_uring_queue_exit(&g_job.u.uring.ring);
}
} else
#endif
{
io_destroy(g_job.u.aio.io_ctx);
}
}
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, sizeof(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 occurred while performing copy\n");
}
dd_free();
spdk_app_fini();
end:
return rc;
}