1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (C) 2018 Intel Corporation. 3 * All rights reserved. 4 * Copyright (c) 2022, 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 5 */ 6 7 #include "spdk/stdinc.h" 8 9 #include "spdk/init.h" 10 #include "spdk/util.h" 11 #include "spdk/file.h" 12 #include "spdk/log.h" 13 #include "spdk/env.h" 14 #include "spdk/thread.h" 15 #include "spdk/jsonrpc.h" 16 #include "spdk/rpc.h" 17 #include "spdk/string.h" 18 19 #include "spdk_internal/event.h" 20 21 #define SPDK_DEBUG_APP_CFG(...) SPDK_DEBUGLOG(app_config, __VA_ARGS__) 22 23 /* JSON configuration format is as follows 24 * 25 * { 26 * "subsystems" : [ <<== *subsystems JSON array 27 * { <<== *subsystems_it array entry pointer (iterator) 28 * "subsystem": "<< SUBSYSTEM NAME >>", 29 * "config": [ <<== *config JSON array 30 * { <<== *config_it array entry pointer (iterator) 31 * "method": "<< METHOD NAME >>", <<== *method 32 * "params": { << PARAMS >> } <<== *params 33 * }, 34 * << MORE "config" ARRY ENTRIES >> 35 * ] 36 * }, 37 * << MORE "subsystems" ARRAY ENTRIES >> 38 * ] 39 * 40 * << ANYTHING ELSE IS IGNORED IN ROOT OBJECT>> 41 * } 42 * 43 */ 44 45 struct load_json_config_ctx; 46 typedef void (*client_resp_handler)(struct load_json_config_ctx *, 47 struct spdk_jsonrpc_client_response *); 48 49 #define RPC_SOCKET_PATH_MAX SPDK_SIZEOF_MEMBER(struct sockaddr_un, sun_path) 50 51 /* 1s connections timeout */ 52 #define RPC_CLIENT_CONNECT_TIMEOUT_US (1U * 1000U * 1000U) 53 54 /* 55 * Currently there is no timeout in SPDK for any RPC command. This result that 56 * we can't put a hard limit during configuration load as it most likely randomly fail. 57 * So just print WARNLOG every 10s. */ 58 #define RPC_CLIENT_REQUEST_TIMEOUT_US (10U * 1000 * 1000) 59 60 struct load_json_config_ctx { 61 /* Thread used during configuration. */ 62 struct spdk_thread *thread; 63 spdk_subsystem_init_fn cb_fn; 64 void *cb_arg; 65 bool stop_on_error; 66 67 /* Current subsystem */ 68 struct spdk_json_val *subsystems; /* "subsystems" array */ 69 struct spdk_json_val *subsystems_it; /* current subsystem array position in "subsystems" array */ 70 71 struct spdk_json_val *subsystem_name; /* current subsystem name */ 72 73 /* Current "config" entry we are processing */ 74 struct spdk_json_val *config; /* "config" array */ 75 struct spdk_json_val *config_it; /* current config position in "config" array */ 76 77 /* Current request id we are sending. */ 78 uint32_t rpc_request_id; 79 80 /* Whole configuration file read and parsed. */ 81 size_t json_data_size; 82 char *json_data; 83 84 size_t values_cnt; 85 struct spdk_json_val *values; 86 87 char rpc_socket_path_temp[RPC_SOCKET_PATH_MAX + 1]; 88 89 struct spdk_jsonrpc_client *client_conn; 90 struct spdk_poller *client_conn_poller; 91 92 client_resp_handler client_resp_cb; 93 94 /* Timeout for current RPC client action. */ 95 uint64_t timeout; 96 97 /* Signals that the code should follow deprecated path of execution. */ 98 bool initalize_subsystems; 99 }; 100 101 static void app_json_config_load_subsystem(void *_ctx); 102 103 static void 104 app_json_config_load_done(struct load_json_config_ctx *ctx, int rc) 105 { 106 spdk_poller_unregister(&ctx->client_conn_poller); 107 if (ctx->client_conn != NULL) { 108 spdk_jsonrpc_client_close(ctx->client_conn); 109 } 110 111 spdk_rpc_server_finish(ctx->rpc_socket_path_temp); 112 113 SPDK_DEBUG_APP_CFG("Config load finished with rc %d\n", rc); 114 ctx->cb_fn(rc, ctx->cb_arg); 115 116 free(ctx->json_data); 117 free(ctx->values); 118 free(ctx); 119 } 120 121 static void 122 rpc_client_set_timeout(struct load_json_config_ctx *ctx, uint64_t timeout_us) 123 { 124 ctx->timeout = spdk_get_ticks() + timeout_us * spdk_get_ticks_hz() / (1000 * 1000); 125 } 126 127 static int 128 rpc_client_check_timeout(struct load_json_config_ctx *ctx) 129 { 130 if (ctx->timeout < spdk_get_ticks()) { 131 SPDK_WARNLOG("RPC client command timeout.\n"); 132 return -ETIMEDOUT; 133 } 134 135 return 0; 136 } 137 138 struct json_write_buf { 139 char data[1024]; 140 unsigned cur_off; 141 }; 142 143 static int 144 json_write_stdout(void *cb_ctx, const void *data, size_t size) 145 { 146 struct json_write_buf *buf = cb_ctx; 147 size_t rc; 148 149 rc = snprintf(buf->data + buf->cur_off, sizeof(buf->data) - buf->cur_off, 150 "%s", (const char *)data); 151 if (rc > 0) { 152 buf->cur_off += rc; 153 } 154 return rc == size ? 0 : -1; 155 } 156 157 static int 158 rpc_client_poller(void *arg) 159 { 160 struct load_json_config_ctx *ctx = arg; 161 struct spdk_jsonrpc_client_response *resp; 162 client_resp_handler cb; 163 int rc; 164 165 assert(spdk_get_thread() == ctx->thread); 166 167 rc = spdk_jsonrpc_client_poll(ctx->client_conn, 0); 168 if (rc == 0) { 169 rc = rpc_client_check_timeout(ctx); 170 if (rc == -ETIMEDOUT) { 171 rpc_client_set_timeout(ctx, RPC_CLIENT_REQUEST_TIMEOUT_US); 172 rc = 0; 173 } 174 } 175 176 if (rc == 0) { 177 /* No response yet */ 178 return SPDK_POLLER_BUSY; 179 } else if (rc < 0) { 180 app_json_config_load_done(ctx, rc); 181 return SPDK_POLLER_BUSY; 182 } 183 184 resp = spdk_jsonrpc_client_get_response(ctx->client_conn); 185 assert(resp); 186 187 if (resp->error) { 188 struct json_write_buf buf = {}; 189 struct spdk_json_write_ctx *w = spdk_json_write_begin(json_write_stdout, 190 &buf, SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE); 191 192 if (w == NULL) { 193 SPDK_ERRLOG("error response: (?)\n"); 194 } else { 195 spdk_json_write_val(w, resp->error); 196 spdk_json_write_end(w); 197 SPDK_ERRLOG("error response: \n%s\n", buf.data); 198 } 199 } 200 201 if (resp->error && ctx->stop_on_error) { 202 spdk_jsonrpc_client_free_response(resp); 203 app_json_config_load_done(ctx, -EINVAL); 204 } else { 205 /* We have response so we must have callback for it. */ 206 cb = ctx->client_resp_cb; 207 assert(cb != NULL); 208 209 /* Mark we are done with this handler. */ 210 ctx->client_resp_cb = NULL; 211 cb(ctx, resp); 212 } 213 214 215 return SPDK_POLLER_BUSY; 216 } 217 218 static int 219 rpc_client_connect_poller(void *_ctx) 220 { 221 struct load_json_config_ctx *ctx = _ctx; 222 int rc; 223 224 rc = spdk_jsonrpc_client_poll(ctx->client_conn, 0); 225 if (rc != -ENOTCONN) { 226 /* We are connected. Start regular poller and issue first request */ 227 spdk_poller_unregister(&ctx->client_conn_poller); 228 ctx->client_conn_poller = SPDK_POLLER_REGISTER(rpc_client_poller, ctx, 100); 229 app_json_config_load_subsystem(ctx); 230 } else { 231 rc = rpc_client_check_timeout(ctx); 232 if (rc) { 233 app_json_config_load_done(ctx, rc); 234 } 235 236 return SPDK_POLLER_IDLE; 237 } 238 239 return SPDK_POLLER_BUSY; 240 } 241 242 static int 243 client_send_request(struct load_json_config_ctx *ctx, struct spdk_jsonrpc_client_request *request, 244 client_resp_handler client_resp_cb) 245 { 246 int rc; 247 248 assert(spdk_get_thread() == ctx->thread); 249 250 ctx->client_resp_cb = client_resp_cb; 251 rpc_client_set_timeout(ctx, RPC_CLIENT_REQUEST_TIMEOUT_US); 252 rc = spdk_jsonrpc_client_send_request(ctx->client_conn, request); 253 254 if (rc) { 255 SPDK_DEBUG_APP_CFG("Sending request to client failed (%d)\n", rc); 256 } 257 258 return rc; 259 } 260 261 static int 262 cap_string(const struct spdk_json_val *val, void *out) 263 { 264 const struct spdk_json_val **vptr = out; 265 266 if (val->type != SPDK_JSON_VAL_STRING) { 267 return -EINVAL; 268 } 269 270 *vptr = val; 271 return 0; 272 } 273 274 static int 275 cap_object(const struct spdk_json_val *val, void *out) 276 { 277 const struct spdk_json_val **vptr = out; 278 279 if (val->type != SPDK_JSON_VAL_OBJECT_BEGIN) { 280 return -EINVAL; 281 } 282 283 *vptr = val; 284 return 0; 285 } 286 287 288 static int 289 cap_array_or_null(const struct spdk_json_val *val, void *out) 290 { 291 const struct spdk_json_val **vptr = out; 292 293 if (val->type != SPDK_JSON_VAL_ARRAY_BEGIN && val->type != SPDK_JSON_VAL_NULL) { 294 return -EINVAL; 295 } 296 297 *vptr = val; 298 return 0; 299 } 300 301 struct config_entry { 302 char *method; 303 struct spdk_json_val *params; 304 }; 305 306 static struct spdk_json_object_decoder jsonrpc_cmd_decoders[] = { 307 {"method", offsetof(struct config_entry, method), spdk_json_decode_string}, 308 {"params", offsetof(struct config_entry, params), cap_object, true} 309 }; 310 311 static void app_json_config_load_subsystem_config_entry(void *_ctx); 312 313 static void 314 app_json_config_load_subsystem_config_entry_next(struct load_json_config_ctx *ctx, 315 struct spdk_jsonrpc_client_response *resp) 316 { 317 /* Don't care about the response */ 318 spdk_jsonrpc_client_free_response(resp); 319 320 ctx->config_it = spdk_json_next(ctx->config_it); 321 app_json_config_load_subsystem_config_entry(ctx); 322 } 323 324 /* Load "config" entry */ 325 static void 326 app_json_config_load_subsystem_config_entry(void *_ctx) 327 { 328 struct load_json_config_ctx *ctx = _ctx; 329 struct spdk_jsonrpc_client_request *rpc_request; 330 struct spdk_json_write_ctx *w; 331 struct config_entry cfg = {}; 332 struct spdk_json_val *params_end; 333 size_t params_len = 0; 334 uint32_t state_mask = 0, cur_state_mask, startup_runtime = SPDK_RPC_STARTUP | SPDK_RPC_RUNTIME; 335 int rc; 336 337 if (ctx->config_it == NULL) { 338 SPDK_DEBUG_APP_CFG("Subsystem '%.*s': configuration done.\n", ctx->subsystem_name->len, 339 (char *)ctx->subsystem_name->start); 340 ctx->subsystems_it = spdk_json_next(ctx->subsystems_it); 341 /* Invoke later to avoid recurrence */ 342 spdk_thread_send_msg(ctx->thread, app_json_config_load_subsystem, ctx); 343 return; 344 } 345 346 if (spdk_json_decode_object(ctx->config_it, jsonrpc_cmd_decoders, 347 SPDK_COUNTOF(jsonrpc_cmd_decoders), &cfg)) { 348 SPDK_ERRLOG("Failed to decode config entry\n"); 349 app_json_config_load_done(ctx, -EINVAL); 350 goto out; 351 } 352 353 rc = spdk_rpc_get_method_state_mask(cfg.method, &state_mask); 354 if (rc == -ENOENT) { 355 SPDK_ERRLOG("Method '%s' was not found\n", cfg.method); 356 app_json_config_load_done(ctx, rc); 357 goto out; 358 } 359 cur_state_mask = spdk_rpc_get_state(); 360 if ((state_mask & cur_state_mask) != cur_state_mask) { 361 SPDK_DEBUG_APP_CFG("Method '%s' not allowed -> skipping\n", cfg.method); 362 /* Invoke later to avoid recurrence */ 363 ctx->config_it = spdk_json_next(ctx->config_it); 364 spdk_thread_send_msg(ctx->thread, app_json_config_load_subsystem_config_entry, ctx); 365 goto out; 366 } 367 if ((state_mask & startup_runtime) == startup_runtime && cur_state_mask == SPDK_RPC_RUNTIME) { 368 /* Some methods are allowed to be run in both STARTUP and RUNTIME states. 369 * We should not call such methods twice, so ignore the second attempt in RUNTIME state */ 370 SPDK_DEBUG_APP_CFG("Method '%s' has already been run in STARTUP state\n", cfg.method); 371 /* Invoke later to avoid recurrence */ 372 ctx->config_it = spdk_json_next(ctx->config_it); 373 spdk_thread_send_msg(ctx->thread, app_json_config_load_subsystem_config_entry, ctx); 374 goto out; 375 } 376 377 SPDK_DEBUG_APP_CFG("\tmethod: %s\n", cfg.method); 378 379 if (cfg.params) { 380 /* Get _END by skipping params and going back by one element. */ 381 params_end = cfg.params + spdk_json_val_len(cfg.params) - 1; 382 383 /* Need to add one character to include '}' */ 384 params_len = params_end->start - cfg.params->start + 1; 385 386 SPDK_DEBUG_APP_CFG("\tparams: %.*s\n", (int)params_len, (char *)cfg.params->start); 387 } 388 389 rpc_request = spdk_jsonrpc_client_create_request(); 390 if (!rpc_request) { 391 app_json_config_load_done(ctx, -errno); 392 goto out; 393 } 394 395 w = spdk_jsonrpc_begin_request(rpc_request, ctx->rpc_request_id, NULL); 396 if (!w) { 397 spdk_jsonrpc_client_free_request(rpc_request); 398 app_json_config_load_done(ctx, -ENOMEM); 399 goto out; 400 } 401 402 spdk_json_write_named_string(w, "method", cfg.method); 403 404 if (cfg.params) { 405 /* No need to parse "params". Just dump the whole content of "params" 406 * directly into the request and let the remote side verify it. */ 407 spdk_json_write_name(w, "params"); 408 spdk_json_write_val_raw(w, cfg.params->start, params_len); 409 } 410 411 spdk_jsonrpc_end_request(rpc_request, w); 412 413 rc = client_send_request(ctx, rpc_request, app_json_config_load_subsystem_config_entry_next); 414 if (rc != 0) { 415 app_json_config_load_done(ctx, -rc); 416 goto out; 417 } 418 out: 419 free(cfg.method); 420 } 421 422 static void 423 subsystem_init_done(int rc, void *arg1) 424 { 425 struct load_json_config_ctx *ctx = arg1; 426 427 if (rc) { 428 app_json_config_load_done(ctx, rc); 429 return; 430 } 431 432 spdk_rpc_set_state(SPDK_RPC_RUNTIME); 433 /* Another round. This time for RUNTIME methods */ 434 SPDK_DEBUG_APP_CFG("'framework_start_init' done - continuing configuration\n"); 435 436 assert(ctx != NULL); 437 if (ctx->subsystems) { 438 ctx->subsystems_it = spdk_json_array_first(ctx->subsystems); 439 } 440 441 app_json_config_load_subsystem(ctx); 442 } 443 444 static struct spdk_json_object_decoder subsystem_decoders[] = { 445 {"subsystem", offsetof(struct load_json_config_ctx, subsystem_name), cap_string}, 446 {"config", offsetof(struct load_json_config_ctx, config), cap_array_or_null} 447 }; 448 449 /* 450 * Start loading subsystem pointed by ctx->subsystems_it. This must point to the 451 * beginning of the "subsystem" object in "subsystems" array or be NULL. If it is 452 * NULL then no more subsystems to load. 453 * 454 * If "initalize_subsystems" is unset, then the function performs one iteration 455 * and does not call subsystem initialization. 456 * 457 * There are two iterations, when "initalize_subsystems" context flag is set: 458 * 459 * In first iteration only STARTUP RPC methods are used, other methods are ignored. When 460 * allsubsystems are walked the ctx->subsystems_it became NULL and "framework_start_init" 461 * is called to let the SPDK move to RUNTIME state (initialize all subsystems) and 462 * second iteration begins. 463 * 464 * In second iteration "subsystems" array is walked through again, this time only 465 * RUNTIME RPC methods are used. When ctx->subsystems_it became NULL second time it 466 * indicate that there is no more subsystems to load. The cb_fn is called to finish 467 * configuration. 468 */ 469 static void 470 app_json_config_load_subsystem(void *_ctx) 471 { 472 struct load_json_config_ctx *ctx = _ctx; 473 474 if (ctx->subsystems_it == NULL) { 475 if (ctx->initalize_subsystems && spdk_rpc_get_state() == SPDK_RPC_STARTUP) { 476 SPDK_DEBUG_APP_CFG("No more entries for current state, calling 'framework_start_init'\n"); 477 spdk_subsystem_init(subsystem_init_done, ctx); 478 } else { 479 SPDK_DEBUG_APP_CFG("No more entries for current state\n"); 480 app_json_config_load_done(ctx, 0); 481 } 482 483 return; 484 } 485 486 /* Capture subsystem name and config array */ 487 if (spdk_json_decode_object(ctx->subsystems_it, subsystem_decoders, 488 SPDK_COUNTOF(subsystem_decoders), ctx)) { 489 SPDK_ERRLOG("Failed to parse subsystem configuration\n"); 490 app_json_config_load_done(ctx, -EINVAL); 491 return; 492 } 493 494 SPDK_DEBUG_APP_CFG("Loading subsystem '%.*s' configuration\n", ctx->subsystem_name->len, 495 (char *)ctx->subsystem_name->start); 496 497 /* Get 'config' array first configuration entry */ 498 ctx->config_it = spdk_json_array_first(ctx->config); 499 app_json_config_load_subsystem_config_entry(ctx); 500 } 501 502 static void * 503 read_file(const char *filename, size_t *size) 504 { 505 FILE *file = fopen(filename, "r"); 506 void *data; 507 508 if (file == NULL) { 509 /* errno is set by fopen */ 510 return NULL; 511 } 512 513 data = spdk_posix_file_load(file, size); 514 fclose(file); 515 return data; 516 } 517 518 static int 519 parse_json(void *json, ssize_t json_size, struct load_json_config_ctx *ctx) 520 { 521 void *end; 522 ssize_t rc; 523 524 if (!json || json_size <= 0) { 525 SPDK_ERRLOG("JSON data cannot be empty\n"); 526 goto err; 527 } 528 529 ctx->json_data = calloc(1, json_size); 530 if (!ctx->json_data) { 531 goto err; 532 } 533 memcpy(ctx->json_data, json, json_size); 534 ctx->json_data_size = json_size; 535 536 rc = spdk_json_parse(ctx->json_data, ctx->json_data_size, NULL, 0, &end, 537 SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); 538 if (rc < 0) { 539 SPDK_ERRLOG("Parsing JSON configuration failed (%zd)\n", rc); 540 goto err; 541 } 542 543 ctx->values_cnt = rc; 544 ctx->values = calloc(ctx->values_cnt, sizeof(struct spdk_json_val)); 545 if (ctx->values == NULL) { 546 SPDK_ERRLOG("Out of memory\n"); 547 goto err; 548 } 549 550 rc = spdk_json_parse(ctx->json_data, ctx->json_data_size, ctx->values, 551 ctx->values_cnt, &end, 552 SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); 553 if ((size_t)rc != ctx->values_cnt) { 554 SPDK_ERRLOG("Parsing JSON configuration failed (%zd)\n", rc); 555 goto err; 556 } 557 558 return 0; 559 err: 560 free(ctx->values); 561 return -EINVAL; 562 } 563 564 static void 565 json_config_prepare_ctx(spdk_subsystem_init_fn cb_fn, void *cb_arg, bool stop_on_error, void *json, 566 ssize_t json_size, bool initalize_subsystems) 567 { 568 struct load_json_config_ctx *ctx = calloc(1, sizeof(*ctx)); 569 int rc; 570 571 if (!ctx) { 572 cb_fn(-ENOMEM, cb_arg); 573 return; 574 } 575 576 ctx->cb_fn = cb_fn; 577 ctx->cb_arg = cb_arg; 578 ctx->stop_on_error = stop_on_error; 579 ctx->thread = spdk_get_thread(); 580 ctx->initalize_subsystems = initalize_subsystems; 581 582 rc = parse_json(json, json_size, ctx); 583 if (rc < 0) { 584 goto fail; 585 } 586 587 /* Capture subsystems array */ 588 rc = spdk_json_find_array(ctx->values, "subsystems", NULL, &ctx->subsystems); 589 switch (rc) { 590 case 0: 591 /* Get first subsystem */ 592 ctx->subsystems_it = spdk_json_array_first(ctx->subsystems); 593 if (ctx->subsystems_it == NULL) { 594 SPDK_NOTICELOG("'subsystems' configuration is empty\n"); 595 } 596 break; 597 case -EPROTOTYPE: 598 SPDK_ERRLOG("Invalid JSON configuration: not enclosed in {}.\n"); 599 goto fail; 600 case -ENOENT: 601 SPDK_WARNLOG("No 'subsystems' key JSON configuration file.\n"); 602 break; 603 case -EDOM: 604 SPDK_ERRLOG("Invalid JSON configuration: 'subsystems' should be an array.\n"); 605 goto fail; 606 default: 607 SPDK_ERRLOG("Failed to parse JSON configuration.\n"); 608 goto fail; 609 } 610 611 /* FIXME: rpc client should use socketpair() instead of this temporary socket nonsense */ 612 rc = snprintf(ctx->rpc_socket_path_temp, sizeof(ctx->rpc_socket_path_temp), 613 "%s.%d_%"PRIu64"_config", SPDK_DEFAULT_RPC_ADDR, getpid(), spdk_get_ticks()); 614 if (rc >= (int)sizeof(ctx->rpc_socket_path_temp)) { 615 SPDK_ERRLOG("Socket name create failed\n"); 616 goto fail; 617 } 618 619 rc = spdk_rpc_initialize(ctx->rpc_socket_path_temp, NULL); 620 if (rc) { 621 goto fail; 622 } 623 624 ctx->client_conn = spdk_jsonrpc_client_connect(ctx->rpc_socket_path_temp, AF_UNIX); 625 if (ctx->client_conn == NULL) { 626 SPDK_ERRLOG("Failed to connect to '%s'\n", ctx->rpc_socket_path_temp); 627 goto fail; 628 } 629 630 rpc_client_set_timeout(ctx, RPC_CLIENT_CONNECT_TIMEOUT_US); 631 ctx->client_conn_poller = SPDK_POLLER_REGISTER(rpc_client_connect_poller, ctx, 100); 632 return; 633 634 fail: 635 app_json_config_load_done(ctx, -EINVAL); 636 } 637 638 SPDK_LOG_DEPRECATION_REGISTER(spdk_subsystem_init_from_json_config, 639 "spdk_subsystem_init_from_json_config is deprecated", "v24.09", 0); 640 641 void 642 spdk_subsystem_init_from_json_config(const char *json_config_file, const char *rpc_addr, 643 spdk_subsystem_init_fn cb_fn, void *cb_arg, 644 bool stop_on_error) 645 { 646 char *json = NULL; 647 ssize_t json_size = 0; 648 649 SPDK_LOG_DEPRECATED(spdk_subsystem_init_from_json_config); 650 651 assert(cb_fn); 652 653 json = read_file(json_config_file, &json_size); 654 if (!json) { 655 SPDK_ERRLOG("Could not read JSON config file\n"); 656 cb_fn(-EINVAL, cb_arg); 657 return; 658 } 659 660 json_config_prepare_ctx(cb_fn, cb_arg, stop_on_error, json, json_size, true); 661 free(json); 662 } 663 664 void 665 spdk_subsystem_load_config(void *json, ssize_t json_size, spdk_subsystem_init_fn cb_fn, 666 void *cb_arg, bool stop_on_error) 667 { 668 assert(cb_fn); 669 json_config_prepare_ctx(cb_fn, cb_arg, stop_on_error, json, json_size, false); 670 } 671 672 SPDK_LOG_REGISTER_COMPONENT(app_config) 673