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