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