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