xref: /spdk/examples/blob/cli/blobcli.c (revision cf0d7736012b5d4414e0da75ec6bcbc57dec4856)
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