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