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