xref: /spdk/examples/blob/cli/blobcli.c (revision e9a50eb8ccca36b86f29f4239eaf9c8071d81ee0)
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 		unload_bs(cli_context, "Error in allocating memory",
687 			  -ENOMEM);
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 
696 		/* read a page of data from the blob */
697 		spdk_bs_io_read_blob(cli_context->blob, cli_context->channel,
698 				     cli_context->buff, cli_context->page_count,
699 				     NUM_PAGES, read_dump_cb, cli_context);
700 	} else {
701 		cli_context->fp = fopen(cli_context->file, "r");
702 
703 		/* get the filesize then rewind read a page of data from file */
704 		fseek(cli_context->fp, 0L, SEEK_END);
705 		cli_context->filesize = ftell(cli_context->fp);
706 		rewind(cli_context->fp);
707 		cli_context->bytes_so_far = fread(cli_context->buff, NUM_PAGES,
708 						  cli_context->page_size,
709 						  cli_context->fp);
710 
711 		/* if the file is < a page, fill the rest with 0s */
712 		if (cli_context->filesize < cli_context->page_size) {
713 			uint8_t *offset =
714 				cli_context->buff + cli_context->filesize;
715 
716 			memset(offset, 0,
717 			       cli_context->page_size - cli_context->filesize);
718 		}
719 
720 		spdk_bs_io_write_blob(cli_context->blob, cli_context->channel,
721 				      cli_context->buff, cli_context->page_count,
722 				      NUM_PAGES, write_imp_cb, cli_context);
723 	}
724 }
725 
726 /*
727  * Callback function for writing a specific pattern to page 0.
728  */
729 static void
730 write_cb(void *arg1, int bserrno)
731 {
732 	struct cli_context_t *cli_context = arg1;
733 
734 	if (bserrno) {
735 		unload_bs(cli_context, "Error in write completion",
736 			  bserrno);
737 		return;
738 	}
739 	printf(".");
740 	if (++cli_context->page_count < cli_context->blob_pages) {
741 		spdk_bs_io_write_blob(cli_context->blob, cli_context->channel,
742 				      cli_context->buff, cli_context->page_count,
743 				      NUM_PAGES, write_cb, cli_context);
744 	} else {
745 		/* done writing */
746 		printf("\nBlob fill complete (with 0x%x).\n", cli_context->fill_value);
747 		spdk_bs_md_close_blob(&cli_context->blob, close_cb,
748 				      cli_context);
749 	}
750 }
751 
752 /*
753  * Callback function to fill a blob with a value, callback from open.
754  */
755 static void
756 fill_blob_cb(void *arg1, struct spdk_blob *blob, int bserrno)
757 {
758 	struct cli_context_t *cli_context = arg1;
759 
760 	if (bserrno) {
761 		unload_bs(cli_context, "Error in open callback",
762 			  bserrno);
763 		return;
764 	}
765 
766 	cli_context->blob = blob;
767 	cli_context->page_count = 0;
768 	cli_context->blob_pages = spdk_blob_get_num_pages(cli_context->blob);
769 	cli_context->buff = spdk_dma_malloc(cli_context->page_size,
770 					    ALIGN_4K, NULL);
771 	if (cli_context->buff == NULL) {
772 		unload_bs(cli_context, "Error in allocating memory",
773 			  -ENOMEM);
774 		return;
775 	}
776 
777 	memset(cli_context->buff, cli_context->fill_value,
778 	       cli_context->page_size);
779 	printf("Working");
780 	spdk_bs_io_write_blob(cli_context->blob, cli_context->channel,
781 			      cli_context->buff,
782 			      STARTING_PAGE, NUM_PAGES, write_cb, cli_context);
783 }
784 
785 /*
786  * Multiple actions require us to open the bs first so here we use
787  * a common callback to set a bunch of values and then move on to
788  * the next step saved off via function pointer.
789  */
790 static void
791 load_bs_cb(void *arg1, struct spdk_blob_store *bs, int bserrno)
792 {
793 	struct cli_context_t *cli_context = arg1;
794 
795 	if (bserrno) {
796 		unload_bs(cli_context, "Error in load callback",
797 			  bserrno);
798 		return;
799 	}
800 
801 	cli_context->bs = bs;
802 	cli_context->page_size = spdk_bs_get_page_size(cli_context->bs);
803 	cli_context->channel = spdk_bs_alloc_io_channel(cli_context->bs);
804 	if (cli_context->channel == NULL) {
805 		unload_bs(cli_context, "Error in allocating channel",
806 			  -ENOMEM);
807 		return;
808 	}
809 
810 	switch (cli_context->action) {
811 	case CLI_SET_SUPER:
812 		spdk_bs_set_super(cli_context->bs, cli_context->superid,
813 				  set_super_cb, cli_context);
814 		break;
815 	case CLI_SHOW_BS:
816 		spdk_bs_get_super(cli_context->bs, show_bs_cb, cli_context);
817 		break;
818 	case CLI_CREATE_BLOB:
819 		spdk_bs_md_create_blob(cli_context->bs, blob_create_cb,
820 				       cli_context);
821 		break;
822 	case CLI_SET_XATTR:
823 	case CLI_REM_XATTR:
824 		spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid,
825 				     set_xattr_cb, cli_context);
826 		break;
827 	case CLI_SHOW_BLOB:
828 	case CLI_LIST_BLOBS:
829 		spdk_bs_md_iter_first(cli_context->bs, blob_iter_cb, cli_context);
830 
831 		break;
832 	case CLI_DUMP:
833 	case CLI_IMPORT:
834 		spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid,
835 				     dump_imp_open_cb, cli_context);
836 		break;
837 	case CLI_FILL:
838 		spdk_bs_md_open_blob(cli_context->bs, cli_context->blobid,
839 				     fill_blob_cb, cli_context);
840 		break;
841 
842 	default:
843 		/* should never get here */
844 		spdk_app_stop(-1);
845 		break;
846 	}
847 }
848 
849 /*
850  * Load the blobstore.
851  */
852 static void
853 load_bs(struct cli_context_t *cli_context)
854 {
855 	struct spdk_bdev *bdev = NULL;
856 	struct spdk_bs_dev *bs_dev = NULL;
857 
858 	bdev = spdk_bdev_get_by_name(cli_context->bdev_name);
859 	if (bdev == NULL) {
860 		printf("Could not find a bdev\n");
861 		spdk_app_stop(-1);
862 		return;
863 	}
864 
865 	bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL);
866 	if (bs_dev == NULL) {
867 		printf("Could not create blob bdev!!\n");
868 		spdk_app_stop(-1);
869 		return;
870 	}
871 
872 	spdk_bs_load(bs_dev, NULL, load_bs_cb, cli_context);
873 }
874 
875 /*
876  * Lists all the blobs on this blobstore.
877  */
878 static void
879 list_bdevs(struct cli_context_t *cli_context)
880 {
881 	struct spdk_bdev *bdev = NULL;
882 
883 	printf("\nList bdevs:\n");
884 
885 	bdev = spdk_bdev_first();
886 	if (bdev == NULL) {
887 		printf("Could not find a bdev\n");
888 	}
889 	while (bdev) {
890 		printf("\tbdev Name: %s\n", spdk_bdev_get_name(bdev));
891 		printf("\tbdev Product Name: %s\n",
892 		       spdk_bdev_get_product_name(bdev));
893 		bdev = spdk_bdev_next(bdev);
894 	}
895 
896 	printf("\n");
897 	if (cli_context->cli_mode == CLI_MODE_CMD) {
898 		spdk_app_stop(0);
899 	} else {
900 		cli_context->action = CLI_NONE;
901 		cli_start(cli_context, NULL);
902 	}
903 }
904 
905 /*
906  * Callback function for initializing a blob.
907  */
908 static void
909 bs_init_cb(void *cb_arg, struct spdk_blob_store *bs,
910 	   int bserrno)
911 {
912 	struct cli_context_t *cli_context = cb_arg;
913 
914 	if (bserrno) {
915 		unload_bs(cli_context, "Error in bs init callback",
916 			  bserrno);
917 		return;
918 	}
919 	cli_context->bs = bs;
920 	printf("blobstore init'd: (%p)\n", cli_context->bs);
921 
922 	unload_bs(cli_context, "", 0);
923 }
924 
925 /*
926  * Initialize a new blobstore.
927  */
928 static void
929 init_bs(struct cli_context_t *cli_context)
930 {
931 	struct spdk_bdev *bdev = NULL;
932 
933 	bdev = spdk_bdev_get_by_name(cli_context->bdev_name);
934 	if (bdev == NULL) {
935 		printf("Could not find a bdev\n");
936 		spdk_app_stop(-1);
937 		return;
938 	}
939 	printf("Init blobstore using bdev Product Name: %s\n",
940 	       spdk_bdev_get_product_name(bdev));
941 
942 	cli_context->bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL);
943 	if (cli_context->bs_dev == NULL) {
944 		printf("Could not create blob bdev!!\n");
945 		spdk_app_stop(-1);
946 		return;
947 	}
948 
949 	spdk_bs_init(cli_context->bs_dev, NULL, bs_init_cb,
950 		     cli_context);
951 }
952 
953 /*
954  * Common cmd/option parser for command and shell modes.
955  */
956 static bool
957 cmd_parser(int argc, char **argv, struct cli_context_t *cli_context)
958 {
959 	int op;
960 	int cmd_chosen = 0;
961 	char resp;
962 	bool cfg_specified = false;
963 
964 	while ((op = getopt(argc, argv, "c:d:f:hil:m:n:p:r:s:ST:Xx:")) != -1) {
965 		switch (op) {
966 		case 'c':
967 			if (cli_context->app_started == false) {
968 				cmd_chosen++;
969 				cfg_specified = true;
970 				cli_context->config_file = optarg;
971 			} else {
972 				usage(cli_context, "ERROR: -c option not valid during shell mode.\n");
973 			}
974 			break;
975 		case 'd':
976 			cmd_chosen++;
977 			cli_context->action = CLI_DUMP;
978 			cli_context->blobid = atoll(optarg);
979 			snprintf(cli_context->file, BUFSIZE, "%s", argv[optind]);
980 			break;
981 		case 'f':
982 			cmd_chosen++;
983 			cli_context->action = CLI_FILL;
984 			cli_context->blobid = atoll(optarg);
985 			cli_context->fill_value = atoi(argv[optind]);
986 			break;
987 		case 'h':
988 			cmd_chosen++;
989 			cli_context->action = CLI_HELP;
990 			break;
991 		case 'i':
992 			if (cli_context->cli_mode != CLI_MODE_SCRIPT) {
993 				printf("Your entire blobstore will be destroyed. Are you sure? (y/n) ");
994 				if (scanf("%c%*c", &resp)) {
995 					if (resp == 'y' || resp == 'Y') {
996 						cmd_chosen++;
997 						cli_context->action = CLI_INIT_BS;
998 					} else {
999 						if (cli_context->cli_mode == CLI_MODE_CMD) {
1000 							spdk_app_stop(0);
1001 							return false;
1002 						}
1003 					}
1004 				}
1005 			} else {
1006 				cmd_chosen++;
1007 				cli_context->action = CLI_INIT_BS;
1008 			}
1009 			break;
1010 		case 'r':
1011 			cmd_chosen++;
1012 			cli_context->action = CLI_REM_XATTR;
1013 			cli_context->blobid = atoll(optarg);
1014 			snprintf(cli_context->key, BUFSIZE, "%s", argv[optind]);
1015 			break;
1016 		case 'l':
1017 			if (strcmp("bdevs", optarg) == 0) {
1018 				cmd_chosen++;
1019 				cli_context->action = CLI_LIST_BDEVS;
1020 			} else if (strcmp("blobs", optarg) == 0) {
1021 				cmd_chosen++;
1022 				cli_context->action = CLI_LIST_BLOBS;
1023 			} else {
1024 				usage(cli_context, "ERROR: invalid option for list\n");
1025 			}
1026 			break;
1027 		case 'm':
1028 			cmd_chosen++;
1029 			cli_context->action = CLI_IMPORT;
1030 			cli_context->blobid = atoll(optarg);
1031 			snprintf(cli_context->file, BUFSIZE, "%s", argv[optind]);
1032 			break;
1033 		case 'n':
1034 			cmd_chosen++;
1035 			cli_context->num_clusters = atoi(optarg);
1036 			if (cli_context->num_clusters > 0) {
1037 				cli_context->action = CLI_CREATE_BLOB;
1038 			} else {
1039 				usage(cli_context, "ERROR: invalid option for new\n");
1040 			}
1041 			break;
1042 		case 'p':
1043 			cmd_chosen++;
1044 			cli_context->action = CLI_SET_SUPER;
1045 			cli_context->superid = atoll(optarg);
1046 			break;
1047 		case 'S':
1048 			if (cli_context->cli_mode == CLI_MODE_CMD) {
1049 				cli_context->action = CLI_NONE;
1050 				cli_context->cli_mode = CLI_MODE_SHELL;
1051 			}
1052 			cli_context->action = CLI_NONE;
1053 			break;
1054 		case 's':
1055 			cmd_chosen++;
1056 			if (strcmp("bs", optarg) == 0) {
1057 				cli_context->action = CLI_SHOW_BS;
1058 			} else {
1059 				cli_context->action = CLI_SHOW_BLOB;
1060 				cli_context->blobid = atoll(optarg);
1061 			}
1062 			break;
1063 		case 'T':
1064 			if (cli_context->cli_mode == CLI_MODE_CMD) {
1065 				cmd_chosen++;
1066 				cli_context->cli_mode = CLI_MODE_SCRIPT;
1067 				if (argv[optind] && (strcmp("ignore", argv[optind]) == 0)) {
1068 					g_script.ignore_errors = true;
1069 				} else {
1070 					g_script.ignore_errors = false;
1071 				}
1072 				snprintf(cli_context->script_file, BUFSIZE, "%s", optarg);
1073 			} else {
1074 				cli_context->action = CLI_NONE;
1075 			}
1076 			break;
1077 		case 'X':
1078 			cmd_chosen++;
1079 			cli_context->action = CLI_SHELL_EXIT;
1080 			break;
1081 		case 'x':
1082 			cmd_chosen++;
1083 			cli_context->action = CLI_SET_XATTR;
1084 			cli_context->blobid = atoll(optarg);
1085 			snprintf(cli_context->key, BUFSIZE, "%s", argv[optind]);
1086 			snprintf(cli_context->value, BUFSIZE, "%s", argv[optind + 1]);
1087 			break;
1088 		default:
1089 			usage(cli_context, "ERROR: invalid option\n");
1090 		}
1091 		/* config file is the only option that can be combined */
1092 		if (cfg_specified == false  && cmd_chosen > 1) {
1093 			usage(cli_context, "Error: Please choose only one command\n");
1094 		}
1095 	}
1096 
1097 	if (cli_context->cli_mode == CLI_MODE_CMD && cmd_chosen == 0) {
1098 		usage(cli_context, "Error: Please choose a command.\n");
1099 		exit(1);
1100 	}
1101 
1102 	/* a few options require some extra paramters */
1103 	if (cli_context->action == CLI_SET_XATTR) {
1104 		snprintf(cli_context->key, BUFSIZE, "%s", argv[3]);
1105 		snprintf(cli_context->value, BUFSIZE, "%s", argv[4]);
1106 	}
1107 	if (cli_context->action == CLI_REM_XATTR) {
1108 		snprintf(cli_context->key, BUFSIZE, "%s", argv[3]);
1109 	}
1110 
1111 	if (cli_context->action == CLI_DUMP ||
1112 	    cli_context->action == CLI_IMPORT) {
1113 		snprintf(cli_context->file, BUFSIZE, "%s", argv[3]);
1114 	}
1115 
1116 	if (cli_context->action == CLI_FILL) {
1117 		cli_context->fill_value = atoi(argv[3]);
1118 	}
1119 
1120 	/* in shell mode we'll call getopt multiple times so need to reset its index */
1121 	optind = 0;
1122 	return (cmd_chosen > 0);
1123 }
1124 
1125 /*
1126  * In script mode, we parsed a script file at startup and saved off a bunch of cmd
1127  * lines that we now parse with each run of cli_start so we us the same cmd parser
1128  * as cmd and shell modes.
1129  */
1130 static bool
1131 line_parser(struct cli_context_t *cli_context)
1132 {
1133 	bool cmd_chosen;
1134 	char *tok = NULL;
1135 	int blob_num = 0;
1136 	int start_idx = cli_context->argc;
1137 	int i;
1138 
1139 	printf("\nSCRIPT NOW PROCESSING: %s\n", g_script.cmdline[g_script.cmdline_idx]);
1140 	tok = strtok(g_script.cmdline[g_script.cmdline_idx], " ");
1141 	while (tok != NULL) {
1142 		/*
1143 		 * We support one replaceable token right now, a $Bn
1144 		 * represents the blobid that was created in position n
1145 		 * so fish this out now and use it here.
1146 		 */
1147 		cli_context->argv[cli_context->argc] = strdup(tok);
1148 		if (tok[0] == '$' && tok[1] == 'B') {
1149 			tok += 2;
1150 			blob_num = atoi(tok);
1151 			cli_context->argv[cli_context->argc] =
1152 				realloc(cli_context->argv[cli_context->argc], BUFSIZE);
1153 			if (cli_context->argv[cli_context->argc] == NULL) {
1154 				printf("ERROR: unable to realloc memory\n");
1155 				spdk_app_stop(-1);
1156 			}
1157 			if (g_script.blobid[blob_num] == 0) {
1158 				printf("ERROR: There is no blob for $B%d\n",
1159 				       blob_num);
1160 			}
1161 			snprintf(cli_context->argv[cli_context->argc], BUFSIZE,
1162 				 "%" PRIu64, g_script.blobid[blob_num]);
1163 		}
1164 		cli_context->argc++;
1165 		tok = strtok(NULL, " ");
1166 	}
1167 
1168 	/* call parse cmd line with user input as args */
1169 	cmd_chosen = cmd_parser(cli_context->argc, &cli_context->argv[0], cli_context);
1170 
1171 	/* free strdup memory and reset arg count for next shell interaction */
1172 	for (i = start_idx; i < cli_context->argc; i++) {
1173 		free(cli_context->argv[i]);
1174 		cli_context->argv[i] = NULL;
1175 	}
1176 	cli_context->argc = 1;
1177 
1178 	g_script.cmdline_idx++;
1179 	assert(g_script.cmdline_idx < MAX_SCRIPT_LINES);
1180 
1181 	if (cmd_chosen == false) {
1182 		printf("ERROR: Invalid script line starting with: %s\n\n",
1183 		       g_script.cmdline[g_script.cmdline_idx - 1]);
1184 		if (g_script.ignore_errors == false) {
1185 			printf("** Aborting **\n");
1186 			cli_context->action = CLI_SHELL_EXIT;
1187 			cmd_chosen = true;
1188 			unload_bs(cli_context, "", 0);
1189 		} else {
1190 			printf("** Skipping **\n");
1191 		}
1192 	}
1193 
1194 	return cmd_chosen;
1195 }
1196 
1197 /*
1198  * For script mode, we read a series of commands from a text file and store them
1199  * in a global struct. That, along with the cli_mode that tells us we're in
1200  * script mode is what feeds the rest of the app in the same way as is it were
1201  * getting commands from shell mode.
1202  */
1203 static void
1204 parse_script(struct cli_context_t *cli_context)
1205 {
1206 	FILE *fp = NULL;
1207 	size_t bufsize = BUFSIZE;
1208 	int64_t bytes_in = 0;
1209 	int i = 0;
1210 
1211 	/* initialize global script values */
1212 	for (i = 0; i < MAX_SCRIPT_BLOBS; i++) {
1213 		g_script.blobid[i] = 0;
1214 	}
1215 	g_script.blobid_idx = 0;
1216 	g_script.cmdline_idx = 0;
1217 	i = 0;
1218 
1219 	fp = fopen(cli_context->script_file, "r");
1220 	if (fp == NULL) {
1221 		printf("ERROR: unable to open script: %s\n",
1222 		       cli_context->script_file);
1223 		cli_cleanup(cli_context);
1224 		exit(-1);
1225 	}
1226 
1227 	do {
1228 		bytes_in = getline(&g_script.cmdline[i], &bufsize, fp);
1229 		if (bytes_in > 0) {
1230 			/* replace newline with null */
1231 			spdk_str_chomp(g_script.cmdline[i]);
1232 
1233 			/* ignore comments */
1234 			if (g_script.cmdline[i][0] != '#') {
1235 				i++;
1236 			}
1237 		}
1238 	} while (bytes_in != -1 && i < MAX_SCRIPT_LINES);
1239 	fclose(fp);
1240 
1241 	/* add an exit cmd in case they didn't */
1242 	g_script.cmdline[i] = realloc(g_script.cmdline[i], BUFSIZE);
1243 	if (g_script.cmdline[i] == NULL)  {
1244 		int j;
1245 
1246 		for (j = 0; j < i; j++) {
1247 			free(g_script.cmdline[j]);
1248 			g_script.cmdline[j] = NULL;
1249 		}
1250 		unload_bs(cli_context, "ERROR: unable to alloc memory.\n", 0);
1251 	}
1252 	snprintf(g_script.cmdline[i], BUFSIZE, "%s", "-X");
1253 	g_script.max_index = i;
1254 }
1255 
1256 /*
1257  * Provides for a shell interface as opposed to one shot command line.
1258  */
1259 static bool
1260 cli_shell(void *arg1, void *arg2)
1261 {
1262 	struct cli_context_t *cli_context = arg1;
1263 	char *line = NULL;
1264 	ssize_t buf_size = 0;
1265 	ssize_t bytes_in = 0;
1266 	ssize_t tok_len = 0;
1267 	char *tok = NULL;
1268 	bool cmd_chosen = false;
1269 	int start_idx = cli_context->argc;
1270 	int i;
1271 
1272 	printf("blob> ");
1273 	bytes_in = getline(&line, &buf_size, stdin);
1274 
1275 	/* parse input and update cli_context so we can use common option parser */
1276 	if (bytes_in > 0) {
1277 		tok = strtok(line, " ");
1278 	}
1279 	while ((tok != NULL) && (cli_context->argc < MAX_ARGS)) {
1280 		cli_context->argv[cli_context->argc] = strdup(tok);
1281 		tok_len = strlen(tok);
1282 		cli_context->argc++;
1283 		tok = strtok(NULL, " ,.-");
1284 	}
1285 
1286 	/* replace newline on last arg with null */
1287 	if (tok_len) {
1288 		spdk_str_chomp(cli_context->argv[cli_context->argc - 1]);
1289 	}
1290 
1291 	/* call parse cmd line with user input as args */
1292 	cmd_chosen = cmd_parser(cli_context->argc, &cli_context->argv[0], cli_context);
1293 
1294 	/* free strdup mem & reset arg count for next shell interaction */
1295 	for (i = start_idx; i < cli_context->argc; i++) {
1296 		free(cli_context->argv[i]);
1297 		cli_context->argv[i] = NULL;
1298 	}
1299 	cli_context->argc = 1;
1300 
1301 	free(line);
1302 
1303 	return cmd_chosen;
1304 }
1305 
1306 /*
1307  * This is the function we pass into the SPDK framework that gets
1308  * called first.
1309  */
1310 static void
1311 cli_start(void *arg1, void *arg2)
1312 {
1313 	struct cli_context_t *cli_context = arg1;
1314 
1315 	/*
1316 	 * If we're in script mode, we already have a list of commands so
1317 	 * just need to pull them out one at a time and process them.
1318 	 */
1319 	if (cli_context->cli_mode == CLI_MODE_SCRIPT) {
1320 		while (line_parser(cli_context) == false);
1321 	}
1322 
1323 	/*
1324 	 * The initial cmd line options are parsed once before this function is
1325 	 * called so if there is no action, we're in shell mode and will loop
1326 	 * here until a a valid option is parsed and returned.
1327 	 */
1328 	if (cli_context->action == CLI_NONE) {
1329 		while (cli_shell(cli_context, NULL) == false);
1330 	}
1331 
1332 	/* Decide what to do next based on cmd line parsing. */
1333 	switch (cli_context->action) {
1334 	case CLI_SET_SUPER:
1335 	case CLI_SHOW_BS:
1336 	case CLI_CREATE_BLOB:
1337 	case CLI_SET_XATTR:
1338 	case CLI_REM_XATTR:
1339 	case CLI_SHOW_BLOB:
1340 	case CLI_LIST_BLOBS:
1341 	case CLI_DUMP:
1342 	case CLI_IMPORT:
1343 	case CLI_FILL:
1344 		load_bs(cli_context);
1345 		break;
1346 	case CLI_INIT_BS:
1347 		init_bs(cli_context);
1348 		break;
1349 	case CLI_LIST_BDEVS:
1350 		list_bdevs(cli_context);
1351 		break;
1352 	case CLI_SHELL_EXIT:
1353 		/*
1354 		 * Because shell mode reuses cmd mode functions, the blobstore
1355 		 * is loaded/unloaded with every action so we just need to
1356 		 * stop the framework. For this app there's no need to optimize
1357 		 * and keep the blobstore open while the app is in shell mode.
1358 		 */
1359 		spdk_app_stop(0);
1360 		break;
1361 	case CLI_HELP:
1362 		usage(cli_context, "");
1363 		unload_complete(cli_context, 0);
1364 		break;
1365 	default:
1366 		/* should never get here */
1367 		spdk_app_stop(-1);
1368 		break;
1369 	}
1370 }
1371 
1372 int
1373 main(int argc, char **argv)
1374 {
1375 	struct spdk_app_opts opts = {};
1376 	struct cli_context_t *cli_context = NULL;
1377 	bool cmd_chosen;
1378 	int rc = 0;
1379 
1380 	if (argc < 2) {
1381 		usage(cli_context, "ERROR: Invalid option\n");
1382 		exit(-1);
1383 	}
1384 
1385 	cli_context = calloc(1, sizeof(struct cli_context_t));
1386 	if (cli_context == NULL) {
1387 		printf("ERROR: could not allocate context structure\n");
1388 		exit(-1);
1389 	}
1390 	cli_context->bdev_name = bdev_name;
1391 	/* default to CMD mode until we've parsed the first parms */
1392 	cli_context->cli_mode = CLI_MODE_CMD;
1393 
1394 	cli_context->argv[0] = strdup(argv[0]);
1395 	cli_context->argc = 1;
1396 
1397 	/* parse command line */
1398 	cmd_chosen = cmd_parser(argc, argv, cli_context);
1399 	free(cli_context->argv[0]);
1400 	cli_context->argv[0] = NULL;
1401 	if (cmd_chosen == false) {
1402 		cli_cleanup(cli_context);
1403 		exit(-1);
1404 	}
1405 
1406 	/* after displaying help, just exit */
1407 	if (cli_context->action == CLI_HELP) {
1408 		usage(cli_context, "");
1409 		cli_cleanup(cli_context);
1410 		exit(-1);
1411 	}
1412 
1413 	/* if they don't supply a conf name, use the default */
1414 	if (!cli_context->config_file) {
1415 		cli_context->config_file = program_conf;
1416 	}
1417 
1418 	/* if the config file doesn't exist, tell them how to make one */
1419 	if (access(cli_context->config_file, F_OK) == -1) {
1420 		printf("Error: No config file found.\n");
1421 		printf("To create a config file named 'blobcli.conf' for your NVMe device:\n");
1422 		printf("   <path to spdk>/scripts/gen_nvme.sh > blobcli.conf\n");
1423 		printf("and then re-run the cli tool.\n");
1424 		exit(1);
1425 	}
1426 
1427 	/*
1428 	 * For script mode we keep a bunch of stuff in a global since
1429 	 * none if it is passed back and forth to SPDK.
1430 	 */
1431 	if (cli_context->cli_mode == CLI_MODE_SCRIPT) {
1432 		/*
1433 		 * Now we'll build up the global which will direct this run of the app
1434 		 * as it will have a list (g_script) of all of the commands line by
1435 		 * line as if they were typed in on the shell at cmd line.
1436 		 */
1437 		parse_script(cli_context);
1438 	}
1439 
1440 	/* Set default values in opts struct along with name and conf file. */
1441 	spdk_app_opts_init(&opts);
1442 	opts.name = "blobcli";
1443 	opts.config_file = cli_context->config_file;
1444 
1445 	cli_context->app_started = true;
1446 	rc = spdk_app_start(&opts, cli_start, cli_context, NULL);
1447 	if (rc) {
1448 		printf("ERROR!\n");
1449 	}
1450 
1451 	/* Free up memory that we allocated */
1452 	cli_cleanup(cli_context);
1453 
1454 	/* Gracefully close out all of the SPDK subsystems. */
1455 	spdk_app_fini();
1456 	return rc;
1457 }
1458