diff --git a/examples/blob/Makefile b/examples/blob/Makefile index afb5e9b57..a297ddb26 100644 --- a/examples/blob/Makefile +++ b/examples/blob/Makefile @@ -34,7 +34,7 @@ SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) include $(SPDK_ROOT_DIR)/mk/spdk.common.mk -DIRS-y += hello_world +DIRS-y += hello_world cli .PHONY: all clean $(DIRS-y) diff --git a/examples/blob/cli/.gitignore b/examples/blob/cli/.gitignore new file mode 100644 index 000000000..6c895d79b --- /dev/null +++ b/examples/blob/cli/.gitignore @@ -0,0 +1 @@ +blobcli diff --git a/examples/blob/cli/Makefile b/examples/blob/cli/Makefile new file mode 100644 index 000000000..440866b05 --- /dev/null +++ b/examples/blob/cli/Makefile @@ -0,0 +1,56 @@ +# +# 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.app.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = blobcli + +C_SRCS := blobcli.c + +SPDK_LIB_LIST = event_bdev event_copy +SPDK_LIB_LIST += blobfs blob bdev blob_bdev copy event util conf trace \ + log jsonrpc json rpc + +LIBS += $(COPY_MODULES_LINKER_ARGS) $(BLOCKDEV_MODULES_LINKER_ARGS) +LIBS += $(SPDK_LIB_LINKER_ARGS) $(ENV_LINKER_ARGS) + +all : $(APP) + +$(APP) : $(OBJS) $(SPDK_LIB_FILES) + $(LINK_C) + +clean : + $(CLEAN_C) $(APP) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/examples/blob/cli/blobcli.c b/examples/blob/cli/blobcli.c new file mode 100644 index 000000000..e7e960d35 --- /dev/null +++ b/examples/blob/cli/blobcli.c @@ -0,0 +1,1165 @@ +/*- + * 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/env.h" +#include "spdk/event.h" +#include "spdk/blob_bdev.h" +#include "spdk/blob.h" +#include "spdk/log.h" +#include "spdk/version.h" + +/* + * This is not a public header file, but the CLI does expose + * some internals of blobstore for dev/debug puposes so we + * include it here. + */ +#include "../lib/blob/blobstore.h" + +static const char *program_name = "blobcli"; +static const char *program_conf = "blobcli.conf"; +static const char *bdev_name = "Nvme0n1"; + +enum cli_action_type { + CLI_IMPORT, + CLI_DUMP, + CLI_FILL, + CLI_REM_XATTR, + CLI_SET_XATTR, + CLI_SET_SUPER, + CLI_SHOW_BS, + CLI_SHOW_BLOB, + CLI_CREATE_BLOB, + CLI_LIST_BDEVS, + CLI_LIST_BLOBS, + CLI_INIT_BS +}; +#define BUFSIZE 255 + +/* todo, scrub this as there may be some extra junk in here picked up along the way... */ +struct cli_context_t { + struct spdk_blob_store *bs; + struct spdk_blob *blob; + spdk_blob_id blobid; + spdk_blob_id superid; + struct spdk_io_channel *channel; + uint8_t *buff; + uint64_t page_size; + uint64_t page_count; + uint64_t blob_pages; + uint64_t bytes_so_far; + FILE *fp; + enum cli_action_type action; + char key[BUFSIZE + 1]; + char value[BUFSIZE + 1]; + char file[BUFSIZE + 1]; + uint64_t filesize; + int fill_value; + const char *bdev_name; + int rc; + int num_clusters; + void (*next_func)(void *arg1, struct spdk_blob_store *bs, int bserrno); +}; + +/* + * Prints usage and relevant error message. + */ +static void +usage(char *msg) +{ + if (msg) { + printf("%s", msg); + } + printf("Version %s\n", SPDK_VERSION_STRING); + printf("Usage: %s [-c SPDK config_file] Command\n", program_name); + printf("\n%s is a command line tool for interacting with blobstore\n", + program_name); + printf("on the underlying device specified in the conf file passed\n"); + printf("in as a command line option.\n"); + printf("\nCommands include:\n"); + printf("\t-i - initialize a blobstore\n"); + printf("\t-l bdevs | blobs - list either available bdevs or existing blobs\n"); + printf("\t-n <# clusters> - create new blob\n"); + printf("\t-p - set the superblob to the ID provided\n"); + printf("\t-s | bs - show blob info or blobstore info\n"); + printf("\t-x name value - set xattr name/value pair\n"); + printf("\t-r name - remove xattr name/value pair\n"); + printf("\t-f value - fill a blob with a decimal value\n"); + printf("\t-d filename - dump contents of a blob to a file\n"); + printf("\t-m filename - import contents of a file to a blob\n"); + printf("\n"); +} + +/* + * Free up memory that we allocated. + */ +static void +cli_cleanup(struct cli_context_t *cli_context) +{ + if (cli_context->buff) { + spdk_dma_free(cli_context->buff); + } + if (cli_context->channel) { + spdk_bs_free_io_channel(cli_context->channel); + } + free(cli_context); +} + +/* + * Callback routine for the blobstore unload. + */ +static void +unload_complete(void *cb_arg, int bserrno) +{ + struct cli_context_t *cli_context = cb_arg; + + if (bserrno) { + printf("Error %d unloading the bobstore\n", bserrno); + cli_context->rc = bserrno; + } + + spdk_app_stop(cli_context->rc); +} + +/* + * Unload the blobstore. + */ +static void +unload_bs(struct cli_context_t *cli_context, char *msg, int bserrno) +{ + if (bserrno) { + printf("%s (err %d)\n", msg, bserrno); + cli_context->rc = bserrno; + } + if (cli_context->bs) { + spdk_bs_unload(cli_context->bs, unload_complete, cli_context); + } else { + spdk_app_stop(bserrno); + } +} + +/* + * Callback for closing a blob. + */ +static void +close_cb(void *arg1, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in close callback", + bserrno); + return; + } + unload_bs(cli_context, "", 0); +} + +/* + * Callback function for sync'ing metadata. + */ +static void +sync_complete(void *arg1, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in sync callback", + bserrno); + return; + } + + spdk_bs_md_close_blob(&cli_context->blob, close_cb, + cli_context); +} + +/* + * Callback function for opening a blob after creating. + */ +static void +open_now_resize(void *cb_arg, struct spdk_blob *blob, int bserrno) +{ + struct cli_context_t *cli_context = cb_arg; + int rc = 0; + uint64_t total = 0; + + if (bserrno) { + unload_bs(cli_context, "Error in open completion", + bserrno); + return; + } + cli_context->blob = blob; + + rc = spdk_bs_md_resize_blob(cli_context->blob, + cli_context->num_clusters); + if (rc) { + unload_bs(cli_context, "Error in blob resize", + bserrno); + return; + } + + total = spdk_blob_get_num_clusters(cli_context->blob); + printf("blob now has USED clusters of %" PRIu64 "\n", + total); + + /* + * Always a good idea to sync after MD changes or the changes + * may be lost if things aren't closed cleanly. + */ + spdk_bs_md_sync_blob(cli_context->blob, sync_complete, + cli_context); +} + +/* + * Callback function for creating a blob. + */ +static void +blob_create_complete(void *arg1, spdk_blob_id blobid, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in blob create callback", + bserrno); + return; + } + + cli_context->blobid = blobid; + printf("New blob id %" PRIu64 "\n", cli_context->blobid); + + /* We have to open the blob before we can do things like resize. */ + spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, + open_now_resize, cli_context); +} + +/* + * Callback for get_super where we'll continue on to show blobstore info. + */ +static void +show_bs(void *arg1, spdk_blob_id blobid, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + uint64_t val; + struct spdk_bdev *bdev = NULL; + + if (bserrno && bserrno != -ENOENT) { + unload_bs(cli_context, "Error in get_super callback", + bserrno); + return; + } + cli_context->superid = blobid; + + bdev = spdk_bdev_get_by_name(cli_context->bdev_name); + if (bdev == NULL) { + unload_bs(cli_context, "Error w/bdev in get_super callback", + bserrno); + return; + } + + printf("Blobstore Public Info:\n"); + printf("\tUsing bdev Product Name: %s\n", + spdk_bdev_get_product_name(bdev)); + printf("\tAPI Version: %d\n", SPDK_BS_VERSION); + + if (bserrno != -ENOENT) { + printf("\tsuper blob ID: %" PRIu64 "\n", cli_context->superid); + } else { + printf("\tsuper blob ID: none assigned\n"); + } + + val = spdk_bs_get_page_size(cli_context->bs); + printf("\tpage size: %" PRIu64 "\n", val); + + val = spdk_bs_get_cluster_size(cli_context->bs); + printf("\tcluster size: %" PRIu64 "\n", val); + + val = spdk_bs_free_cluster_count(cli_context->bs); + printf("\t# free clusters: %" PRIu64 "\n", val); + + /* + * Private info isn't accessible via the public API but + * may be useful for debug of blobstore based applications. + */ + printf("\nBlobstore Private Info:\n"); + printf("\tMetadata start (pages): %" PRIu64 "\n", + cli_context->bs->md_start); + printf("\tMetadata length (pages): %d \n", + cli_context->bs->md_len); + + unload_bs(cli_context, "", 0); +} + +/* + * Load callback where we'll get the super blobid next. + */ +static void +get_super_load_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in load blob callback", + bserrno); + return; + } + cli_context->bs = bs; + + spdk_bs_get_super(cli_context->bs, show_bs, cli_context); +} + +/* + * Callback for load bs where we'll continue on to create a blob. + */ +static void +create_load_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in load callback", + bserrno); + return; + } + cli_context->bs = bs; + + spdk_bs_md_create_blob(cli_context->bs, blob_create_complete, + cli_context); +} + +/* + * Show detailed info about a particular blob. + */ +static void +show_blob(struct cli_context_t *cli_context) +{ + uint64_t val; + struct spdk_xattr_names *names; + const void *value; + size_t value_len; + char data[256]; + unsigned int i; + + printf("Blob Public Info:\n"); + + printf("blob ID: %" PRIu64 "\n", cli_context->blobid); + + val = spdk_blob_get_num_clusters(cli_context->blob); + printf("# of clusters: %" PRIu64 "\n", val); + + printf("# of bytes: %" PRIu64 "\n", + val * spdk_bs_get_cluster_size(cli_context->bs)); + + val = spdk_blob_get_num_pages(cli_context->blob); + printf("# of pages: %" PRIu64 "\n", val); + + spdk_bs_md_get_xattr_names(cli_context->blob, &names); + + printf("# of xattrs: %d\n", spdk_xattr_names_get_count(names)); + printf("xattrs:\n"); + for (i = 0; i < spdk_xattr_names_get_count(names); i++) { + spdk_bs_md_get_xattr_value(cli_context->blob, + spdk_xattr_names_get_name(names, i), + &value, &value_len); + if ((value_len + 1) > sizeof(data)) { + printf("FYI: adjusting size of xattr due to CLI limits.\n"); + value_len = sizeof(data) - 1; + } + memcpy(&data, value, value_len); + data[value_len] = '\0'; + printf("\n(%d) Name:%s\n", i, + spdk_xattr_names_get_name(names, i)); + printf("(%d) Value:\n", i); + spdk_trace_dump(stdout, "", value, value_len); + } + + /* + * Private info isn't accessible via the public API but + * may be useful for debug of blobstore based applications. + */ + printf("\nBlob Private Info:\n"); + switch (cli_context->blob->state) { + case SPDK_BLOB_STATE_DIRTY: + printf("state: DIRTY\n"); + break; + case SPDK_BLOB_STATE_CLEAN: + printf("state: CLEAN\n"); + break; + case SPDK_BLOB_STATE_LOADING: + printf("state: LOADING\n"); + break; + case SPDK_BLOB_STATE_SYNCING: + printf("state: SYNCING\n"); + break; + default: + printf("state: UNKNOWN\n"); + break; + } + printf("open ref count: %d\n", + cli_context->blob->open_ref); +} + +/* + * Callback for getting the first blob. + */ +static void +blob_iter_cb(void *arg1, struct spdk_blob *blob, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + if (bserrno == -ENOENT) { + /* this simply means there are no more blobs */ + unload_bs(cli_context, "", 0); + } else { + unload_bs(cli_context, "Error in blob iter callback", + bserrno); + } + return; + } + + if (cli_context->action == CLI_LIST_BLOBS) { + /* just listing blobs */ + printf("Found blob with ID# %" PRIu64 "\n", + spdk_blob_get_id(blob)); + } else if (spdk_blob_get_id(blob) == cli_context->blobid) { + /* + * Found the blob we're looking for, but we need to finish + * iterating even after showing the info so that internally + * the blobstore logic will close the blob. Or we could + * chose to close it now, either way. + */ + cli_context->blob = blob; + show_blob(cli_context); + } + + spdk_bs_md_iter_next(cli_context->bs, &blob, blob_iter_cb, + cli_context); +} + +/* + * Callback for load bs where we'll continue on to list all blobs. + */ +static void +list_load_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in load callback", + bserrno); + return; + } + cli_context->bs = bs; + + if (cli_context->action == CLI_LIST_BLOBS) { + printf("\nList BLOBS:\n"); + } + + spdk_bs_md_iter_first(cli_context->bs, blob_iter_cb, cli_context); +} + +/* + * Callback for setting the super blob ID. + */ +static void +set_super_cb(void *arg1, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in set_super callback", + bserrno); + return; + } + + printf("Super Blob ID has been set.\n"); + unload_bs(cli_context, "", 0); +} + +/* + * Callback for load bs where we'll continue on to set the super blob. + */ +static void +set_super_load_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in load callback", + bserrno); + return; + } + cli_context->bs = bs; + + spdk_bs_set_super(cli_context->bs, cli_context->superid, + set_super_cb, cli_context); +} + +/* + * Callback for set_xattr_open where we set or delete xattrs. + */ +static void +set_xattr(void *cb_arg, struct spdk_blob *blob, int bserrno) +{ + struct cli_context_t *cli_context = cb_arg; + + if (bserrno) { + unload_bs(cli_context, "Error in blob open callback", + bserrno); + return; + } + cli_context->blob = blob; + + if (cli_context->action == CLI_SET_XATTR) { + spdk_blob_md_set_xattr(cli_context->blob, + cli_context->key, + cli_context->value, + strlen(cli_context->value) + 1); + printf("Xattr has been set.\n"); + } else { + spdk_blob_md_remove_xattr(cli_context->blob, + cli_context->key); + printf("Xattr has been removed.\n"); + } + + spdk_bs_md_sync_blob(cli_context->blob, sync_complete, + cli_context); +} + +/* + * Callback for load bs where we'll continue on to set/del an xattr. + */ +static void +xattr_load_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in load callback", + bserrno); + return; + } + cli_context->bs = bs; + + spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, + set_xattr, cli_context); +} + +/* + * Callback function for reading a blob for dumping to a file. + */ +static void +read_dump_complete(void *arg1, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + uint64_t bytes_written; + + if (bserrno) { + fclose(cli_context->fp); + unload_bs(cli_context, "Error in read completion", + bserrno); + return; + } + + bytes_written = fwrite(cli_context->buff, 1, cli_context->page_size, + cli_context->fp); + if (bytes_written != cli_context->page_size) { + fclose(cli_context->fp); + unload_bs(cli_context, "Error with fwrite", + bserrno); + return; + } + + printf("."); + if (++cli_context->page_count < cli_context->blob_pages) { + /* perform another read */ + spdk_bs_io_read_blob(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->page_count, + 1, read_dump_complete, cli_context); + } else { + /* done reading */ + printf("\nFile write complete.\n"); + fclose(cli_context->fp); + spdk_bs_md_close_blob(&cli_context->blob, close_cb, + cli_context); + } +} + +/* + * Callback for write completion on the import of a file to a blob. + */ +static void +write_imp_complete(void *arg1, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + uint64_t bytes_read; + + if (bserrno) { + fclose(cli_context->fp); + unload_bs(cli_context, "Error in write completion", + bserrno); + return; + } + + if (cli_context->bytes_so_far < cli_context->filesize) { + /* perform another file read */ + bytes_read = fread(cli_context->buff, 1, + cli_context->page_size, + cli_context->fp); + cli_context->bytes_so_far += bytes_read; + + /* if this read is < 1 page, fill with 0s */ + if (bytes_read < cli_context->page_size) { + uint8_t *offset = cli_context->buff + bytes_read; + memset(offset, 0, + cli_context->page_size - bytes_read); + } + } else { + /* + * Done reading the file, fill the rest of the blob with 0s, + * yeah we're memsetting the same page over and over here + */ + memset(cli_context->buff, 0, cli_context->page_size); + } + if (++cli_context->page_count < cli_context->blob_pages) { + printf("."); + spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->page_count, + 1, write_imp_complete, cli_context); + } else { + /* done writing */ + printf("\nBlob import complete.\n"); + fclose(cli_context->fp); + spdk_bs_md_close_blob(&cli_context->blob, close_cb, + cli_context); + } +} + +/* + * Callback for open blobs where we'll continue on dump a blob to a file or + * import a file to a blob. For dump, the resulting file will always be the + * full size of the blob. For import, the blob will fill with the file + * contents first and then 0 out the rest of the blob. + */ +static void +dmpimp_open_cb(void *cb_arg, struct spdk_blob *blob, int bserrno) +{ + struct cli_context_t *cli_context = cb_arg; + + if (bserrno) { + unload_bs(cli_context, "Error in blob open callback", + bserrno); + return; + } + cli_context->blob = blob; + cli_context->page_size = spdk_bs_get_page_size(cli_context->bs); + cli_context->channel = spdk_bs_alloc_io_channel(cli_context->bs); + if (cli_context->channel == NULL) { + unload_bs(cli_context, "Error in allocating channel", + -ENOMEM); + return; + } + + /* + * We'll transfer just one page at a time to keep the buffer + * small. This could be bigger of course. + */ + cli_context->buff = spdk_dma_malloc(cli_context->page_size, + 0x1000, NULL); + if (cli_context->buff == NULL) { + unload_bs(cli_context, "Error in allocating memory", + -ENOMEM); + return; + } + printf("Working"); + cli_context->blob_pages = spdk_blob_get_num_pages(cli_context->blob); + cli_context->page_count = 0; + if (cli_context->action == CLI_DUMP) { + cli_context->fp = fopen(cli_context->file , "w"); + + /* read a page of data from the blob */ + spdk_bs_io_read_blob(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->page_count, + 1, read_dump_complete, cli_context); + } else { + cli_context->fp = fopen(cli_context->file , "r"); + + /* get the filesize then rewind read a page of data from file */ + fseek(cli_context->fp, 0L, SEEK_END); + cli_context->filesize = ftell(cli_context->fp); + rewind(cli_context->fp); + cli_context->bytes_so_far = fread(cli_context->buff, 1, + cli_context->page_size, + cli_context->fp); + + /* if the file is < a page, fill the rest with 0s */ + if (cli_context->filesize < cli_context->page_size) { + uint8_t *offset = + cli_context->buff + cli_context->filesize; + + memset(offset, 0, + cli_context->page_size - cli_context->filesize); + } + + spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->page_count, + 1, write_imp_complete, cli_context); + } +} + +/* + * Callback for load bs where we'll continue on dump a blob to a file or + * import a file to a blob. + */ +static void +dmpimp_load_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in load callback", + bserrno); + return; + } + cli_context->bs = bs; + + spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, + dmpimp_open_cb, cli_context); +} + +/* + * Callback function for writing a specific pattern to page 0. + */ +static void +write_complete(void *arg1, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in write completion", + bserrno); + return; + } + printf("."); + if (++cli_context->page_count < cli_context->blob_pages) { + spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->page_count, + 1, write_complete, cli_context); + } else { + /* done writing */ + printf("\nBlob fill complete.\n"); + spdk_bs_md_close_blob(&cli_context->blob, close_cb, + cli_context); + } + +} + +/* + * function to fill a blob with a value. + */ +static void +fill_blob(void *cb_arg, struct spdk_blob *blob, int bserrno) +{ + struct cli_context_t *cli_context = cb_arg; + + if (bserrno) { + unload_bs(cli_context, "Error in blob open callback", + bserrno); + return; + } + cli_context->blob = blob; + cli_context->page_count = 0; + cli_context->blob_pages = spdk_blob_get_num_pages(cli_context->blob); + cli_context->buff = spdk_dma_malloc(cli_context->page_size, + 0x1000, NULL); + if (cli_context->buff == NULL) { + unload_bs(cli_context, "Error in allocating memory", + -ENOMEM); + return; + } + + memset(cli_context->buff, cli_context->fill_value, + cli_context->page_size); + printf("\n"); + spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, + cli_context->buff, + 0, 1, write_complete, cli_context); +} + +/* + * Callback for load bs where we'll continue on to fill a blob. + */ +static void +fill_load_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in load callback", + bserrno); + return; + } + cli_context->bs = bs; + cli_context->page_size = spdk_bs_get_page_size(cli_context->bs); + cli_context->channel = spdk_bs_alloc_io_channel(cli_context->bs); + if (cli_context->channel == NULL) { + unload_bs(cli_context, "Error in allocating channel", + -ENOMEM); + return; + } + + spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, + fill_blob, cli_context); +} + +/* + * Multiple actions require us to open the bs first. A function pointer + * setup earlier will direct the callback accordingly. + */ +static void +load_bs(struct cli_context_t *cli_context) +{ + struct spdk_bdev *bdev = NULL; + struct spdk_bs_dev *bs_dev = NULL; + + bdev = spdk_bdev_get_by_name(cli_context->bdev_name); + if (bdev == NULL) { + printf("Could not find a bdev\n"); + spdk_app_stop(-1); + return; + } + + bs_dev = spdk_bdev_create_bs_dev(bdev); + if (bs_dev == NULL) { + printf("Could not create blob bdev!!\n"); + spdk_app_stop(-1); + return; + } + + spdk_bs_load(bs_dev, cli_context->next_func, cli_context); +} + +/* + * Lists all the blobs on this blobstore. + */ +static void +list_bdevs(void) +{ + struct spdk_bdev *bdev = NULL; + + printf("\nList bdevs:\n"); + + bdev = spdk_bdev_first(); + if (bdev == NULL) { + printf("Could not find a bdev\n"); + } + while (bdev) { + printf("\tbdev Name: %s\n", spdk_bdev_get_name(bdev)); + printf("\tbdev Product Name: %s\n", + spdk_bdev_get_product_name(bdev)); + bdev = spdk_bdev_next(bdev); + } + + printf("\n"); + spdk_app_stop(0); +} + +/* + * Callback function for initializing a blob. + */ +static void +bs_init_complete(void *cb_arg, struct spdk_blob_store *bs, + int bserrno) +{ + struct cli_context_t *cli_context = cb_arg; + + if (bserrno) { + unload_bs(cli_context, "Error in bs init callback", + bserrno); + return; + } + cli_context->bs = bs; + printf("blobstore init'd: (%p)\n", cli_context->bs); + + unload_bs(cli_context, "", 0); +} + +/* + * Initialize a new blobstore. + */ +static void +init_bs(struct cli_context_t *cli_context) +{ + struct spdk_bdev *bdev = NULL; + struct spdk_bs_dev *bs_dev = NULL; + + bdev = spdk_bdev_get_by_name(cli_context->bdev_name); + if (bdev == NULL) { + printf("Could not find a bdev\n"); + spdk_app_stop(-1); + return; + } + printf("Blobstore using bdev Product Name: %s\n", + spdk_bdev_get_product_name(bdev)); + + bs_dev = spdk_bdev_create_bs_dev(bdev); + if (bs_dev == NULL) { + printf("Could not create blob bdev!!\n"); + spdk_app_stop(-1); + return; + } + + spdk_bs_init(bs_dev, NULL, bs_init_complete, + cli_context); +} + +/* + * This is the function we pass into the SPDK framework that gets + * called first. + */ +static void +cli_start(void *arg1, void *arg2) +{ + struct cli_context_t *cli_context = arg1; + + printf("\n"); + + /* + * Decide what to do next based on cmd line parsing that + * happened earlier, in many cases we setup a function pointer + * to be used as a callback following a generic action like + * loading the blobstore. + */ + switch (cli_context->action) { + case CLI_SET_SUPER: + cli_context->next_func = &set_super_load_cb; + load_bs(cli_context); + break; + case CLI_SHOW_BS: + cli_context->next_func = &get_super_load_cb; + load_bs(cli_context); + break; + case CLI_CREATE_BLOB: + cli_context->next_func = &create_load_cb; + load_bs(cli_context); + break; + case CLI_SET_XATTR: + case CLI_REM_XATTR: + cli_context->next_func = &xattr_load_cb; + load_bs(cli_context); + break; + case CLI_SHOW_BLOB: + case CLI_LIST_BLOBS: + cli_context->next_func = &list_load_cb; + load_bs(cli_context); + break; + case CLI_DUMP: + case CLI_IMPORT: + cli_context->next_func = &dmpimp_load_cb; + load_bs(cli_context); + break; + case CLI_FILL: + cli_context->next_func = &fill_load_cb; + load_bs(cli_context); + break; + case CLI_INIT_BS: + init_bs(cli_context); + break; + case CLI_LIST_BDEVS: + list_bdevs(); + break; + default: + /* should never get here */ + spdk_app_stop(-1); + break; + } +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + struct cli_context_t *cli_context = NULL; + const char *config_file = NULL; + int rc = 0; + int op; + bool cmd_chosen = false; + + if (argc < 2) { + usage("ERROR: Invalid option\n"); + exit(1); + } + + cli_context = calloc(1, sizeof(struct cli_context_t)); + if (cli_context == NULL) { + printf("ERROR: could not allocate context structure\n"); + exit(-1); + } + cli_context->bdev_name = bdev_name; + + while ((op = getopt(argc, argv, "c:d:f:il:m:n:p:r:s:x:")) != -1) { + switch (op) { + case 'c': + config_file = optarg; + break; + case 'd': + cli_context->action = CLI_DUMP; + cli_context->blobid = atoll(optarg); + break; + case 'f': + cli_context->action = CLI_FILL; + cli_context->blobid = atoll(optarg); + break; + case 'i': + cli_context->action = CLI_INIT_BS; + break; + case 'r': + cli_context->action = CLI_REM_XATTR; + cli_context->blobid = atoll(optarg); + break; + case 'l': + if (strcmp("bdevs", optarg) == 0) { + cli_context->action = CLI_LIST_BDEVS; + } else if (strcmp("blobs", optarg) == 0) { + cli_context->action = CLI_LIST_BLOBS; + } else { + usage("ERROR: invalid option for list\n"); + cli_cleanup(cli_context); + exit(-1); + } + break; + case 'm': + cli_context->action = CLI_IMPORT; + cli_context->blobid = atoll(optarg); + break; + case 'n': + cli_context->num_clusters = atoi(optarg); + if (cli_context->num_clusters > 0) { + cli_context->action = CLI_CREATE_BLOB; + } else { + usage("ERROR: invalid option for new\n"); + cli_cleanup(cli_context); + exit(-1); + } + break; + case 'p': + cli_context->action = CLI_SET_SUPER; + cli_context->superid = atoll(optarg); + break; + case 's': + if (strcmp("bs", optarg) == 0) { + cli_context->action = CLI_SHOW_BS; + } else { + cli_context->action = CLI_SHOW_BLOB; + cli_context->blobid = atoll(optarg); + } + break; + case 'x': + cli_context->action = CLI_SET_XATTR; + cli_context->blobid = atoll(optarg); + break; + default: + usage("ERROR: Invalid option\n"); + cli_cleanup(cli_context); + exit(1); + } + /* config file is the only option that can be combined */ + if (op != 'c') { + if (cmd_chosen) { + usage("Error: Please choose only one command\n"); + cli_cleanup(cli_context); + exit(1); + } else { + cmd_chosen = true; + } + } + } + + if (cmd_chosen == false) { + usage("Error: Please choose a command.\n"); + exit(1); + } + + /* if they don't supply a conf name, use the default */ + if (!config_file) { + config_file = program_conf; + } + + /* if the config file doesn't exist, tell them how to make one */ + if (access(config_file, F_OK) == -1) { + printf("Error: No config file found.\n"); + printf("To create a config file named 'blobcli.conf' for your NVMe device:\n"); + printf(" /scripts/gen_nvme.sh > blobcli.conf\n"); + printf("and then re-run the cli tool.\n"); + exit(1); + } + + /* a few options require some extra paramters */ + if (cli_context->action == CLI_SET_XATTR || + cli_context->action == CLI_REM_XATTR) { + snprintf(cli_context->key, BUFSIZE, "%s", argv[3]); + snprintf(cli_context->value, BUFSIZE, "%s", argv[4]); + } + + if (cli_context->action == CLI_DUMP || + cli_context->action == CLI_IMPORT) { + snprintf(cli_context->file, BUFSIZE, "%s", argv[3]); + } + + if (cli_context->action == CLI_FILL) { + cli_context->fill_value = atoi(argv[3]); + } + + /* Set default values in opts struct along with name and conf file. */ + spdk_app_opts_init(&opts); + opts.name = "blobcli"; + opts.config_file = config_file; + + /* + * spdk_app_start() will block running cli_start() until + * spdk_app_stop() is called by someone (not simply when + * cli_start() returns) + */ + rc = spdk_app_start(&opts, cli_start, cli_context, NULL); + if (rc) { + printf("ERROR!\n"); + } + + /* Free up memory that we allocated */ + cli_cleanup(cli_context); + + /* Gracefully close out all of the SPDK subsystems. */ + spdk_app_fini(); + return rc; +}