Spdk/lib/init/json_config.c
Jim Harris 488570ebd4 Replace most BSD 3-clause license text with SPDX identifier.
Many open source projects have moved to using SPDX identifiers
to specify license information, reducing the amount of
boilerplate code in every source file.  This patch replaces
the bulk of SPDK .c, .cpp and Makefiles with the BSD-3-Clause
identifier.

Almost all of these files share the exact same license text,
and this patch only modifies the files that contain the
most common license text.  There can be slight variations
because the third clause contains company names - most say
"Intel Corporation", but there are instances for Nvidia,
Samsung, Eideticom and even "the copyright holder".

Used a bash script to automate replacement of the license text
with SPDX identifier which is checked into scripts/spdx.sh.

Signed-off-by: Jim Harris <james.r.harris@intel.com>
Change-Id: Iaa88ab5e92ea471691dc298cfe41ebfb5d169780
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/12904
Community-CI: Broadcom CI <spdk-ci.pdl@broadcom.com>
Community-CI: Mellanox Build Bot
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Aleksey Marchuk <alexeymar@nvidia.com>
Reviewed-by: Changpeng Liu <changpeng.liu@intel.com>
Reviewed-by: Dong Yi <dongx.yi@intel.com>
Reviewed-by: Konrad Sztyber <konrad.sztyber@intel.com>
Reviewed-by: Paul Luse <paul.e.luse@intel.com>
Reviewed-by: <qun.wan@intel.com>
2022-06-09 07:35:12 +00:00

612 lines
16 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (c) Intel Corporation.
* All rights reserved.
*/
#include "spdk/stdinc.h"
#include "spdk/init.h"
#include "spdk/util.h"
#include "spdk/file.h"
#include "spdk/log.h"
#include "spdk/env.h"
#include "spdk/thread.h"
#include "spdk/jsonrpc.h"
#include "spdk/rpc.h"
#include "spdk/string.h"
#include "spdk_internal/event.h"
#define SPDK_DEBUG_APP_CFG(...) SPDK_DEBUGLOG(app_config, __VA_ARGS__)
/* JSON configuration format is as follows
*
* {
* "subsystems" : [ <<== *subsystems JSON array
* { <<== *subsystems_it array entry pointer (iterator)
* "subsystem": "<< SUBSYSTEM NAME >>",
* "config": [ <<== *config JSON array
* { <<== *config_it array entry pointer (iterator)
* "method": "<< METHOD NAME >>", <<== *method
* "params": { << PARAMS >> } <<== *params
* },
* << MORE "config" ARRY ENTRIES >>
* ]
* },
* << MORE "subsystems" ARRAY ENTRIES >>
* ]
*
* << ANYTHING ELSE IS IGNORED IN ROOT OBJECT>>
* }
*
*/
struct load_json_config_ctx;
typedef void (*client_resp_handler)(struct load_json_config_ctx *,
struct spdk_jsonrpc_client_response *);
#define RPC_SOCKET_PATH_MAX SPDK_SIZEOF_MEMBER(struct sockaddr_un, sun_path)
/* 1s connections timeout */
#define RPC_CLIENT_CONNECT_TIMEOUT_US (1U * 1000U * 1000U)
/*
* Currently there is no timeout in SPDK for any RPC command. This result that
* we can't put a hard limit during configuration load as it most likely randomly fail.
* So just print WARNLOG every 10s. */
#define RPC_CLIENT_REQUEST_TIMEOUT_US (10U * 1000 * 1000)
struct load_json_config_ctx {
/* Thread used during configuration. */
struct spdk_thread *thread;
spdk_subsystem_init_fn cb_fn;
void *cb_arg;
bool stop_on_error;
/* Current subsystem */
struct spdk_json_val *subsystems; /* "subsystems" array */
struct spdk_json_val *subsystems_it; /* current subsystem array position in "subsystems" array */
struct spdk_json_val *subsystem_name; /* current subsystem name */
/* Current "config" entry we are processing */
struct spdk_json_val *config; /* "config" array */
struct spdk_json_val *config_it; /* current config position in "config" array */
/* Current request id we are sending. */
uint32_t rpc_request_id;
/* Whole configuration file read and parsed. */
size_t json_data_size;
char *json_data;
size_t values_cnt;
struct spdk_json_val *values;
char rpc_socket_path_temp[RPC_SOCKET_PATH_MAX + 1];
struct spdk_jsonrpc_client *client_conn;
struct spdk_poller *client_conn_poller;
client_resp_handler client_resp_cb;
/* Timeout for current RPC client action. */
uint64_t timeout;
};
static void app_json_config_load_subsystem(void *_ctx);
static void
app_json_config_load_done(struct load_json_config_ctx *ctx, int rc)
{
spdk_poller_unregister(&ctx->client_conn_poller);
if (ctx->client_conn != NULL) {
spdk_jsonrpc_client_close(ctx->client_conn);
}
spdk_rpc_finish();
SPDK_DEBUG_APP_CFG("Config load finished with rc %d\n", rc);
ctx->cb_fn(rc, ctx->cb_arg);
free(ctx->json_data);
free(ctx->values);
free(ctx);
}
static void
rpc_client_set_timeout(struct load_json_config_ctx *ctx, uint64_t timeout_us)
{
ctx->timeout = spdk_get_ticks() + timeout_us * spdk_get_ticks_hz() / (1000 * 1000);
}
static int
rpc_client_check_timeout(struct load_json_config_ctx *ctx)
{
if (ctx->timeout < spdk_get_ticks()) {
SPDK_WARNLOG("RPC client command timeout.\n");
return -ETIMEDOUT;
}
return 0;
}
struct json_write_buf {
char data[1024];
unsigned cur_off;
};
static int
json_write_stdout(void *cb_ctx, const void *data, size_t size)
{
struct json_write_buf *buf = cb_ctx;
size_t rc;
rc = snprintf(buf->data + buf->cur_off, sizeof(buf->data) - buf->cur_off,
"%s", (const char *)data);
if (rc > 0) {
buf->cur_off += rc;
}
return rc == size ? 0 : -1;
}
static int
rpc_client_poller(void *arg)
{
struct load_json_config_ctx *ctx = arg;
struct spdk_jsonrpc_client_response *resp;
client_resp_handler cb;
int rc;
assert(spdk_get_thread() == ctx->thread);
rc = spdk_jsonrpc_client_poll(ctx->client_conn, 0);
if (rc == 0) {
rc = rpc_client_check_timeout(ctx);
if (rc == -ETIMEDOUT) {
rpc_client_set_timeout(ctx, RPC_CLIENT_REQUEST_TIMEOUT_US);
rc = 0;
}
}
if (rc == 0) {
/* No response yet */
return SPDK_POLLER_BUSY;
} else if (rc < 0) {
app_json_config_load_done(ctx, rc);
return SPDK_POLLER_BUSY;
}
resp = spdk_jsonrpc_client_get_response(ctx->client_conn);
assert(resp);
if (resp->error) {
struct json_write_buf buf = {};
struct spdk_json_write_ctx *w = spdk_json_write_begin(json_write_stdout,
&buf, SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE);
if (w == NULL) {
SPDK_ERRLOG("error response: (?)\n");
} else {
spdk_json_write_val(w, resp->error);
spdk_json_write_end(w);
SPDK_ERRLOG("error response: \n%s\n", buf.data);
}
}
if (resp->error && ctx->stop_on_error) {
spdk_jsonrpc_client_free_response(resp);
app_json_config_load_done(ctx, -EINVAL);
} else {
/* We have response so we must have callback for it. */
cb = ctx->client_resp_cb;
assert(cb != NULL);
/* Mark we are done with this handler. */
ctx->client_resp_cb = NULL;
cb(ctx, resp);
}
return SPDK_POLLER_BUSY;
}
static int
rpc_client_connect_poller(void *_ctx)
{
struct load_json_config_ctx *ctx = _ctx;
int rc;
rc = spdk_jsonrpc_client_poll(ctx->client_conn, 0);
if (rc != -ENOTCONN) {
/* We are connected. Start regular poller and issue first request */
spdk_poller_unregister(&ctx->client_conn_poller);
ctx->client_conn_poller = SPDK_POLLER_REGISTER(rpc_client_poller, ctx, 100);
app_json_config_load_subsystem(ctx);
} else {
rc = rpc_client_check_timeout(ctx);
if (rc) {
app_json_config_load_done(ctx, rc);
}
return SPDK_POLLER_IDLE;
}
return SPDK_POLLER_BUSY;
}
static int
client_send_request(struct load_json_config_ctx *ctx, struct spdk_jsonrpc_client_request *request,
client_resp_handler client_resp_cb)
{
int rc;
assert(spdk_get_thread() == ctx->thread);
ctx->client_resp_cb = client_resp_cb;
rpc_client_set_timeout(ctx, RPC_CLIENT_REQUEST_TIMEOUT_US);
rc = spdk_jsonrpc_client_send_request(ctx->client_conn, request);
if (rc) {
SPDK_DEBUG_APP_CFG("Sending request to client failed (%d)\n", rc);
}
return rc;
}
static int
cap_string(const struct spdk_json_val *val, void *out)
{
const struct spdk_json_val **vptr = out;
if (val->type != SPDK_JSON_VAL_STRING) {
return -EINVAL;
}
*vptr = val;
return 0;
}
static int
cap_object(const struct spdk_json_val *val, void *out)
{
const struct spdk_json_val **vptr = out;
if (val->type != SPDK_JSON_VAL_OBJECT_BEGIN) {
return -EINVAL;
}
*vptr = val;
return 0;
}
static int
cap_array_or_null(const struct spdk_json_val *val, void *out)
{
const struct spdk_json_val **vptr = out;
if (val->type != SPDK_JSON_VAL_ARRAY_BEGIN && val->type != SPDK_JSON_VAL_NULL) {
return -EINVAL;
}
*vptr = val;
return 0;
}
struct config_entry {
char *method;
struct spdk_json_val *params;
};
static struct spdk_json_object_decoder jsonrpc_cmd_decoders[] = {
{"method", offsetof(struct config_entry, method), spdk_json_decode_string},
{"params", offsetof(struct config_entry, params), cap_object, true}
};
static void app_json_config_load_subsystem_config_entry(void *_ctx);
static void
app_json_config_load_subsystem_config_entry_next(struct load_json_config_ctx *ctx,
struct spdk_jsonrpc_client_response *resp)
{
/* Don't care about the response */
spdk_jsonrpc_client_free_response(resp);
ctx->config_it = spdk_json_next(ctx->config_it);
app_json_config_load_subsystem_config_entry(ctx);
}
/* Load "config" entry */
static void
app_json_config_load_subsystem_config_entry(void *_ctx)
{
struct load_json_config_ctx *ctx = _ctx;
struct spdk_jsonrpc_client_request *rpc_request;
struct spdk_json_write_ctx *w;
struct config_entry cfg = {};
struct spdk_json_val *params_end;
size_t params_len = 0;
int rc;
if (ctx->config_it == NULL) {
SPDK_DEBUG_APP_CFG("Subsystem '%.*s': configuration done.\n", ctx->subsystem_name->len,
(char *)ctx->subsystem_name->start);
ctx->subsystems_it = spdk_json_next(ctx->subsystems_it);
/* Invoke later to avoid recurrency */
spdk_thread_send_msg(ctx->thread, app_json_config_load_subsystem, ctx);
return;
}
if (spdk_json_decode_object(ctx->config_it, jsonrpc_cmd_decoders,
SPDK_COUNTOF(jsonrpc_cmd_decoders), &cfg)) {
SPDK_ERRLOG("Failed to decode config entry\n");
app_json_config_load_done(ctx, -EINVAL);
goto out;
}
rc = spdk_rpc_is_method_allowed(cfg.method, spdk_rpc_get_state());
if (rc == -EPERM) {
SPDK_DEBUG_APP_CFG("Method '%s' not allowed -> skipping\n", cfg.method);
/* Invoke later to avoid recurrency */
ctx->config_it = spdk_json_next(ctx->config_it);
spdk_thread_send_msg(ctx->thread, app_json_config_load_subsystem_config_entry, ctx);
goto out;
}
SPDK_DEBUG_APP_CFG("\tmethod: %s\n", cfg.method);
if (cfg.params) {
/* Get _END by skipping params and going back by one element. */
params_end = cfg.params + spdk_json_val_len(cfg.params) - 1;
/* Need to add one character to include '}' */
params_len = params_end->start - cfg.params->start + 1;
SPDK_DEBUG_APP_CFG("\tparams: %.*s\n", (int)params_len, (char *)cfg.params->start);
}
rpc_request = spdk_jsonrpc_client_create_request();
if (!rpc_request) {
app_json_config_load_done(ctx, -errno);
goto out;
}
w = spdk_jsonrpc_begin_request(rpc_request, ctx->rpc_request_id, NULL);
if (!w) {
spdk_jsonrpc_client_free_request(rpc_request);
app_json_config_load_done(ctx, -ENOMEM);
goto out;
}
spdk_json_write_named_string(w, "method", cfg.method);
if (cfg.params) {
/* No need to parse "params". Just dump the whole content of "params"
* directly into the request and let the remote side verify it. */
spdk_json_write_name(w, "params");
spdk_json_write_val_raw(w, cfg.params->start, params_len);
}
spdk_jsonrpc_end_request(rpc_request, w);
rc = client_send_request(ctx, rpc_request, app_json_config_load_subsystem_config_entry_next);
if (rc != 0) {
app_json_config_load_done(ctx, -rc);
goto out;
}
out:
free(cfg.method);
}
static void
subsystem_init_done(int rc, void *arg1)
{
struct load_json_config_ctx *ctx = arg1;
if (rc) {
app_json_config_load_done(ctx, rc);
return;
}
spdk_rpc_set_state(SPDK_RPC_RUNTIME);
/* Another round. This time for RUNTIME methods */
SPDK_DEBUG_APP_CFG("'framework_start_init' done - continuing configuration\n");
assert(ctx != NULL);
if (ctx->subsystems) {
ctx->subsystems_it = spdk_json_array_first(ctx->subsystems);
}
app_json_config_load_subsystem(ctx);
}
static struct spdk_json_object_decoder subsystem_decoders[] = {
{"subsystem", offsetof(struct load_json_config_ctx, subsystem_name), cap_string},
{"config", offsetof(struct load_json_config_ctx, config), cap_array_or_null}
};
/*
* Start loading subsystem pointed by ctx->subsystems_it. This must point to the
* beginning of the "subsystem" object in "subsystems" array or be NULL. If it is
* NULL then no more subsystems to load.
*
* There are two iterations:
*
* In first iteration only STARTUP RPC methods are used, other methods are ignored. When
* allsubsystems are walked the ctx->subsystems_it became NULL and "framework_start_init"
* is called to let the SPDK move to RUNTIME state (initialize all subsystems) and
* second iteration begins.
*
* In second iteration "subsystems" array is walked through again, this time only
* RUNTIME RPC methods are used. When ctx->subsystems_it became NULL second time it
* indicate that there is no more subsystems to load. The cb_fn is called to finish
* configuration.
*/
static void
app_json_config_load_subsystem(void *_ctx)
{
struct load_json_config_ctx *ctx = _ctx;
if (ctx->subsystems_it == NULL) {
if (spdk_rpc_get_state() == SPDK_RPC_STARTUP) {
SPDK_DEBUG_APP_CFG("No more entries for current state, calling 'framework_start_init'\n");
spdk_subsystem_init(subsystem_init_done, ctx);
} else {
app_json_config_load_done(ctx, 0);
}
return;
}
/* Capture subsystem name and config array */
if (spdk_json_decode_object(ctx->subsystems_it, subsystem_decoders,
SPDK_COUNTOF(subsystem_decoders), ctx)) {
SPDK_ERRLOG("Failed to parse subsystem configuration\n");
app_json_config_load_done(ctx, -EINVAL);
return;
}
SPDK_DEBUG_APP_CFG("Loading subsystem '%.*s' configuration\n", ctx->subsystem_name->len,
(char *)ctx->subsystem_name->start);
/* Get 'config' array first configuration entry */
ctx->config_it = spdk_json_array_first(ctx->config);
app_json_config_load_subsystem_config_entry(ctx);
}
static void *
read_file(const char *filename, size_t *size)
{
FILE *file = fopen(filename, "r");
void *data;
if (file == NULL) {
/* errno is set by fopen */
return NULL;
}
data = spdk_posix_file_load(file, size);
fclose(file);
return data;
}
static int
app_json_config_read(const char *config_file, struct load_json_config_ctx *ctx)
{
struct spdk_json_val *values = NULL;
void *json = NULL, *end;
ssize_t values_cnt, rc;
size_t json_size;
json = read_file(config_file, &json_size);
if (!json) {
SPDK_ERRLOG("Read JSON configuration file %s failed: %s\n",
config_file, spdk_strerror(errno));
return -errno;
}
rc = spdk_json_parse(json, json_size, NULL, 0, &end,
SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
if (rc < 0) {
SPDK_ERRLOG("Parsing JSON configuration failed (%zd)\n", rc);
goto err;
}
values_cnt = rc;
values = calloc(values_cnt, sizeof(struct spdk_json_val));
if (values == NULL) {
SPDK_ERRLOG("Out of memory\n");
goto err;
}
rc = spdk_json_parse(json, json_size, values, values_cnt, &end,
SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
if (rc != values_cnt) {
SPDK_ERRLOG("Parsing JSON configuration failed (%zd)\n", rc);
goto err;
}
ctx->json_data = json;
ctx->json_data_size = json_size;
ctx->values = values;
ctx->values_cnt = values_cnt;
return 0;
err:
free(json);
free(values);
return rc;
}
void
spdk_subsystem_init_from_json_config(const char *json_config_file, const char *rpc_addr,
spdk_subsystem_init_fn cb_fn, void *cb_arg,
bool stop_on_error)
{
struct load_json_config_ctx *ctx = calloc(1, sizeof(*ctx));
int rc;
assert(cb_fn);
if (!ctx) {
cb_fn(-ENOMEM, cb_arg);
return;
}
ctx->cb_fn = cb_fn;
ctx->cb_arg = cb_arg;
ctx->stop_on_error = stop_on_error;
ctx->thread = spdk_get_thread();
rc = app_json_config_read(json_config_file, ctx);
if (rc) {
goto fail;
}
/* Capture subsystems array */
rc = spdk_json_find_array(ctx->values, "subsystems", NULL, &ctx->subsystems);
if (rc) {
SPDK_WARNLOG("No 'subsystems' key JSON configuration file.\n");
} else {
/* Get first subsystem */
ctx->subsystems_it = spdk_json_array_first(ctx->subsystems);
if (ctx->subsystems_it == NULL) {
SPDK_NOTICELOG("'subsystems' configuration is empty\n");
}
}
/* If rpc_addr is not an Unix socket use default address as prefix. */
if (rpc_addr == NULL || rpc_addr[0] != '/') {
rpc_addr = SPDK_DEFAULT_RPC_ADDR;
}
/* FIXME: rpc client should use socketpair() instead of this temporary socket nonsense */
rc = snprintf(ctx->rpc_socket_path_temp, sizeof(ctx->rpc_socket_path_temp), "%s.%d_config",
rpc_addr, getpid());
if (rc >= (int)sizeof(ctx->rpc_socket_path_temp)) {
SPDK_ERRLOG("Socket name create failed\n");
goto fail;
}
rc = spdk_rpc_initialize(ctx->rpc_socket_path_temp);
if (rc) {
goto fail;
}
ctx->client_conn = spdk_jsonrpc_client_connect(ctx->rpc_socket_path_temp, AF_UNIX);
if (ctx->client_conn == NULL) {
SPDK_ERRLOG("Failed to connect to '%s'\n", ctx->rpc_socket_path_temp);
goto fail;
}
rpc_client_set_timeout(ctx, RPC_CLIENT_CONNECT_TIMEOUT_US);
ctx->client_conn_poller = SPDK_POLLER_REGISTER(rpc_client_connect_poller, ctx, 100);
return;
fail:
app_json_config_load_done(ctx, -EINVAL);
}
SPDK_LOG_REGISTER_COMPONENT(app_config)