diff options
Diffstat (limited to '')
-rw-r--r-- | src/spdk/examples/blob/cli/blobcli.c | 1575 |
1 files changed, 1575 insertions, 0 deletions
diff --git a/src/spdk/examples/blob/cli/blobcli.c b/src/spdk/examples/blob/cli/blobcli.c new file mode 100644 index 00000000..e0b843a8 --- /dev/null +++ b/src/spdk/examples/blob/cli/blobcli.c @@ -0,0 +1,1575 @@ +/*- + * 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" +#include "spdk/string.h" +#include "spdk/uuid.h" + +/* + * The following 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 void cli_start(void *arg1, void *arg2); + +static const char *program_name = "blobcli"; +/* default name for .conf file, any name can be used however with -c switch */ +static const char *program_conf = "blobcli.conf"; + +/* + * CMD mode runs one command at a time which can be annoying as the init takes + * a few seconds, so the shell mode, invoked with -S, does the init once and gives + * the user an interactive shell instead. With script mode init is also done just + * once. + */ +enum cli_mode_type { + CLI_MODE_CMD, + CLI_MODE_SHELL, + CLI_MODE_SCRIPT +}; + +enum cli_action_type { + CLI_NONE, + CLI_IMPORT_BLOB, + CLI_DUMP_BLOB, + 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, + CLI_DUMP_BS, + CLI_SHELL_EXIT, + CLI_HELP, +}; + +#define BUFSIZE 255 +#define MAX_ARGS 16 +#define ALIGN_4K 4096 +#define STARTING_IO_UNIT 0 +#define NUM_IO_UNITS 1 + +/* + * The CLI uses the SPDK app framework so is async and callback driven. A + * pointer to this structure is passed to SPDK calls and returned in the + * callbacks for easy access to all the info we may need. + */ +struct cli_context_t { + struct spdk_blob_store *bs; + struct spdk_blob *blob; + struct spdk_bs_dev *bs_dev; + spdk_blob_id blobid; + spdk_blob_id superid; + struct spdk_io_channel *channel; + uint8_t *buff; + uint64_t page_size; + uint64_t io_unit_size; + uint64_t io_unit_count; + uint64_t blob_io_units; + 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; + char bdev_name[BUFSIZE]; + int rc; + int num_clusters; + enum cli_mode_type cli_mode; + const char *config_file; + int argc; + char *argv[MAX_ARGS]; + bool app_started; + char script_file[BUFSIZE + 1]; +}; + +/* we store a bunch of stuff in a global struct for use by scripting mode */ +#define MAX_SCRIPT_LINES 64 +#define MAX_SCRIPT_BLOBS 16 +struct cli_script_t { + spdk_blob_id blobid[MAX_SCRIPT_BLOBS]; + int blobid_idx; + int max_index; + int cmdline_idx; + bool ignore_errors; + char *cmdline[MAX_SCRIPT_LINES]; +}; +struct cli_script_t g_script; + +/* + * Common printing of commands for CLI and shell modes. + */ +static void +print_cmds(void) +{ + printf("\nCommands include:\n"); + printf("\t-b bdev - name of the block device to use (example: Nvme0n1)\n"); + printf("\t-d <blobid> filename - dump contents of a blob to a file\n"); + printf("\t-D - dump metadata contents of an existing blobstore\n"); + printf("\t-f <blobid> value - fill a blob with a decimal value\n"); + printf("\t-h - this help screen\n"); + printf("\t-i - initialize a blobstore\n"); + printf("\t-l bdevs | blobs - list either available bdevs or existing blobs\n"); + printf("\t-m <blobid> filename - import contents of a file to a blob\n"); + printf("\t-n <# clusters> - create new blob\n"); + printf("\t-p <blobid> - set the superblob to the ID provided\n"); + printf("\t-r <blobid> name - remove xattr name/value pair\n"); + printf("\t-s <blobid> | bs - show blob info or blobstore info\n"); + printf("\t-x <blobid> name value - set xattr name/value pair\n"); + printf("\t-X - exit when in interactive shell mode\n"); + printf("\t-S - enter interactive shell mode\n"); + printf("\t-T <filename> - automated script mode\n"); + printf("\n"); +} + +/* + * Prints usage and relevant error message. + */ +static void +usage(struct cli_context_t *cli_context, char *msg) +{ + if (msg) { + printf("%s", msg); + } + + if (!cli_context || cli_context->cli_mode == CLI_MODE_CMD) { + 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"); + } + if (!cli_context || cli_context->cli_mode != CLI_MODE_SCRIPT) { + print_cmds(); + } +} + +/* + * 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->cli_mode == CLI_MODE_SCRIPT) { + int i; + + for (i = 0; i <= g_script.max_index; i++) { + free(g_script.cmdline[i]); + } + } + 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; + } + + /* + * Quit if we're in cmd mode or exiting shell mode, otherwise + * clear the action field and start the main function again. + */ + if (cli_context->cli_mode == CLI_MODE_CMD || + cli_context->action == CLI_SHELL_EXIT) { + spdk_app_stop(cli_context->rc); + } else { + /* when action is CLI_NONE, we know we need to remain in the shell */ + cli_context->bs = NULL; + cli_context->action = CLI_NONE; + cli_start(cli_context, NULL); + } +} + +/* + * 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) { + if (cli_context->channel) { + spdk_bs_free_io_channel(cli_context->channel); + cli_context->channel = NULL; + } + spdk_bs_unload(cli_context->bs, unload_complete, cli_context); + } else if (cli_context->cli_mode != CLI_MODE_SCRIPT) { + 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_cb(void *arg1, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in sync callback", + bserrno); + return; + } + + spdk_blob_close(cli_context->blob, close_cb, cli_context); +} + +static void +resize_cb(void *cb_arg, int bserrno) +{ + struct cli_context_t *cli_context = cb_arg; + uint64_t total = 0; + + if (bserrno) { + 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_blob_sync_md(cli_context->blob, sync_cb, cli_context); +} + +/* + * Callback function for opening a blob after creating. + */ +static void +open_now_resize_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 open completion", + bserrno); + return; + } + cli_context->blob = blob; + + spdk_blob_resize(cli_context->blob, cli_context->num_clusters, + resize_cb, cli_context); +} + +/* + * Callback function for creating a blob. + */ +static void +blob_create_cb(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); + + /* if we're in script mode, we need info on all blobids for later */ + if (cli_context->cli_mode == CLI_MODE_SCRIPT) { + g_script.blobid[g_script.blobid_idx++] = blobid; + } + + /* We have to open the blob before we can do things like resize. */ + spdk_bs_open_blob(cli_context->bs, cli_context->blobid, + open_now_resize_cb, cli_context); +} + +/* + * Callback for get_super where we'll continue on to show blobstore info. + */ +static void +show_bs_cb(void *arg1, spdk_blob_id blobid, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + struct spdk_bs_type bstype; + 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"); + } + + printf("\tpage size: %" PRIu64 "\n", cli_context->page_size); + printf("\tio unit size: %" PRIu64 "\n", cli_context->io_unit_size); + + 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); + + bstype = spdk_bs_get_bstype(cli_context->bs); + spdk_trace_dump(stdout, "\tblobstore type:", &bstype, sizeof(bstype)); + + /* + * 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); +} + +/* + * 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[BUFSIZE]; + 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_blob_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_blob_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; + default: + printf("state: UNKNOWN\n"); + break; + } + printf("open ref count: %d\n", + cli_context->blob->open_ref); + + spdk_xattr_names_free(names); +} + +/* + * Callback for getting the first blob, shared with simple blob listing as well. + */ +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) { + printf("\nList BLOBS:\n"); + 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_iter_next(cli_context->bs, blob, 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 set_xattr_open where we set or delete xattrs. + */ +static void +set_xattr_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; + + if (cli_context->action == CLI_SET_XATTR) { + spdk_blob_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_remove_xattr(cli_context->blob, cli_context->key); + printf("Xattr has been removed.\n"); + } + + spdk_blob_sync_md(cli_context->blob, sync_cb, cli_context); +} + +/* + * Callback function for reading a blob for dumping to a file. + */ +static void +read_dump_cb(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, NUM_IO_UNITS, cli_context->io_unit_size, + cli_context->fp); + if (bytes_written != cli_context->io_unit_size) { + fclose(cli_context->fp); + unload_bs(cli_context, "Error with fwrite", + bserrno); + return; + } + + printf("."); + if (++cli_context->io_unit_count < cli_context->blob_io_units) { + /* perform another read */ + spdk_blob_io_read(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->io_unit_count, + NUM_IO_UNITS, read_dump_cb, cli_context); + } else { + /* done reading */ + printf("\nFile write complete (to %s).\n", cli_context->file); + fclose(cli_context->fp); + spdk_blob_close(cli_context->blob, close_cb, cli_context); + } +} + +/* + * Callback for write completion on the import of a file to a blob. + */ +static void +write_imp_cb(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->io_unit_size, + cli_context->fp); + cli_context->bytes_so_far += bytes_read; + + /* if this read is < 1 io_unit, fill with 0s */ + if (bytes_read < cli_context->io_unit_size) { + uint8_t *offset = cli_context->buff + bytes_read; + memset(offset, 0, cli_context->io_unit_size - bytes_read); + } + } else { + /* + * Done reading the file, fill the rest of the blob with 0s, + * yeah we're memsetting the same io_unit over and over here + */ + memset(cli_context->buff, 0, cli_context->io_unit_size); + } + if (++cli_context->io_unit_count < cli_context->blob_io_units) { + printf("."); + spdk_blob_io_write(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->io_unit_count, + NUM_IO_UNITS, write_imp_cb, cli_context); + } else { + /* done writing */ + printf("\nBlob import complete (from %s).\n", cli_context->file); + fclose(cli_context->fp); + spdk_blob_close(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 +dump_imp_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; + + /* + * We'll transfer just one io_unit at a time to keep the buffer + * small. This could be bigger of course. + */ + cli_context->buff = spdk_dma_malloc(cli_context->io_unit_size, + ALIGN_4K, NULL); + if (cli_context->buff == NULL) { + printf("Error in allocating memory\n"); + spdk_blob_close(cli_context->blob, close_cb, cli_context); + return; + } + printf("Working"); + cli_context->blob_io_units = spdk_blob_get_num_io_units(cli_context->blob); + cli_context->io_unit_count = 0; + if (cli_context->action == CLI_DUMP_BLOB) { + cli_context->fp = fopen(cli_context->file, "w"); + if (cli_context->fp == NULL) { + printf("Error in opening file\n"); + spdk_blob_close(cli_context->blob, close_cb, cli_context); + return; + } + + /* read a io_unit of data from the blob */ + spdk_blob_io_read(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->io_unit_count, + NUM_IO_UNITS, read_dump_cb, cli_context); + } else { + cli_context->fp = fopen(cli_context->file, "r"); + if (cli_context->fp == NULL) { + printf("Error in opening file: errno %d\n", errno); + spdk_blob_close(cli_context->blob, close_cb, cli_context); + return; + } + + /* get the filesize then rewind read a io_unit 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, NUM_IO_UNITS, + cli_context->io_unit_size, + cli_context->fp); + + /* if the file is < a io_unit, fill the rest with 0s */ + if (cli_context->filesize < cli_context->io_unit_size) { + uint8_t *offset = + cli_context->buff + cli_context->filesize; + + memset(offset, 0, + cli_context->io_unit_size - cli_context->filesize); + } + + spdk_blob_io_write(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->io_unit_count, + NUM_IO_UNITS, write_imp_cb, cli_context); + } +} + +/* + * Callback function for writing a specific pattern to io_unit 0. + */ +static void +write_cb(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->io_unit_count < cli_context->blob_io_units) { + spdk_blob_io_write(cli_context->blob, cli_context->channel, + cli_context->buff, cli_context->io_unit_count, + NUM_IO_UNITS, write_cb, cli_context); + } else { + /* done writing */ + printf("\nBlob fill complete (with 0x%x).\n", cli_context->fill_value); + spdk_blob_close(cli_context->blob, close_cb, cli_context); + } +} + +/* + * Callback function to fill a blob with a value, callback from open. + */ +static void +fill_blob_cb(void *arg1, struct spdk_blob *blob, int bserrno) +{ + struct cli_context_t *cli_context = arg1; + + if (bserrno) { + unload_bs(cli_context, "Error in open callback", + bserrno); + return; + } + + cli_context->blob = blob; + cli_context->io_unit_count = 0; + cli_context->blob_io_units = spdk_blob_get_num_io_units(cli_context->blob); + cli_context->buff = spdk_dma_malloc(cli_context->io_unit_size, + ALIGN_4K, 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->io_unit_size); + printf("Working"); + spdk_blob_io_write(cli_context->blob, cli_context->channel, + cli_context->buff, + STARTING_IO_UNIT, NUM_IO_UNITS, write_cb, cli_context); +} + +/* + * Multiple actions require us to open the bs first so here we use + * a common callback to set a bunch of values and then move on to + * the next step saved off via function pointer. + */ +static void +load_bs_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->io_unit_size = spdk_bs_get_io_unit_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; + } + + switch (cli_context->action) { + case CLI_SET_SUPER: + spdk_bs_set_super(cli_context->bs, cli_context->superid, + set_super_cb, cli_context); + break; + case CLI_SHOW_BS: + spdk_bs_get_super(cli_context->bs, show_bs_cb, cli_context); + break; + case CLI_CREATE_BLOB: + spdk_bs_create_blob(cli_context->bs, blob_create_cb, cli_context); + break; + case CLI_SET_XATTR: + case CLI_REM_XATTR: + spdk_bs_open_blob(cli_context->bs, cli_context->blobid, + set_xattr_cb, cli_context); + break; + case CLI_SHOW_BLOB: + case CLI_LIST_BLOBS: + spdk_bs_iter_first(cli_context->bs, blob_iter_cb, cli_context); + + break; + case CLI_DUMP_BLOB: + case CLI_IMPORT_BLOB: + spdk_bs_open_blob(cli_context->bs, cli_context->blobid, + dump_imp_open_cb, cli_context); + break; + case CLI_FILL: + spdk_bs_open_blob(cli_context->bs, cli_context->blobid, + fill_blob_cb, cli_context); + break; + + default: + /* should never get here */ + exit(-1); + break; + } +} + +/* + * Load the blobstore. + */ +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, NULL, NULL); + if (bs_dev == NULL) { + printf("Could not create blob bdev!!\n"); + spdk_app_stop(-1); + return; + } + + spdk_bs_load(bs_dev, NULL, load_bs_cb, cli_context); +} + +/* + * Lists all the blobs on this blobstore. + */ +static void +list_bdevs(struct cli_context_t *cli_context) +{ + 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"); + if (cli_context->cli_mode == CLI_MODE_CMD) { + spdk_app_stop(0); + } else { + cli_context->action = CLI_NONE; + cli_start(cli_context, NULL); + } +} + +/* + * Callback function for initializing a blob. + */ +static void +bs_init_cb(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; + + 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("Init blobstore using bdev Product Name: %s\n", + spdk_bdev_get_product_name(bdev)); + + cli_context->bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); + if (cli_context->bs_dev == NULL) { + printf("Could not create blob bdev!!\n"); + spdk_app_stop(-1); + return; + } + + spdk_bs_init(cli_context->bs_dev, NULL, bs_init_cb, + cli_context); +} + +static void +spdk_bsdump_done(void *arg, int bserrno) +{ + struct cli_context_t *cli_context = arg; + + if (cli_context->cli_mode == CLI_MODE_CMD) { + spdk_app_stop(0); + } else { + cli_context->action = CLI_NONE; + cli_start(cli_context, NULL); + } +} + +static void +bsdump_print_xattr(FILE *fp, const char *bstype, const char *name, const void *value, + size_t value_len) +{ + if (strncmp(bstype, "BLOBFS", SPDK_BLOBSTORE_TYPE_LENGTH) == 0) { + if (strcmp(name, "name") == 0) { + fprintf(fp, "%.*s", (int)value_len, (char *)value); + } else if (strcmp(name, "length") == 0 && value_len == sizeof(uint64_t)) { + uint64_t length; + + memcpy(&length, value, sizeof(length)); + fprintf(fp, "%" PRIu64, length); + } else { + fprintf(fp, "?"); + } + } else if (strncmp(bstype, "LVOLSTORE", SPDK_BLOBSTORE_TYPE_LENGTH) == 0) { + if (strcmp(name, "name") == 0) { + fprintf(fp, "%s", (char *)value); + } else if (strcmp(name, "uuid") == 0 && value_len == sizeof(struct spdk_uuid)) { + char uuid[SPDK_UUID_STRING_LEN]; + + spdk_uuid_fmt_lower(uuid, sizeof(uuid), (struct spdk_uuid *)value); + fprintf(fp, "%s", uuid); + } else { + fprintf(fp, "?"); + } + } else { + fprintf(fp, "?"); + } +} + +/* + * Dump metadata of an existing blobstore in a human-readable format. + */ +static void +dump_bs(struct cli_context_t *cli_context) +{ + struct spdk_bdev *bdev = 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("Init blobstore using bdev Product Name: %s\n", + spdk_bdev_get_product_name(bdev)); + + cli_context->bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); + if (cli_context->bs_dev == NULL) { + printf("Could not create blob bdev!!\n"); + spdk_app_stop(-1); + return; + } + + spdk_bs_dump(cli_context->bs_dev, stdout, bsdump_print_xattr, spdk_bsdump_done, cli_context); +} + +/* + * Common cmd/option parser for command and shell modes. + */ +static bool +cmd_parser(int argc, char **argv, struct cli_context_t *cli_context) +{ + int op; + int cmd_chosen = 0; + char resp; + + while ((op = getopt(argc, argv, "b:c:d:f:hil:m:n:p:r:s:DST:Xx:")) != -1) { + switch (op) { + case 'b': + if (strcmp(cli_context->bdev_name, "") == 0) { + snprintf(cli_context->bdev_name, BUFSIZE, "%s", optarg); + } else { + printf("Current setting for -b is: %s\n", cli_context->bdev_name); + usage(cli_context, "ERROR: -b option can only be set once.\n"); + } + break; + case 'c': + if (cli_context->app_started == false) { + cli_context->config_file = optarg; + } else { + usage(cli_context, "ERROR: -c option not valid during shell mode.\n"); + } + break; + case 'D': + cmd_chosen++; + cli_context->action = CLI_DUMP_BS; + break; + case 'd': + if (argv[optind] != NULL) { + cmd_chosen++; + cli_context->action = CLI_DUMP_BLOB; + cli_context->blobid = atoll(optarg); + snprintf(cli_context->file, BUFSIZE, "%s", argv[optind]); + } else { + usage(cli_context, "ERROR: missing parameter.\n"); + } + break; + case 'f': + if (argv[optind] != NULL) { + cmd_chosen++; + cli_context->action = CLI_FILL; + cli_context->blobid = atoll(optarg); + cli_context->fill_value = atoi(argv[optind]); + } else { + usage(cli_context, "ERROR: missing parameter.\n"); + } + break; + case 'h': + cmd_chosen++; + cli_context->action = CLI_HELP; + break; + case 'i': + if (cli_context->cli_mode != CLI_MODE_SCRIPT) { + printf("Your entire blobstore will be destroyed. Are you sure? (y/n) "); + if (scanf("%c%*c", &resp)) { + if (resp == 'y' || resp == 'Y') { + cmd_chosen++; + cli_context->action = CLI_INIT_BS; + } else { + if (cli_context->cli_mode == CLI_MODE_CMD) { + spdk_app_stop(0); + return false; + } + } + } + } else { + cmd_chosen++; + cli_context->action = CLI_INIT_BS; + } + break; + case 'r': + if (argv[optind] != NULL) { + cmd_chosen++; + cli_context->action = CLI_REM_XATTR; + cli_context->blobid = atoll(optarg); + snprintf(cli_context->key, BUFSIZE, "%s", argv[optind]); + } else { + usage(cli_context, "ERROR: missing parameter.\n"); + } + break; + case 'l': + if (strcmp("bdevs", optarg) == 0) { + cmd_chosen++; + cli_context->action = CLI_LIST_BDEVS; + } else if (strcmp("blobs", optarg) == 0) { + cmd_chosen++; + cli_context->action = CLI_LIST_BLOBS; + } else { + usage(cli_context, "ERROR: invalid option for list\n"); + } + break; + case 'm': + if (argv[optind] != NULL) { + cmd_chosen++; + cli_context->action = CLI_IMPORT_BLOB; + cli_context->blobid = atoll(optarg); + snprintf(cli_context->file, BUFSIZE, "%s", argv[optind]); + } else { + usage(cli_context, "ERROR: missing parameter.\n"); + } + break; + case 'n': + cli_context->num_clusters = atoi(optarg); + if (cli_context->num_clusters > 0) { + cmd_chosen++; + cli_context->action = CLI_CREATE_BLOB; + } else { + usage(cli_context, "ERROR: invalid option for new\n"); + } + break; + case 'p': + cmd_chosen++; + cli_context->action = CLI_SET_SUPER; + cli_context->superid = atoll(optarg); + break; + case 'S': + if (cli_context->cli_mode == CLI_MODE_CMD) { + cmd_chosen++; + cli_context->cli_mode = CLI_MODE_SHELL; + } + cli_context->action = CLI_NONE; + break; + case 's': + cmd_chosen++; + 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 'T': + if (cli_context->cli_mode == CLI_MODE_CMD) { + cmd_chosen++; + cli_context->cli_mode = CLI_MODE_SCRIPT; + if (argv[optind] && (strcmp("ignore", argv[optind]) == 0)) { + g_script.ignore_errors = true; + } else { + g_script.ignore_errors = false; + } + snprintf(cli_context->script_file, BUFSIZE, "%s", optarg); + } else { + cli_context->action = CLI_NONE; + } + break; + case 'X': + cmd_chosen++; + cli_context->action = CLI_SHELL_EXIT; + break; + case 'x': + if (argv[optind] != NULL || argv[optind + 1] != NULL) { + cmd_chosen++; + cli_context->action = CLI_SET_XATTR; + cli_context->blobid = atoll(optarg); + snprintf(cli_context->key, BUFSIZE, "%s", argv[optind]); + snprintf(cli_context->value, BUFSIZE, "%s", argv[optind + 1]); + } else { + usage(cli_context, "ERROR: missing parameter.\n"); + } + break; + default: + usage(cli_context, "ERROR: invalid option\n"); + } + /* only one actual command can be done at a time */ + if (cmd_chosen > 1) { + usage(cli_context, "Error: Please choose only one command\n"); + } + } + + if (cli_context->cli_mode == CLI_MODE_CMD && cmd_chosen == 0) { + usage(cli_context, "Error: Please choose a command.\n"); + } + + /* + * We don't check the local boolean because in some modes it will have been set + * on and earlier command. + */ + if (strcmp(cli_context->bdev_name, "") == 0) { + usage(cli_context, "Error: -b option is required.\n"); + cmd_chosen = 0; + } + + /* in shell mode we'll call getopt multiple times so need to reset its index */ + optind = 0; + return (cmd_chosen == 1); +} + +/* + * In script mode, we parsed a script file at startup and saved off a bunch of cmd + * lines that we now parse with each run of cli_start so we us the same cmd parser + * as cmd and shell modes. + */ +static bool +line_parser(struct cli_context_t *cli_context) +{ + bool cmd_chosen; + char *tok = NULL; + int blob_num = 0; + int start_idx = cli_context->argc; + int i; + + printf("\nSCRIPT NOW PROCESSING: %s\n", g_script.cmdline[g_script.cmdline_idx]); + tok = strtok(g_script.cmdline[g_script.cmdline_idx], " "); + while (tok != NULL) { + /* + * We support one replaceable token right now, a $Bn + * represents the blobid that was created in position n + * so fish this out now and use it here. + */ + cli_context->argv[cli_context->argc] = strdup(tok); + if (tok[0] == '$' && tok[1] == 'B') { + tok += 2; + blob_num = atoi(tok); + if (blob_num >= 0 && blob_num < MAX_SCRIPT_BLOBS) { + cli_context->argv[cli_context->argc] = + realloc(cli_context->argv[cli_context->argc], BUFSIZE); + if (cli_context->argv[cli_context->argc] == NULL) { + printf("ERROR: unable to realloc memory\n"); + spdk_app_stop(-1); + } + if (g_script.blobid[blob_num] == 0) { + printf("ERROR: There is no blob for $B%d\n", + blob_num); + } + snprintf(cli_context->argv[cli_context->argc], BUFSIZE, + "%" PRIu64, g_script.blobid[blob_num]); + } else { + printf("ERROR: Invalid token or exceeded max blobs of %d\n", + MAX_SCRIPT_BLOBS); + } + } + cli_context->argc++; + tok = strtok(NULL, " "); + } + + /* call parse cmd line with user input as args */ + cmd_chosen = cmd_parser(cli_context->argc, &cli_context->argv[0], cli_context); + + /* free strdup memory and reset arg count for next shell interaction */ + for (i = start_idx; i < cli_context->argc; i++) { + free(cli_context->argv[i]); + cli_context->argv[i] = NULL; + } + cli_context->argc = 1; + + g_script.cmdline_idx++; + assert(g_script.cmdline_idx < MAX_SCRIPT_LINES); + + if (cmd_chosen == false) { + printf("ERROR: Invalid script line starting with: %s\n\n", + g_script.cmdline[g_script.cmdline_idx - 1]); + if (g_script.ignore_errors == false) { + printf("** Aborting **\n"); + cli_context->action = CLI_SHELL_EXIT; + cmd_chosen = true; + unload_bs(cli_context, "", 0); + } else { + printf("** Skipping **\n"); + } + } + + return cmd_chosen; +} + +/* + * For script mode, we read a series of commands from a text file and store them + * in a global struct. That, along with the cli_mode that tells us we're in + * script mode is what feeds the rest of the app in the same way as is it were + * getting commands from shell mode. + */ +static void +parse_script(struct cli_context_t *cli_context) +{ + FILE *fp = NULL; + size_t bufsize = BUFSIZE; + int64_t bytes_in = 0; + int i = 0; + + /* initialize global script values */ + for (i = 0; i < MAX_SCRIPT_BLOBS; i++) { + g_script.blobid[i] = 0; + } + g_script.blobid_idx = 0; + g_script.cmdline_idx = 0; + i = 0; + + fp = fopen(cli_context->script_file, "r"); + if (fp == NULL) { + printf("ERROR: unable to open script: %s\n", + cli_context->script_file); + cli_cleanup(cli_context); + exit(-1); + } + + do { + bytes_in = getline(&g_script.cmdline[i], &bufsize, fp); + if (bytes_in > 0) { + /* replace newline with null */ + spdk_str_chomp(g_script.cmdline[i]); + + /* ignore comments */ + if (g_script.cmdline[i][0] != '#') { + i++; + } + } + } while (bytes_in != -1 && i < MAX_SCRIPT_LINES - 1); + fclose(fp); + + /* add an exit cmd in case they didn't */ + g_script.cmdline[i] = realloc(g_script.cmdline[i], BUFSIZE); + if (g_script.cmdline[i] == NULL) { + int j; + + for (j = 0; j < i; j++) { + free(g_script.cmdline[j]); + g_script.cmdline[j] = NULL; + } + unload_bs(cli_context, "ERROR: unable to alloc memory.\n", 0); + } + snprintf(g_script.cmdline[i], BUFSIZE, "%s", "-X"); + g_script.max_index = i; +} + +/* + * Provides for a shell interface as opposed to one shot command line. + */ +static bool +cli_shell(void *arg1, void *arg2) +{ + struct cli_context_t *cli_context = arg1; + char *line = NULL; + ssize_t buf_size = 0; + ssize_t bytes_in = 0; + ssize_t tok_len = 0; + char *tok = NULL; + bool cmd_chosen = false; + int start_idx = cli_context->argc; + int i; + + printf("blob> "); + bytes_in = getline(&line, &buf_size, stdin); + + /* If getline() failed (EOF), exit the shell. */ + if (bytes_in < 0) { + free(line); + cli_context->action = CLI_SHELL_EXIT; + return true; + } + + /* parse input and update cli_context so we can use common option parser */ + if (bytes_in > 0) { + tok = strtok(line, " "); + } + while ((tok != NULL) && (cli_context->argc < MAX_ARGS)) { + cli_context->argv[cli_context->argc] = strdup(tok); + tok_len = strlen(tok); + cli_context->argc++; + tok = strtok(NULL, " "); + } + + /* replace newline on last arg with null */ + if (tok_len) { + spdk_str_chomp(cli_context->argv[cli_context->argc - 1]); + } + + /* call parse cmd line with user input as args */ + cmd_chosen = cmd_parser(cli_context->argc, &cli_context->argv[0], cli_context); + + /* free strdup mem & reset arg count for next shell interaction */ + for (i = start_idx; i < cli_context->argc; i++) { + free(cli_context->argv[i]); + cli_context->argv[i] = NULL; + } + cli_context->argc = 1; + + free(line); + + return cmd_chosen; +} + +/* + * 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; + + /* + * If we're in script mode, we already have a list of commands so + * just need to pull them out one at a time and process them. + */ + if (cli_context->cli_mode == CLI_MODE_SCRIPT) { + while (line_parser(cli_context) == false); + } + + /* + * The initial cmd line options are parsed once before this function is + * called so if there is no action, we're in shell mode and will loop + * here until a a valid option is parsed and returned. + */ + if (cli_context->action == CLI_NONE) { + while (cli_shell(cli_context, NULL) == false); + } + + /* Decide what to do next based on cmd line parsing. */ + switch (cli_context->action) { + case CLI_SET_SUPER: + case CLI_SHOW_BS: + case CLI_CREATE_BLOB: + case CLI_SET_XATTR: + case CLI_REM_XATTR: + case CLI_SHOW_BLOB: + case CLI_LIST_BLOBS: + case CLI_DUMP_BLOB: + case CLI_IMPORT_BLOB: + case CLI_FILL: + load_bs(cli_context); + break; + case CLI_INIT_BS: + init_bs(cli_context); + break; + case CLI_DUMP_BS: + dump_bs(cli_context); + break; + case CLI_LIST_BDEVS: + list_bdevs(cli_context); + break; + case CLI_SHELL_EXIT: + /* + * Because shell mode reuses cmd mode functions, the blobstore + * is loaded/unloaded with every action so we just need to + * stop the framework. For this app there's no need to optimize + * and keep the blobstore open while the app is in shell mode. + */ + spdk_app_stop(0); + break; + case CLI_HELP: + usage(cli_context, ""); + unload_complete(cli_context, 0); + break; + default: + /* should never get here */ + exit(-1); + break; + } +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + struct cli_context_t *cli_context = NULL; + bool cmd_chosen; + int rc = 0; + + if (argc < 2) { + usage(cli_context, "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); + } + + /* default to CMD mode until we've parsed the first parms */ + cli_context->cli_mode = CLI_MODE_CMD; + cli_context->argv[0] = strdup(argv[0]); + cli_context->argc = 1; + + /* parse command line */ + cmd_chosen = cmd_parser(argc, argv, cli_context); + free(cli_context->argv[0]); + cli_context->argv[0] = NULL; + if (cmd_chosen == false) { + cli_cleanup(cli_context); + exit(-1); + } + + /* after displaying help, just exit */ + if (cli_context->action == CLI_HELP) { + usage(cli_context, ""); + cli_cleanup(cli_context); + exit(-1); + } + + /* if they don't supply a conf name, use the default */ + if (!cli_context->config_file) { + cli_context->config_file = program_conf; + } + + /* if the config file doesn't exist, tell them how to make one */ + if (access(cli_context->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(" <path to spdk>/scripts/gen_nvme.sh > blobcli.conf\n"); + printf("and then re-run the cli tool.\n"); + exit(-1); + } + + /* + * For script mode we keep a bunch of stuff in a global since + * none if it is passed back and forth to SPDK. + */ + if (cli_context->cli_mode == CLI_MODE_SCRIPT) { + /* + * Now we'll build up the global which will direct this run of the app + * as it will have a list (g_script) of all of the commands line by + * line as if they were typed in on the shell at cmd line. + */ + parse_script(cli_context); + } + + /* Set default values in opts struct along with name and conf file. */ + spdk_app_opts_init(&opts); + opts.name = "blobcli"; + opts.config_file = cli_context->config_file; + + cli_context->app_started = true; + 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; +} |