1 /*- 2 * BSD LICENSE 3 * 4 * Copyright (c) Intel Corporation. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * * Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * * Neither the name of Intel Corporation nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include "spdk/stdinc.h" 35 36 #include "spdk/bdev.h" 37 #include "spdk/env.h" 38 #include "spdk/event.h" 39 #include "spdk/blob_bdev.h" 40 #include "spdk/blob.h" 41 #include "spdk/log.h" 42 #include "spdk/version.h" 43 #include "spdk/string.h" 44 45 /* 46 * The following is not a public header file, but the CLI does expose 47 * some internals of blobstore for dev/debug puposes so we 48 * include it here. 49 */ 50 #include "../lib/blob/blobstore.h" 51 static void cli_start(void *arg1, void *arg2); 52 53 static const char *program_name = "blobcli"; 54 /* default name for .conf file, any name can be used however with -c switch */ 55 static const char *program_conf = "blobcli.conf"; 56 /* default name for the first NVMe device, this needs to match what is in th conf */ 57 static const char *bdev_name = "Nvme0n1"; 58 59 /* 60 * CMD mode runs one command at a time which can be annoying as the init takes 61 * a few seconds, so the shell mode, invoked with -S, does the init once and gives 62 * the user an interactive shell instead. With script mode init is also done just 63 * once. 64 */ 65 enum cli_mode_type { 66 CLI_MODE_CMD, 67 CLI_MODE_SHELL, 68 CLI_MODE_SCRIPT 69 }; 70 71 enum cli_action_type { 72 CLI_NONE, 73 CLI_IMPORT, 74 CLI_DUMP, 75 CLI_FILL, 76 CLI_REM_XATTR, 77 CLI_SET_XATTR, 78 CLI_SET_SUPER, 79 CLI_SHOW_BS, 80 CLI_SHOW_BLOB, 81 CLI_CREATE_BLOB, 82 CLI_LIST_BDEVS, 83 CLI_LIST_BLOBS, 84 CLI_INIT_BS, 85 CLI_SHELL_EXIT, 86 CLI_HELP, 87 }; 88 89 #define BUFSIZE 255 90 #define MAX_ARGS 16 91 #define ALIGN_4K 4096 92 #define STARTING_PAGE 0 93 #define NUM_PAGES 1 94 95 /* 96 * The CLI uses the SPDK app framework so is async and callback driven. A 97 * pointer to this structure is passed to SPDK calls and returned in the 98 * callbacks for easy access to all the info we may need. 99 */ 100 struct cli_context_t { 101 struct spdk_blob_store *bs; 102 struct spdk_blob *blob; 103 struct spdk_bs_dev *bs_dev; 104 spdk_blob_id blobid; 105 spdk_blob_id superid; 106 struct spdk_io_channel *channel; 107 uint8_t *buff; 108 uint64_t page_size; 109 uint64_t page_count; 110 uint64_t blob_pages; 111 uint64_t bytes_so_far; 112 FILE *fp; 113 enum cli_action_type action; 114 char key[BUFSIZE + 1]; 115 char value[BUFSIZE + 1]; 116 char file[BUFSIZE + 1]; 117 uint64_t filesize; 118 int fill_value; 119 const char *bdev_name; 120 int rc; 121 int num_clusters; 122 enum cli_mode_type cli_mode; 123 const char *config_file; 124 int argc; 125 char *argv[MAX_ARGS]; 126 bool app_started; 127 char script_file[BUFSIZE + 1]; 128 }; 129 130 /* we store a bunch of stuff in a global struct for use by scripting mode */ 131 #define MAX_SCRIPT_LINES 64 132 #define MAX_SCRIPT_BLOBS 16 133 struct cli_script_t { 134 spdk_blob_id blobid[MAX_SCRIPT_BLOBS]; 135 int blobid_idx; 136 int max_index; 137 int cmdline_idx; 138 bool ignore_errors; 139 char *cmdline[MAX_SCRIPT_LINES]; 140 }; 141 struct cli_script_t g_script; 142 143 /* 144 * Common printing of commands for CLI and shell modes. 145 */ 146 static void 147 print_cmds(void) 148 { 149 printf("\nCommands include:\n"); 150 printf("\t-d <blobid> filename - dump contents of a blob to a file\n"); 151 printf("\t-f <blobid> value - fill a blob with a decimal value\n"); 152 printf("\t-h - this help screen\n"); 153 printf("\t-i - initialize a blobstore\n"); 154 printf("\t-l bdevs | blobs - list either available bdevs or existing blobs\n"); 155 printf("\t-m <blobid> filename - import contents of a file to a blob\n"); 156 printf("\t-n <# clusters> - create new blob\n"); 157 printf("\t-p <blobid> - set the superblob to the ID provided\n"); 158 printf("\t-r <blobid> name - remove xattr name/value pair\n"); 159 printf("\t-s <blobid> | bs - show blob info or blobstore info\n"); 160 printf("\t-x <blobid> name value - set xattr name/value pair\n"); 161 printf("\t-X - exit when in interactive shell mode\n"); 162 printf("\t-S - enter interactive shell mode\n"); 163 printf("\t-T <filename> - automated script mode\n"); 164 printf("\n"); 165 } 166 167 /* 168 * Prints usage and relevant error message. 169 */ 170 static void 171 usage(struct cli_context_t *cli_context, char *msg) 172 { 173 if (msg) { 174 printf("%s", msg); 175 } 176 177 if (cli_context && cli_context->cli_mode == CLI_MODE_CMD) { 178 printf("Version %s\n", SPDK_VERSION_STRING); 179 printf("Usage: %s [-c SPDK config_file] Command\n", program_name); 180 printf("\n%s is a command line tool for interacting with blobstore\n", 181 program_name); 182 printf("on the underlying device specified in the conf file passed\n"); 183 printf("in as a command line option.\n"); 184 } 185 if (cli_context && cli_context->cli_mode != CLI_MODE_SCRIPT) { 186 print_cmds(); 187 } 188 } 189 190 /* 191 * Free up memory that we allocated. 192 */ 193 static void 194 cli_cleanup(struct cli_context_t *cli_context) 195 { 196 if (cli_context->buff) { 197 spdk_dma_free(cli_context->buff); 198 } 199 if (cli_context->cli_mode == CLI_MODE_SCRIPT) { 200 int i; 201 202 for (i = 0; i <= g_script.max_index; i++) { 203 free(g_script.cmdline[i]); 204 } 205 } 206 free(cli_context); 207 } 208 209 /* 210 * Callback routine for the blobstore unload. 211 */ 212 static void 213 unload_complete(void *cb_arg, int bserrno) 214 { 215 struct cli_context_t *cli_context = cb_arg; 216 217 if (bserrno) { 218 printf("Error %d unloading the bobstore\n", bserrno); 219 cli_context->rc = bserrno; 220 } 221 222 /* 223 * Quit if we're in cmd mode or exiting shell mode, otherwise 224 * clear the action field and start the main function again. 225 */ 226 if (cli_context->cli_mode == CLI_MODE_CMD || 227 cli_context->action == CLI_SHELL_EXIT) { 228 spdk_app_stop(cli_context->rc); 229 } else { 230 /* when action is CLI_NONE, we know we need to remain in the shell */ 231 cli_context->bs = NULL; 232 cli_context->action = CLI_NONE; 233 cli_start(cli_context, NULL); 234 } 235 } 236 237 /* 238 * Unload the blobstore. 239 */ 240 static void 241 unload_bs(struct cli_context_t *cli_context, char *msg, int bserrno) 242 { 243 if (bserrno) { 244 printf("%s (err %d)\n", msg, bserrno); 245 cli_context->rc = bserrno; 246 } 247 248 if (cli_context->bs) { 249 if (cli_context->channel) { 250 spdk_bs_free_io_channel(cli_context->channel); 251 cli_context->channel = NULL; 252 } 253 spdk_bs_unload(cli_context->bs, unload_complete, cli_context); 254 } else { 255 spdk_app_stop(bserrno); 256 } 257 } 258 259 /* 260 * Callback for closing a blob. 261 */ 262 static void 263 close_cb(void *arg1, int bserrno) 264 { 265 struct cli_context_t *cli_context = arg1; 266 267 if (bserrno) { 268 unload_bs(cli_context, "Error in close callback", 269 bserrno); 270 return; 271 } 272 unload_bs(cli_context, "", 0); 273 } 274 275 /* 276 * Callback function for sync'ing metadata. 277 */ 278 static void 279 sync_cb(void *arg1, int bserrno) 280 { 281 struct cli_context_t *cli_context = arg1; 282 283 if (bserrno) { 284 unload_bs(cli_context, "Error in sync callback", 285 bserrno); 286 return; 287 } 288 289 spdk_bs_md_close_blob(&cli_context->blob, close_cb, 290 cli_context); 291 } 292 293 /* 294 * Callback function for opening a blob after creating. 295 */ 296 static void 297 open_now_resize_cb(void *cb_arg, struct spdk_blob *blob, int bserrno) 298 { 299 struct cli_context_t *cli_context = cb_arg; 300 int rc = 0; 301 uint64_t total = 0; 302 303 if (bserrno) { 304 unload_bs(cli_context, "Error in open completion", 305 bserrno); 306 return; 307 } 308 cli_context->blob = blob; 309 310 rc = spdk_bs_md_resize_blob(cli_context->blob, 311 cli_context->num_clusters); 312 if (rc) { 313 unload_bs(cli_context, "Error in blob resize", 314 bserrno); 315 return; 316 } 317 318 total = spdk_blob_get_num_clusters(cli_context->blob); 319 printf("blob now has USED clusters of %" PRIu64 "\n", 320 total); 321 322 /* 323 * Always a good idea to sync after MD changes or the changes 324 * may be lost if things aren't closed cleanly. 325 */ 326 spdk_bs_md_sync_blob(cli_context->blob, sync_cb, 327 cli_context); 328 } 329 330 /* 331 * Callback function for creating a blob. 332 */ 333 static void 334 blob_create_cb(void *arg1, spdk_blob_id blobid, int bserrno) 335 { 336 struct cli_context_t *cli_context = arg1; 337 338 if (bserrno) { 339 unload_bs(cli_context, "Error in blob create callback", 340 bserrno); 341 return; 342 } 343 344 cli_context->blobid = blobid; 345 printf("New blob id %" PRIu64 "\n", cli_context->blobid); 346 347 /* if we're in script mode, we need info on all blobids for later */ 348 if (cli_context->cli_mode == CLI_MODE_SCRIPT) { 349 g_script.blobid[g_script.blobid_idx++] = blobid; 350 } 351 352 /* We have to open the blob before we can do things like resize. */ 353 spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, 354 open_now_resize_cb, cli_context); 355 } 356 357 /* 358 * Callback for get_super where we'll continue on to show blobstore info. 359 */ 360 static void 361 show_bs_cb(void *arg1, spdk_blob_id blobid, int bserrno) 362 { 363 struct cli_context_t *cli_context = arg1; 364 uint64_t val; 365 struct spdk_bdev *bdev = NULL; 366 367 if (bserrno && bserrno != -ENOENT) { 368 unload_bs(cli_context, "Error in get_super callback", 369 bserrno); 370 return; 371 } 372 cli_context->superid = blobid; 373 374 bdev = spdk_bdev_get_by_name(cli_context->bdev_name); 375 if (bdev == NULL) { 376 unload_bs(cli_context, "Error w/bdev in get_super callback", 377 bserrno); 378 return; 379 } 380 381 printf("Blobstore Public Info:\n"); 382 printf("\tUsing bdev Product Name: %s\n", 383 spdk_bdev_get_product_name(bdev)); 384 printf("\tAPI Version: %d\n", SPDK_BS_VERSION); 385 386 if (bserrno != -ENOENT) { 387 printf("\tsuper blob ID: %" PRIu64 "\n", cli_context->superid); 388 } else { 389 printf("\tsuper blob ID: none assigned\n"); 390 } 391 392 printf("\tpage size: %" PRIu64 "\n", cli_context->page_size); 393 394 val = spdk_bs_get_cluster_size(cli_context->bs); 395 printf("\tcluster size: %" PRIu64 "\n", val); 396 397 val = spdk_bs_free_cluster_count(cli_context->bs); 398 printf("\t# free clusters: %" PRIu64 "\n", val); 399 400 /* 401 * Private info isn't accessible via the public API but 402 * may be useful for debug of blobstore based applications. 403 */ 404 printf("\nBlobstore Private Info:\n"); 405 printf("\tMetadata start (pages): %" PRIu64 "\n", 406 cli_context->bs->md_start); 407 printf("\tMetadata length (pages): %d \n", 408 cli_context->bs->md_len); 409 410 unload_bs(cli_context, "", 0); 411 } 412 413 /* 414 * Show detailed info about a particular blob. 415 */ 416 static void 417 show_blob(struct cli_context_t *cli_context) 418 { 419 uint64_t val; 420 struct spdk_xattr_names *names; 421 const void *value; 422 size_t value_len; 423 char data[BUFSIZE]; 424 unsigned int i; 425 426 printf("Blob Public Info:\n"); 427 428 printf("blob ID: %" PRIu64 "\n", cli_context->blobid); 429 430 val = spdk_blob_get_num_clusters(cli_context->blob); 431 printf("# of clusters: %" PRIu64 "\n", val); 432 433 printf("# of bytes: %" PRIu64 "\n", 434 val * spdk_bs_get_cluster_size(cli_context->bs)); 435 436 val = spdk_blob_get_num_pages(cli_context->blob); 437 printf("# of pages: %" PRIu64 "\n", val); 438 439 spdk_bs_md_get_xattr_names(cli_context->blob, &names); 440 441 printf("# of xattrs: %d\n", spdk_xattr_names_get_count(names)); 442 printf("xattrs:\n"); 443 for (i = 0; i < spdk_xattr_names_get_count(names); i++) { 444 spdk_bs_md_get_xattr_value(cli_context->blob, 445 spdk_xattr_names_get_name(names, i), 446 &value, &value_len); 447 if ((value_len + 1) > sizeof(data)) { 448 printf("FYI: adjusting size of xattr due to CLI limits.\n"); 449 value_len = sizeof(data) - 1; 450 } 451 memcpy(&data, value, value_len); 452 data[value_len] = '\0'; 453 printf("\n(%d) Name:%s\n", i, 454 spdk_xattr_names_get_name(names, i)); 455 printf("(%d) Value:\n", i); 456 spdk_trace_dump(stdout, "", value, value_len); 457 } 458 459 /* 460 * Private info isn't accessible via the public API but 461 * may be useful for debug of blobstore based applications. 462 */ 463 printf("\nBlob Private Info:\n"); 464 switch (cli_context->blob->state) { 465 case SPDK_BLOB_STATE_DIRTY: 466 printf("state: DIRTY\n"); 467 break; 468 case SPDK_BLOB_STATE_CLEAN: 469 printf("state: CLEAN\n"); 470 break; 471 case SPDK_BLOB_STATE_LOADING: 472 printf("state: LOADING\n"); 473 break; 474 case SPDK_BLOB_STATE_SYNCING: 475 printf("state: SYNCING\n"); 476 break; 477 default: 478 printf("state: UNKNOWN\n"); 479 break; 480 } 481 printf("open ref count: %d\n", 482 cli_context->blob->open_ref); 483 484 spdk_xattr_names_free(names); 485 } 486 487 /* 488 * Callback for getting the first blob, shared with simple blob listing as well. 489 */ 490 static void 491 blob_iter_cb(void *arg1, struct spdk_blob *blob, int bserrno) 492 { 493 struct cli_context_t *cli_context = arg1; 494 495 if (bserrno) { 496 if (bserrno == -ENOENT) { 497 /* this simply means there are no more blobs */ 498 unload_bs(cli_context, "", 0); 499 } else { 500 unload_bs(cli_context, "Error in blob iter callback", 501 bserrno); 502 } 503 return; 504 } 505 506 if (cli_context->action == CLI_LIST_BLOBS) { 507 printf("\nList BLOBS:\n"); 508 printf("Found blob with ID# %" PRIu64 "\n", 509 spdk_blob_get_id(blob)); 510 } else if (spdk_blob_get_id(blob) == cli_context->blobid) { 511 /* 512 * Found the blob we're looking for, but we need to finish 513 * iterating even after showing the info so that internally 514 * the blobstore logic will close the blob. Or we could 515 * chose to close it now, either way. 516 */ 517 cli_context->blob = blob; 518 show_blob(cli_context); 519 } 520 521 spdk_bs_md_iter_next(cli_context->bs, &blob, blob_iter_cb, 522 cli_context); 523 } 524 525 /* 526 * Callback for setting the super blob ID. 527 */ 528 static void 529 set_super_cb(void *arg1, int bserrno) 530 { 531 struct cli_context_t *cli_context = arg1; 532 533 if (bserrno) { 534 unload_bs(cli_context, "Error in set_super callback", 535 bserrno); 536 return; 537 } 538 539 printf("Super Blob ID has been set.\n"); 540 unload_bs(cli_context, "", 0); 541 } 542 543 /* 544 * Callback for set_xattr_open where we set or delete xattrs. 545 */ 546 static void 547 set_xattr_cb(void *cb_arg, struct spdk_blob *blob, int bserrno) 548 { 549 struct cli_context_t *cli_context = cb_arg; 550 551 if (bserrno) { 552 unload_bs(cli_context, "Error in blob open callback", 553 bserrno); 554 return; 555 } 556 cli_context->blob = blob; 557 558 if (cli_context->action == CLI_SET_XATTR) { 559 spdk_blob_md_set_xattr(cli_context->blob, 560 cli_context->key, 561 cli_context->value, 562 strlen(cli_context->value) + 1); 563 printf("Xattr has been set.\n"); 564 } else { 565 spdk_blob_md_remove_xattr(cli_context->blob, 566 cli_context->key); 567 printf("Xattr has been removed.\n"); 568 } 569 570 spdk_bs_md_sync_blob(cli_context->blob, sync_cb, cli_context); 571 } 572 573 /* 574 * Callback function for reading a blob for dumping to a file. 575 */ 576 static void 577 read_dump_cb(void *arg1, int bserrno) 578 { 579 struct cli_context_t *cli_context = arg1; 580 uint64_t bytes_written; 581 582 if (bserrno) { 583 fclose(cli_context->fp); 584 unload_bs(cli_context, "Error in read completion", 585 bserrno); 586 return; 587 } 588 589 bytes_written = fwrite(cli_context->buff, NUM_PAGES, cli_context->page_size, 590 cli_context->fp); 591 if (bytes_written != cli_context->page_size) { 592 fclose(cli_context->fp); 593 unload_bs(cli_context, "Error with fwrite", 594 bserrno); 595 return; 596 } 597 598 printf("."); 599 if (++cli_context->page_count < cli_context->blob_pages) { 600 /* perform another read */ 601 spdk_bs_io_read_blob(cli_context->blob, cli_context->channel, 602 cli_context->buff, cli_context->page_count, 603 NUM_PAGES, read_dump_cb, cli_context); 604 } else { 605 /* done reading */ 606 printf("\nFile write complete (to %s).\n", cli_context->file); 607 fclose(cli_context->fp); 608 spdk_bs_md_close_blob(&cli_context->blob, close_cb, 609 cli_context); 610 } 611 } 612 613 /* 614 * Callback for write completion on the import of a file to a blob. 615 */ 616 static void 617 write_imp_cb(void *arg1, int bserrno) 618 { 619 struct cli_context_t *cli_context = arg1; 620 uint64_t bytes_read; 621 622 if (bserrno) { 623 fclose(cli_context->fp); 624 unload_bs(cli_context, "Error in write completion", 625 bserrno); 626 return; 627 } 628 629 if (cli_context->bytes_so_far < cli_context->filesize) { 630 /* perform another file read */ 631 bytes_read = fread(cli_context->buff, 1, 632 cli_context->page_size, 633 cli_context->fp); 634 cli_context->bytes_so_far += bytes_read; 635 636 /* if this read is < 1 page, fill with 0s */ 637 if (bytes_read < cli_context->page_size) { 638 uint8_t *offset = cli_context->buff + bytes_read; 639 memset(offset, 0, cli_context->page_size - bytes_read); 640 } 641 } else { 642 /* 643 * Done reading the file, fill the rest of the blob with 0s, 644 * yeah we're memsetting the same page over and over here 645 */ 646 memset(cli_context->buff, 0, cli_context->page_size); 647 } 648 if (++cli_context->page_count < cli_context->blob_pages) { 649 printf("."); 650 spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, 651 cli_context->buff, cli_context->page_count, 652 NUM_PAGES, write_imp_cb, cli_context); 653 } else { 654 /* done writing */ 655 printf("\nBlob import complete (from %s).\n", cli_context->file); 656 fclose(cli_context->fp); 657 spdk_bs_md_close_blob(&cli_context->blob, close_cb, cli_context); 658 } 659 } 660 661 /* 662 * Callback for open blobs where we'll continue on dump a blob to a file or 663 * import a file to a blob. For dump, the resulting file will always be the 664 * full size of the blob. For import, the blob will fill with the file 665 * contents first and then 0 out the rest of the blob. 666 */ 667 static void 668 dump_imp_open_cb(void *cb_arg, struct spdk_blob *blob, int bserrno) 669 { 670 struct cli_context_t *cli_context = cb_arg; 671 672 if (bserrno) { 673 unload_bs(cli_context, "Error in blob open callback", 674 bserrno); 675 return; 676 } 677 cli_context->blob = blob; 678 679 /* 680 * We'll transfer just one page at a time to keep the buffer 681 * small. This could be bigger of course. 682 */ 683 cli_context->buff = spdk_dma_malloc(cli_context->page_size, 684 ALIGN_4K, NULL); 685 if (cli_context->buff == NULL) { 686 printf("Error in allocating memory\n"); 687 spdk_bs_md_close_blob(&cli_context->blob, close_cb, cli_context); 688 return; 689 } 690 printf("Working"); 691 cli_context->blob_pages = spdk_blob_get_num_pages(cli_context->blob); 692 cli_context->page_count = 0; 693 if (cli_context->action == CLI_DUMP) { 694 cli_context->fp = fopen(cli_context->file, "w"); 695 if (cli_context->fp == NULL) { 696 printf("Error in opening file\n"); 697 spdk_bs_md_close_blob(&cli_context->blob, close_cb, cli_context); 698 return; 699 } 700 701 /* read a page of data from the blob */ 702 spdk_bs_io_read_blob(cli_context->blob, cli_context->channel, 703 cli_context->buff, cli_context->page_count, 704 NUM_PAGES, read_dump_cb, cli_context); 705 } else { 706 cli_context->fp = fopen(cli_context->file, "r"); 707 if (cli_context->fp == NULL) { 708 printf("Error in opening file\n"); 709 spdk_bs_md_close_blob(&cli_context->blob, close_cb, cli_context); 710 return; 711 } 712 713 /* get the filesize then rewind read a page of data from file */ 714 fseek(cli_context->fp, 0L, SEEK_END); 715 cli_context->filesize = ftell(cli_context->fp); 716 rewind(cli_context->fp); 717 cli_context->bytes_so_far = fread(cli_context->buff, NUM_PAGES, 718 cli_context->page_size, 719 cli_context->fp); 720 721 /* if the file is < a page, fill the rest with 0s */ 722 if (cli_context->filesize < cli_context->page_size) { 723 uint8_t *offset = 724 cli_context->buff + cli_context->filesize; 725 726 memset(offset, 0, 727 cli_context->page_size - cli_context->filesize); 728 } 729 730 spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, 731 cli_context->buff, cli_context->page_count, 732 NUM_PAGES, write_imp_cb, cli_context); 733 } 734 } 735 736 /* 737 * Callback function for writing a specific pattern to page 0. 738 */ 739 static void 740 write_cb(void *arg1, int bserrno) 741 { 742 struct cli_context_t *cli_context = arg1; 743 744 if (bserrno) { 745 unload_bs(cli_context, "Error in write completion", 746 bserrno); 747 return; 748 } 749 printf("."); 750 if (++cli_context->page_count < cli_context->blob_pages) { 751 spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, 752 cli_context->buff, cli_context->page_count, 753 NUM_PAGES, write_cb, cli_context); 754 } else { 755 /* done writing */ 756 printf("\nBlob fill complete (with 0x%x).\n", cli_context->fill_value); 757 spdk_bs_md_close_blob(&cli_context->blob, close_cb, 758 cli_context); 759 } 760 } 761 762 /* 763 * Callback function to fill a blob with a value, callback from open. 764 */ 765 static void 766 fill_blob_cb(void *arg1, struct spdk_blob *blob, int bserrno) 767 { 768 struct cli_context_t *cli_context = arg1; 769 770 if (bserrno) { 771 unload_bs(cli_context, "Error in open callback", 772 bserrno); 773 return; 774 } 775 776 cli_context->blob = blob; 777 cli_context->page_count = 0; 778 cli_context->blob_pages = spdk_blob_get_num_pages(cli_context->blob); 779 cli_context->buff = spdk_dma_malloc(cli_context->page_size, 780 ALIGN_4K, NULL); 781 if (cli_context->buff == NULL) { 782 unload_bs(cli_context, "Error in allocating memory", 783 -ENOMEM); 784 return; 785 } 786 787 memset(cli_context->buff, cli_context->fill_value, 788 cli_context->page_size); 789 printf("Working"); 790 spdk_bs_io_write_blob(cli_context->blob, cli_context->channel, 791 cli_context->buff, 792 STARTING_PAGE, NUM_PAGES, write_cb, cli_context); 793 } 794 795 /* 796 * Multiple actions require us to open the bs first so here we use 797 * a common callback to set a bunch of values and then move on to 798 * the next step saved off via function pointer. 799 */ 800 static void 801 load_bs_cb(void *arg1, struct spdk_blob_store *bs, int bserrno) 802 { 803 struct cli_context_t *cli_context = arg1; 804 805 if (bserrno) { 806 unload_bs(cli_context, "Error in load callback", 807 bserrno); 808 return; 809 } 810 811 cli_context->bs = bs; 812 cli_context->page_size = spdk_bs_get_page_size(cli_context->bs); 813 cli_context->channel = spdk_bs_alloc_io_channel(cli_context->bs); 814 if (cli_context->channel == NULL) { 815 unload_bs(cli_context, "Error in allocating channel", 816 -ENOMEM); 817 return; 818 } 819 820 switch (cli_context->action) { 821 case CLI_SET_SUPER: 822 spdk_bs_set_super(cli_context->bs, cli_context->superid, 823 set_super_cb, cli_context); 824 break; 825 case CLI_SHOW_BS: 826 spdk_bs_get_super(cli_context->bs, show_bs_cb, cli_context); 827 break; 828 case CLI_CREATE_BLOB: 829 spdk_bs_md_create_blob(cli_context->bs, blob_create_cb, 830 cli_context); 831 break; 832 case CLI_SET_XATTR: 833 case CLI_REM_XATTR: 834 spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, 835 set_xattr_cb, cli_context); 836 break; 837 case CLI_SHOW_BLOB: 838 case CLI_LIST_BLOBS: 839 spdk_bs_md_iter_first(cli_context->bs, blob_iter_cb, cli_context); 840 841 break; 842 case CLI_DUMP: 843 case CLI_IMPORT: 844 spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, 845 dump_imp_open_cb, cli_context); 846 break; 847 case CLI_FILL: 848 spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid, 849 fill_blob_cb, cli_context); 850 break; 851 852 default: 853 /* should never get here */ 854 spdk_app_stop(-1); 855 break; 856 } 857 } 858 859 /* 860 * Load the blobstore. 861 */ 862 static void 863 load_bs(struct cli_context_t *cli_context) 864 { 865 struct spdk_bdev *bdev = NULL; 866 struct spdk_bs_dev *bs_dev = NULL; 867 868 bdev = spdk_bdev_get_by_name(cli_context->bdev_name); 869 if (bdev == NULL) { 870 printf("Could not find a bdev\n"); 871 spdk_app_stop(-1); 872 return; 873 } 874 875 bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); 876 if (bs_dev == NULL) { 877 printf("Could not create blob bdev!!\n"); 878 spdk_app_stop(-1); 879 return; 880 } 881 882 spdk_bs_load(bs_dev, NULL, load_bs_cb, cli_context); 883 } 884 885 /* 886 * Lists all the blobs on this blobstore. 887 */ 888 static void 889 list_bdevs(struct cli_context_t *cli_context) 890 { 891 struct spdk_bdev *bdev = NULL; 892 893 printf("\nList bdevs:\n"); 894 895 bdev = spdk_bdev_first(); 896 if (bdev == NULL) { 897 printf("Could not find a bdev\n"); 898 } 899 while (bdev) { 900 printf("\tbdev Name: %s\n", spdk_bdev_get_name(bdev)); 901 printf("\tbdev Product Name: %s\n", 902 spdk_bdev_get_product_name(bdev)); 903 bdev = spdk_bdev_next(bdev); 904 } 905 906 printf("\n"); 907 if (cli_context->cli_mode == CLI_MODE_CMD) { 908 spdk_app_stop(0); 909 } else { 910 cli_context->action = CLI_NONE; 911 cli_start(cli_context, NULL); 912 } 913 } 914 915 /* 916 * Callback function for initializing a blob. 917 */ 918 static void 919 bs_init_cb(void *cb_arg, struct spdk_blob_store *bs, 920 int bserrno) 921 { 922 struct cli_context_t *cli_context = cb_arg; 923 924 if (bserrno) { 925 unload_bs(cli_context, "Error in bs init callback", 926 bserrno); 927 return; 928 } 929 cli_context->bs = bs; 930 printf("blobstore init'd: (%p)\n", cli_context->bs); 931 932 unload_bs(cli_context, "", 0); 933 } 934 935 /* 936 * Initialize a new blobstore. 937 */ 938 static void 939 init_bs(struct cli_context_t *cli_context) 940 { 941 struct spdk_bdev *bdev = NULL; 942 943 bdev = spdk_bdev_get_by_name(cli_context->bdev_name); 944 if (bdev == NULL) { 945 printf("Could not find a bdev\n"); 946 spdk_app_stop(-1); 947 return; 948 } 949 printf("Init blobstore using bdev Product Name: %s\n", 950 spdk_bdev_get_product_name(bdev)); 951 952 cli_context->bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); 953 if (cli_context->bs_dev == NULL) { 954 printf("Could not create blob bdev!!\n"); 955 spdk_app_stop(-1); 956 return; 957 } 958 959 spdk_bs_init(cli_context->bs_dev, NULL, bs_init_cb, 960 cli_context); 961 } 962 963 /* 964 * Common cmd/option parser for command and shell modes. 965 */ 966 static bool 967 cmd_parser(int argc, char **argv, struct cli_context_t *cli_context) 968 { 969 int op; 970 int cmd_chosen = 0; 971 char resp; 972 bool cfg_specified = false; 973 974 while ((op = getopt(argc, argv, "c:d:f:hil:m:n:p:r:s:ST:Xx:")) != -1) { 975 switch (op) { 976 case 'c': 977 if (cli_context->app_started == false) { 978 cmd_chosen++; 979 cfg_specified = true; 980 cli_context->config_file = optarg; 981 } else { 982 usage(cli_context, "ERROR: -c option not valid during shell mode.\n"); 983 } 984 break; 985 case 'd': 986 if (argv[optind] != NULL) { 987 cmd_chosen++; 988 cli_context->action = CLI_DUMP; 989 cli_context->blobid = atoll(optarg); 990 snprintf(cli_context->file, BUFSIZE, "%s", argv[optind]); 991 } else { 992 usage(cli_context, "ERROR: missing parameter.\n"); 993 } 994 break; 995 case 'f': 996 if (argv[optind] != NULL) { 997 cmd_chosen++; 998 cli_context->action = CLI_FILL; 999 cli_context->blobid = atoll(optarg); 1000 cli_context->fill_value = atoi(argv[optind]); 1001 } else { 1002 usage(cli_context, "ERROR: missing parameter.\n"); 1003 } 1004 break; 1005 case 'h': 1006 cmd_chosen++; 1007 cli_context->action = CLI_HELP; 1008 break; 1009 case 'i': 1010 if (cli_context->cli_mode != CLI_MODE_SCRIPT) { 1011 printf("Your entire blobstore will be destroyed. Are you sure? (y/n) "); 1012 if (scanf("%c%*c", &resp)) { 1013 if (resp == 'y' || resp == 'Y') { 1014 cmd_chosen++; 1015 cli_context->action = CLI_INIT_BS; 1016 } else { 1017 if (cli_context->cli_mode == CLI_MODE_CMD) { 1018 spdk_app_stop(0); 1019 return false; 1020 } 1021 } 1022 } 1023 } else { 1024 cmd_chosen++; 1025 cli_context->action = CLI_INIT_BS; 1026 } 1027 break; 1028 case 'r': 1029 if (argv[optind] != NULL) { 1030 cmd_chosen++; 1031 cli_context->action = CLI_REM_XATTR; 1032 cli_context->blobid = atoll(optarg); 1033 snprintf(cli_context->key, BUFSIZE, "%s", argv[optind]); 1034 } else { 1035 usage(cli_context, "ERROR: missing parameter.\n"); 1036 } 1037 break; 1038 case 'l': 1039 if (strcmp("bdevs", optarg) == 0) { 1040 cmd_chosen++; 1041 cli_context->action = CLI_LIST_BDEVS; 1042 } else if (strcmp("blobs", optarg) == 0) { 1043 cmd_chosen++; 1044 cli_context->action = CLI_LIST_BLOBS; 1045 } else { 1046 usage(cli_context, "ERROR: invalid option for list\n"); 1047 } 1048 break; 1049 case 'm': 1050 if (argv[optind] != NULL) { 1051 cmd_chosen++; 1052 cli_context->action = CLI_IMPORT; 1053 cli_context->blobid = atoll(optarg); 1054 snprintf(cli_context->file, BUFSIZE, "%s", argv[optind]); 1055 } else { 1056 usage(cli_context, "ERROR: missing parameter.\n"); 1057 } 1058 break; 1059 case 'n': 1060 cli_context->num_clusters = atoi(optarg); 1061 if (cli_context->num_clusters > 0) { 1062 cmd_chosen++; 1063 cli_context->action = CLI_CREATE_BLOB; 1064 } else { 1065 usage(cli_context, "ERROR: invalid option for new\n"); 1066 } 1067 break; 1068 case 'p': 1069 cmd_chosen++; 1070 cli_context->action = CLI_SET_SUPER; 1071 cli_context->superid = atoll(optarg); 1072 break; 1073 case 'S': 1074 if (cli_context->cli_mode == CLI_MODE_CMD) { 1075 cmd_chosen++; 1076 cli_context->cli_mode = CLI_MODE_SHELL; 1077 } 1078 cli_context->action = CLI_NONE; 1079 break; 1080 case 's': 1081 cmd_chosen++; 1082 if (strcmp("bs", optarg) == 0) { 1083 cli_context->action = CLI_SHOW_BS; 1084 } else { 1085 cli_context->action = CLI_SHOW_BLOB; 1086 cli_context->blobid = atoll(optarg); 1087 } 1088 break; 1089 case 'T': 1090 if (cli_context->cli_mode == CLI_MODE_CMD) { 1091 cmd_chosen++; 1092 cli_context->cli_mode = CLI_MODE_SCRIPT; 1093 if (argv[optind] && (strcmp("ignore", argv[optind]) == 0)) { 1094 g_script.ignore_errors = true; 1095 } else { 1096 g_script.ignore_errors = false; 1097 } 1098 snprintf(cli_context->script_file, BUFSIZE, "%s", optarg); 1099 } else { 1100 cli_context->action = CLI_NONE; 1101 } 1102 break; 1103 case 'X': 1104 cmd_chosen++; 1105 cli_context->action = CLI_SHELL_EXIT; 1106 break; 1107 case 'x': 1108 if (argv[optind] != NULL || argv[optind + 1] != NULL) { 1109 cmd_chosen++; 1110 cli_context->action = CLI_SET_XATTR; 1111 cli_context->blobid = atoll(optarg); 1112 snprintf(cli_context->key, BUFSIZE, "%s", argv[optind]); 1113 snprintf(cli_context->value, BUFSIZE, "%s", argv[optind + 1]); 1114 } else { 1115 usage(cli_context, "ERROR: missing parameter.\n"); 1116 } 1117 break; 1118 default: 1119 usage(cli_context, "ERROR: invalid option\n"); 1120 } 1121 /* config file is the only option that can be combined */ 1122 if (cfg_specified == false && cmd_chosen > 1) { 1123 usage(cli_context, "Error: Please choose only one command\n"); 1124 } 1125 } 1126 1127 if (cli_context->cli_mode == CLI_MODE_CMD && cmd_chosen == 0) { 1128 usage(cli_context, "Error: Please choose a command.\n"); 1129 } 1130 1131 /* in shell mode we'll call getopt multiple times so need to reset its index */ 1132 optind = 0; 1133 return (cmd_chosen > 0); 1134 } 1135 1136 /* 1137 * In script mode, we parsed a script file at startup and saved off a bunch of cmd 1138 * lines that we now parse with each run of cli_start so we us the same cmd parser 1139 * as cmd and shell modes. 1140 */ 1141 static bool 1142 line_parser(struct cli_context_t *cli_context) 1143 { 1144 bool cmd_chosen; 1145 char *tok = NULL; 1146 int blob_num = 0; 1147 int start_idx = cli_context->argc; 1148 int i; 1149 1150 printf("\nSCRIPT NOW PROCESSING: %s\n", g_script.cmdline[g_script.cmdline_idx]); 1151 tok = strtok(g_script.cmdline[g_script.cmdline_idx], " "); 1152 while (tok != NULL) { 1153 /* 1154 * We support one replaceable token right now, a $Bn 1155 * represents the blobid that was created in position n 1156 * so fish this out now and use it here. 1157 */ 1158 cli_context->argv[cli_context->argc] = strdup(tok); 1159 if (tok[0] == '$' && tok[1] == 'B') { 1160 tok += 2; 1161 blob_num = atoi(tok); 1162 cli_context->argv[cli_context->argc] = 1163 realloc(cli_context->argv[cli_context->argc], BUFSIZE); 1164 if (cli_context->argv[cli_context->argc] == NULL) { 1165 printf("ERROR: unable to realloc memory\n"); 1166 spdk_app_stop(-1); 1167 } 1168 if (g_script.blobid[blob_num] == 0) { 1169 printf("ERROR: There is no blob for $B%d\n", 1170 blob_num); 1171 } 1172 snprintf(cli_context->argv[cli_context->argc], BUFSIZE, 1173 "%" PRIu64, g_script.blobid[blob_num]); 1174 } 1175 cli_context->argc++; 1176 tok = strtok(NULL, " "); 1177 } 1178 1179 /* call parse cmd line with user input as args */ 1180 cmd_chosen = cmd_parser(cli_context->argc, &cli_context->argv[0], cli_context); 1181 1182 /* free strdup memory and reset arg count for next shell interaction */ 1183 for (i = start_idx; i < cli_context->argc; i++) { 1184 free(cli_context->argv[i]); 1185 cli_context->argv[i] = NULL; 1186 } 1187 cli_context->argc = 1; 1188 1189 g_script.cmdline_idx++; 1190 assert(g_script.cmdline_idx < MAX_SCRIPT_LINES); 1191 1192 if (cmd_chosen == false) { 1193 printf("ERROR: Invalid script line starting with: %s\n\n", 1194 g_script.cmdline[g_script.cmdline_idx - 1]); 1195 if (g_script.ignore_errors == false) { 1196 printf("** Aborting **\n"); 1197 cli_context->action = CLI_SHELL_EXIT; 1198 cmd_chosen = true; 1199 unload_bs(cli_context, "", 0); 1200 } else { 1201 printf("** Skipping **\n"); 1202 } 1203 } 1204 1205 return cmd_chosen; 1206 } 1207 1208 /* 1209 * For script mode, we read a series of commands from a text file and store them 1210 * in a global struct. That, along with the cli_mode that tells us we're in 1211 * script mode is what feeds the rest of the app in the same way as is it were 1212 * getting commands from shell mode. 1213 */ 1214 static void 1215 parse_script(struct cli_context_t *cli_context) 1216 { 1217 FILE *fp = NULL; 1218 size_t bufsize = BUFSIZE; 1219 int64_t bytes_in = 0; 1220 int i = 0; 1221 1222 /* initialize global script values */ 1223 for (i = 0; i < MAX_SCRIPT_BLOBS; i++) { 1224 g_script.blobid[i] = 0; 1225 } 1226 g_script.blobid_idx = 0; 1227 g_script.cmdline_idx = 0; 1228 i = 0; 1229 1230 fp = fopen(cli_context->script_file, "r"); 1231 if (fp == NULL) { 1232 printf("ERROR: unable to open script: %s\n", 1233 cli_context->script_file); 1234 cli_cleanup(cli_context); 1235 exit(-1); 1236 } 1237 1238 do { 1239 bytes_in = getline(&g_script.cmdline[i], &bufsize, fp); 1240 if (bytes_in > 0) { 1241 /* replace newline with null */ 1242 spdk_str_chomp(g_script.cmdline[i]); 1243 1244 /* ignore comments */ 1245 if (g_script.cmdline[i][0] != '#') { 1246 i++; 1247 } 1248 } 1249 } while (bytes_in != -1 && i < MAX_SCRIPT_LINES); 1250 fclose(fp); 1251 1252 /* add an exit cmd in case they didn't */ 1253 g_script.cmdline[i] = realloc(g_script.cmdline[i], BUFSIZE); 1254 if (g_script.cmdline[i] == NULL) { 1255 int j; 1256 1257 for (j = 0; j < i; j++) { 1258 free(g_script.cmdline[j]); 1259 g_script.cmdline[j] = NULL; 1260 } 1261 unload_bs(cli_context, "ERROR: unable to alloc memory.\n", 0); 1262 } 1263 snprintf(g_script.cmdline[i], BUFSIZE, "%s", "-X"); 1264 g_script.max_index = i; 1265 } 1266 1267 /* 1268 * Provides for a shell interface as opposed to one shot command line. 1269 */ 1270 static bool 1271 cli_shell(void *arg1, void *arg2) 1272 { 1273 struct cli_context_t *cli_context = arg1; 1274 char *line = NULL; 1275 ssize_t buf_size = 0; 1276 ssize_t bytes_in = 0; 1277 ssize_t tok_len = 0; 1278 char *tok = NULL; 1279 bool cmd_chosen = false; 1280 int start_idx = cli_context->argc; 1281 int i; 1282 1283 printf("blob> "); 1284 bytes_in = getline(&line, &buf_size, stdin); 1285 1286 /* parse input and update cli_context so we can use common option parser */ 1287 if (bytes_in > 0) { 1288 tok = strtok(line, " "); 1289 } 1290 while ((tok != NULL) && (cli_context->argc < MAX_ARGS)) { 1291 cli_context->argv[cli_context->argc] = strdup(tok); 1292 tok_len = strlen(tok); 1293 cli_context->argc++; 1294 tok = strtok(NULL, " ,.-"); 1295 } 1296 1297 /* replace newline on last arg with null */ 1298 if (tok_len) { 1299 spdk_str_chomp(cli_context->argv[cli_context->argc - 1]); 1300 } 1301 1302 /* call parse cmd line with user input as args */ 1303 cmd_chosen = cmd_parser(cli_context->argc, &cli_context->argv[0], cli_context); 1304 1305 /* free strdup mem & reset arg count for next shell interaction */ 1306 for (i = start_idx; i < cli_context->argc; i++) { 1307 free(cli_context->argv[i]); 1308 cli_context->argv[i] = NULL; 1309 } 1310 cli_context->argc = 1; 1311 1312 free(line); 1313 1314 return cmd_chosen; 1315 } 1316 1317 /* 1318 * This is the function we pass into the SPDK framework that gets 1319 * called first. 1320 */ 1321 static void 1322 cli_start(void *arg1, void *arg2) 1323 { 1324 struct cli_context_t *cli_context = arg1; 1325 1326 /* 1327 * If we're in script mode, we already have a list of commands so 1328 * just need to pull them out one at a time and process them. 1329 */ 1330 if (cli_context->cli_mode == CLI_MODE_SCRIPT) { 1331 while (line_parser(cli_context) == false); 1332 } 1333 1334 /* 1335 * The initial cmd line options are parsed once before this function is 1336 * called so if there is no action, we're in shell mode and will loop 1337 * here until a a valid option is parsed and returned. 1338 */ 1339 if (cli_context->action == CLI_NONE) { 1340 while (cli_shell(cli_context, NULL) == false); 1341 } 1342 1343 /* Decide what to do next based on cmd line parsing. */ 1344 switch (cli_context->action) { 1345 case CLI_SET_SUPER: 1346 case CLI_SHOW_BS: 1347 case CLI_CREATE_BLOB: 1348 case CLI_SET_XATTR: 1349 case CLI_REM_XATTR: 1350 case CLI_SHOW_BLOB: 1351 case CLI_LIST_BLOBS: 1352 case CLI_DUMP: 1353 case CLI_IMPORT: 1354 case CLI_FILL: 1355 load_bs(cli_context); 1356 break; 1357 case CLI_INIT_BS: 1358 init_bs(cli_context); 1359 break; 1360 case CLI_LIST_BDEVS: 1361 list_bdevs(cli_context); 1362 break; 1363 case CLI_SHELL_EXIT: 1364 /* 1365 * Because shell mode reuses cmd mode functions, the blobstore 1366 * is loaded/unloaded with every action so we just need to 1367 * stop the framework. For this app there's no need to optimize 1368 * and keep the blobstore open while the app is in shell mode. 1369 */ 1370 spdk_app_stop(0); 1371 break; 1372 case CLI_HELP: 1373 usage(cli_context, ""); 1374 unload_complete(cli_context, 0); 1375 break; 1376 default: 1377 /* should never get here */ 1378 spdk_app_stop(-1); 1379 break; 1380 } 1381 } 1382 1383 int 1384 main(int argc, char **argv) 1385 { 1386 struct spdk_app_opts opts = {}; 1387 struct cli_context_t *cli_context = NULL; 1388 bool cmd_chosen; 1389 int rc = 0; 1390 1391 if (argc < 2) { 1392 usage(cli_context, "ERROR: Invalid option\n"); 1393 exit(-1); 1394 } 1395 1396 cli_context = calloc(1, sizeof(struct cli_context_t)); 1397 if (cli_context == NULL) { 1398 printf("ERROR: could not allocate context structure\n"); 1399 exit(-1); 1400 } 1401 cli_context->bdev_name = bdev_name; 1402 /* default to CMD mode until we've parsed the first parms */ 1403 cli_context->cli_mode = CLI_MODE_CMD; 1404 1405 cli_context->argv[0] = strdup(argv[0]); 1406 cli_context->argc = 1; 1407 1408 /* parse command line */ 1409 cmd_chosen = cmd_parser(argc, argv, cli_context); 1410 free(cli_context->argv[0]); 1411 cli_context->argv[0] = NULL; 1412 if (cmd_chosen == false) { 1413 cli_cleanup(cli_context); 1414 exit(-1); 1415 } 1416 1417 /* after displaying help, just exit */ 1418 if (cli_context->action == CLI_HELP) { 1419 usage(cli_context, ""); 1420 cli_cleanup(cli_context); 1421 exit(-1); 1422 } 1423 1424 /* if they don't supply a conf name, use the default */ 1425 if (!cli_context->config_file) { 1426 cli_context->config_file = program_conf; 1427 } 1428 1429 /* if the config file doesn't exist, tell them how to make one */ 1430 if (access(cli_context->config_file, F_OK) == -1) { 1431 printf("Error: No config file found.\n"); 1432 printf("To create a config file named 'blobcli.conf' for your NVMe device:\n"); 1433 printf(" <path to spdk>/scripts/gen_nvme.sh > blobcli.conf\n"); 1434 printf("and then re-run the cli tool.\n"); 1435 exit(1); 1436 } 1437 1438 /* 1439 * For script mode we keep a bunch of stuff in a global since 1440 * none if it is passed back and forth to SPDK. 1441 */ 1442 if (cli_context->cli_mode == CLI_MODE_SCRIPT) { 1443 /* 1444 * Now we'll build up the global which will direct this run of the app 1445 * as it will have a list (g_script) of all of the commands line by 1446 * line as if they were typed in on the shell at cmd line. 1447 */ 1448 parse_script(cli_context); 1449 } 1450 1451 /* Set default values in opts struct along with name and conf file. */ 1452 spdk_app_opts_init(&opts); 1453 opts.name = "blobcli"; 1454 opts.config_file = cli_context->config_file; 1455 1456 cli_context->app_started = true; 1457 rc = spdk_app_start(&opts, cli_start, cli_context, NULL); 1458 if (rc) { 1459 printf("ERROR!\n"); 1460 } 1461 1462 /* Free up memory that we allocated */ 1463 cli_cleanup(cli_context); 1464 1465 /* Gracefully close out all of the SPDK subsystems. */ 1466 spdk_app_fini(); 1467 return rc; 1468 } 1469