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