/*
* Copyright 2018, Intel Corporation
*
* 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 the copyright holder 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.
*/
/*
* daxio.c -- simple app for reading and writing data from/to
* Device DAX device using mmap instead of file I/O API
*/
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <limits.h>
#include <string.h>
#include <ndctl/libndctl.h>
#include <ndctl/libdaxctl.h>
#include <libpmem.h>
#include "util.h"
#include "os_dimm.h"
#define ALIGN_UP(size, align) (((size) + (align) - 1) & ~((align) - 1))
#define ALIGN_DOWN(size, align) ((size) & ~((align) - 1))
#define ERR(fmt, ...)\
do {\
fprintf(stderr, "daxio: " fmt, ##__VA_ARGS__);\
} while (0)
#define FAIL(func)\
do {\
fprintf(stderr, "daxio: %s:%d: %s: %s\n",\
__func__, __LINE__, func, strerror(errno));\
} while (0)
#define USAGE_MESSAGE \
"Usage: daxio [option] ...\n"\
"Valid options:\n"\
" -i, --input=FILE - input device/file (default stdin)\n"\
" -o, --output=FILE - output device/file (default stdout)\n"\
" -k, --skip=BYTES - skip offset for input (default 0)\n"\
" -s, --seek=BYTES - seek offset for output (default 0)\n"\
" -l, --len=BYTES - total length to perform the I/O\n"\
" -b, --clear-bad-blocks=<yes|no> - clear bad blocks (default: yes)\n"\
" -z, --zero - zeroing the device\n"\
" -h. --help - print this help\n"\
" -V, --version - display version of daxio\n"
struct daxio_device {
char *path;
int fd;
size_t size; /* actual file/device size */
int is_devdax;
/* Device DAX only */
size_t align; /* internal device alignment */
char *addr; /* mapping base address */
size_t maplen; /* mapping length */
size_t offset; /* seek or skip */
unsigned major;
unsigned minor;
struct ndctl_ctx *ndctl_ctx;
struct ndctl_region *region; /* parent region */
};
/*
* daxio_context -- context and arguments
*/
struct daxio_context {
size_t len; /* total length of I/O */
int zero;
int clear_bad_blocks;
struct daxio_device src;
struct daxio_device dst;
};
/*
* default context
*/
static struct daxio_context Ctx = {
SIZE_MAX, /* len */
0, /* zero */
1, /* clear_bad_blocks */
{ NULL, -1, SIZE_MAX, 0, 0, NULL, 0, 0, 0, 0, NULL, NULL },
{ NULL, -1, SIZE_MAX, 0, 0, NULL, 0, 0, 0, 0, NULL, NULL },
};
/*
* print_version -- print daxio version
*/
static void
print_version(void)
{
printf("%s\n", SRCVERSION);
}
/*
* print_usage -- print short description of usage
*/
static void
print_usage(void)
{
fprintf(stderr, USAGE_MESSAGE);
}
/*
* long_options -- command line options
*/
static const struct option long_options[] = {
{"input", required_argument, NULL, 'i'},
{"output", required_argument, NULL, 'o'},
{"skip", required_argument, NULL, 'k'},
{"seek", required_argument, NULL, 's'},
{"len", required_argument, NULL, 'l'},
{"clear-bad-blocks", required_argument, NULL, 'b'},
{"zero", no_argument, NULL, 'z'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0 },
};
/*
* parse_args -- (internal) parse command line arguments
*/
static int
parse_args(struct daxio_context *ctx, int argc, char * const argv[])
{
int opt;
size_t offset;
size_t len;
while ((opt = getopt_long(argc, argv, "i:o:k:s:l:b:zhV",
long_options, NULL)) != -1) {
switch (opt) {
case 'i':
ctx->src.path = optarg;
break;
case 'o':
ctx->dst.path = optarg;
break;
case 'k':
if (util_parse_size(optarg, &offset)) {
ERR("'%s' -- invalid input offset\n", optarg);
return -1;
}
ctx->src.offset = offset;
break;
case 's':
if (util_parse_size(optarg, &offset)) {
ERR("'%s' -- invalid output offset\n", optarg);
return -1;
}
ctx->dst.offset = offset;
break;
case 'l':
if (util_parse_size(optarg, &len)) {
ERR("'%s' -- invalid length\n", optarg);
return -1;
}
ctx->len = len;
break;
case 'z':
ctx->zero = 1;
break;
case 'b':
if (strcmp(optarg, "no") == 0) {
ctx->clear_bad_blocks = 0;
} else if (strcmp(optarg, "yes") == 0) {
ctx->clear_bad_blocks = 1;
} else {
ERR(
"'%s' -- invalid argument of the '--clear-bad-blocks' option\n",
optarg);
return -1;
}
break;
case 'h':
print_usage();
exit(EXIT_SUCCESS);
case 'V':
print_version();
exit(EXIT_SUCCESS);
default:
print_usage();
exit(EXIT_FAILURE);
}
}
return 0;
}
/*
* validate_args -- (internal) validate command line arguments
*/
static int
validate_args(struct daxio_context *ctx)
{
if (ctx->zero && ctx->dst.path == NULL) {
ERR("zeroing flag specified but no output file provided\n");
return -1;
}
if (!ctx->zero && ctx->src.path == NULL && ctx->dst.path == NULL) {
ERR("an input file and/or an output file must be provided\n");
return -1;
}
/* if no input file provided, use stdin */
if (ctx->src.path == NULL) {
if (ctx->src.offset != 0) {
ERR(
"skip offset specified but no input file provided\n");
return -1;
}
ctx->src.fd = STDIN_FILENO;
ctx->src.path = "STDIN";
}
/* if no output file provided, use stdout */
if (ctx->dst.path == NULL) {
if (ctx->dst.offset != 0) {
ERR(
"seek offset specified but no output file provided\n");
return -1;
}
ctx->dst.fd = STDOUT_FILENO;
ctx->dst.path = "STDOUT";
}
return 0;
}
/*
* match_dev_dax -- (internal) find Device DAX by major/minor device number
*/
static int
match_dev_dax(struct daxio_device *dev, struct daxctl_region *dax_region)
{
struct daxctl_dev *d;
daxctl_dev_foreach(dax_region, d) {
if (dev->major == (unsigned)daxctl_dev_get_major(d) &&
dev->minor == (unsigned)daxctl_dev_get_minor(d)) {
dev->size = daxctl_dev_get_size(d);
return 1;
}
}
return 0;
}
/*
* find_dev_dax -- (internal) check if device is Device DAX
*
* If there is matching Device DAX, find its region, size and alignment.
*/
static int
find_dev_dax(struct ndctl_ctx *ndctl_ctx, struct daxio_device *dev)
{
struct ndctl_bus *bus = NULL;
struct ndctl_region *region = NULL;
struct ndctl_dax *dax = NULL;
struct daxctl_region *dax_region = NULL;
ndctl_bus_foreach(ndctl_ctx, bus) {
ndctl_region_foreach(bus, region) {
ndctl_dax_foreach(region, dax) {
dax_region = ndctl_dax_get_daxctl_region(dax);
if (match_dev_dax(dev, dax_region)) {
dev->is_devdax = 1;
dev->align = ndctl_dax_get_align(dax);
dev->region = region;
return 1;
}
}
}
}
/* try with dax regions */
struct daxctl_ctx *daxctl_ctx;
if (daxctl_new(&daxctl_ctx))
return 0;
int ret = 0;
daxctl_region_foreach(daxctl_ctx, dax_region) {
if (match_dev_dax(dev, dax_region)) {
dev->is_devdax = 1;
dev->align = daxctl_region_get_align(dax_region);
dev->region = region;
ret = 1;
goto end;
}
}
end:
daxctl_unref(daxctl_ctx);
return ret;
}
/*
* setup_device -- (internal) open/mmap file/device
*/
static int
setup_device(struct ndctl_ctx *ndctl_ctx, struct daxio_device *dev, int is_dst,
int clear_bad_blocks)
{
int ret;
int flags = O_RDWR;
int prot = is_dst ? PROT_WRITE : PROT_READ;
if (dev->fd != -1) {
dev->size = SIZE_MAX;
return 0; /* stdin/stdout */
}
/* try to open file/device (if exists) */
dev->fd = open(dev->path, flags, S_IRUSR|S_IWUSR);
if (dev->fd == -1) {
ret = errno;
if (ret == ENOENT && is_dst) {
/* file does not exist - create it */
flags = O_CREAT|O_WRONLY|O_TRUNC;
dev->size = SIZE_MAX;
dev->fd = open(dev->path, flags, S_IRUSR|S_IWUSR);
if (dev->fd == -1) {
FAIL("open");
return -1;
}
return 0;
} else {
ERR("failed to open '%s': %s\n", dev->path,
strerror(errno));
return -1;
}
}
struct stat stbuf;
ret = fstat(dev->fd, &stbuf);
if (ret == -1) {
FAIL("stat");
return -1;
}
/* check if this is regular file or device */
if (S_ISREG(stbuf.st_mode)) {
if (is_dst)
dev->size = SIZE_MAX;
else
dev->size = (size_t)stbuf.st_size;
} else if (S_ISBLK(stbuf.st_mode)) {
dev->size = (size_t)stbuf.st_size;
} else if (S_ISCHR(stbuf.st_mode)) {
dev->size = SIZE_MAX;
dev->major = major(stbuf.st_rdev);
dev->minor = minor(stbuf.st_rdev);
} else {
return -1;
}
/* check if this is Device DAX */
if (S_ISCHR(stbuf.st_mode))
find_dev_dax(ndctl_ctx, dev);
if (!dev->is_devdax)
return 0;
if (is_dst && clear_bad_blocks) {
/* XXX - clear only badblocks in range bound by offset/len */
if (os_dimm_devdax_clear_badblocks_all(dev->path)) {
ERR("failed to clear bad blocks on \"%s\"\n"
" Probably you have not enough permissions to do that.\n"
" You can choose one of three options now:\n"
" 1) run 'daxio' with 'sudo' or as 'root',\n"
" 2) turn off clearing bad blocks using\n"
" the '-b/--clear-bad-blocks=no' option or\n"
" 3) change permissions of some resource files -\n"
" - for details see the description of the CHECK_BAD_BLOCKS\n"
" compat feature in the pmempool-feature(1) man page.\n",
dev->path);
return -1;
}
}
if (dev->align == ULONG_MAX) {
ERR("cannot determine device alignment for \"%s\"\n",
dev->path);
return -1;
}
if (dev->offset > dev->size) {
ERR("'%zu' -- offset beyond device size (%zu)\n",
dev->offset, dev->size);
return -1;
}
/* align len/offset to the internal device alignment */
dev->maplen = ALIGN_UP(dev->size, dev->align);
size_t offset = ALIGN_DOWN(dev->offset, dev->align);
dev->offset = dev->offset - offset;
dev->maplen = dev->maplen - offset;
dev->addr = mmap(NULL, dev->maplen, prot, MAP_SHARED, dev->fd,
(off_t)offset);
if (dev->addr == MAP_FAILED) {
FAIL("mmap");
return -1;
}
return 0;
}
/*
* setup_devices -- (internal) open/mmap input and output
*/
static int
setup_devices(struct ndctl_ctx *ndctl_ctx, struct daxio_context *ctx)
{
if (!ctx->zero &&
setup_device(ndctl_ctx, &ctx->src, 0, ctx->clear_bad_blocks))
return -1;
return setup_device(ndctl_ctx, &ctx->dst, 1, ctx->clear_bad_blocks);
}
/*
* adjust_io_len -- (internal) calculate I/O length if not specified
*/
static void
adjust_io_len(struct daxio_context *ctx)
{
size_t src_len = ctx->src.maplen - ctx->src.offset;
size_t dst_len = ctx->dst.maplen - ctx->dst.offset;
size_t max_len = SIZE_MAX;
if (ctx->zero)
assert(ctx->dst.is_devdax);
else
assert(ctx->src.is_devdax || ctx->dst.is_devdax);
if (ctx->src.is_devdax)
max_len = src_len;
if (ctx->dst.is_devdax)
max_len = max_len < dst_len ? max_len : dst_len;
/* if length is specified and is not bigger than mmaped region */
if (ctx->len != SIZE_MAX && ctx->len <= max_len)
return;
/* adjust len to device size */
ctx->len = max_len;
}
/*
* cleanup_device -- (internal) unmap/close file/device
*/
static void
cleanup_device(struct daxio_device *dev)
{
if (dev->addr)
(void) munmap(dev->addr, dev->maplen);
if (dev->path && dev->fd != -1)
(void) close(dev->fd);
}
/*
* cleanup_devices -- (internal) unmap/close input and output
*/
static void
cleanup_devices(struct daxio_context *ctx)
{
cleanup_device(&ctx->dst);
if (!ctx->zero)
cleanup_device(&ctx->src);
}
/*
* do_io -- (internal) write data to device/file
*/
static int
do_io(struct ndctl_ctx *ndctl_ctx, struct daxio_context *ctx)
{
ssize_t cnt = 0;
assert(ctx->src.is_devdax || ctx->dst.is_devdax);
if (ctx->zero) {
if (ctx->dst.offset > ctx->dst.maplen) {
ERR("output offset larger than device size");
return -1;
}
if (ctx->dst.offset + ctx->len > ctx->dst.maplen) {
ERR("output offset beyond device size");
return -1;
}
char *dst_addr = ctx->dst.addr + ctx->dst.offset;
pmem_memset_persist(dst_addr, 0, ctx->len);
cnt = (ssize_t)ctx->len;
} else if (ctx->src.is_devdax && ctx->dst.is_devdax) {
/* memcpy between src and dst */
char *src_addr = ctx->src.addr + ctx->src.offset;
char *dst_addr = ctx->dst.addr + ctx->dst.offset;
pmem_memcpy_persist(dst_addr, src_addr, ctx->len);
cnt = (ssize_t)ctx->len;
} else if (ctx->src.is_devdax) {
/* write to file directly from mmap'ed src */
char *src_addr = ctx->src.addr + ctx->src.offset;
if (ctx->dst.offset) {
if (lseek(ctx->dst.fd, (off_t)ctx->dst.offset,
SEEK_SET) < 0) {
FAIL("lseek");
goto err;
}
}
do {
ssize_t wcnt = write(ctx->dst.fd, src_addr + cnt,
ctx->len - (size_t)cnt);
if (wcnt == -1) {
FAIL("write");
goto err;
}
cnt += wcnt;
} while ((size_t)cnt < ctx->len);
} else if (ctx->dst.is_devdax) {
/* read from file directly to mmap'ed dst */
char *dst_addr = ctx->dst.addr + ctx->dst.offset;
if (ctx->src.offset) {
if (lseek(ctx->src.fd, (off_t)ctx->src.offset,
SEEK_SET) < 0) {
FAIL("lseek");
return -1;
}
}
do {
ssize_t rcnt = read(ctx->src.fd, dst_addr + cnt,
ctx->len - (size_t)cnt);
if (rcnt == -1) {
FAIL("read");
goto err;
}
/* end of file */
if (rcnt == 0)
break;
cnt = cnt + rcnt;
} while ((size_t)cnt < ctx->len);
pmem_persist(dst_addr, (size_t)cnt);
if ((size_t)cnt != ctx->len)
ERR("requested size %zu larger than source\n",
ctx->len);
}
ERR("copied %zd bytes to device \"%s\"\n", cnt, ctx->dst.path);
return 0;
err:
ERR("failed to perform I/O\n");
return -1;
}
int
main(int argc, char **argv)
{
struct ndctl_ctx *ndctl_ctx;
int ret = EXIT_SUCCESS;
if (parse_args(&Ctx, argc, argv))
return EXIT_FAILURE;
if (validate_args(&Ctx))
return EXIT_FAILURE;
if (ndctl_new(&ndctl_ctx))
return EXIT_FAILURE;
if (setup_devices(ndctl_ctx, &Ctx)) {
ret = EXIT_FAILURE;
goto err;
}
if (!Ctx.src.is_devdax && !Ctx.dst.is_devdax) {
ERR("neither input nor output is device dax\n");
ret = EXIT_FAILURE;
goto err;
}
adjust_io_len(&Ctx);
if (do_io(ndctl_ctx, &Ctx))
ret = EXIT_FAILURE;
err:
cleanup_devices(&Ctx);
ndctl_unref(ndctl_ctx);
return ret;
}