/* * Copyright (c) 2023 Egor Tensin * This file is part of the "cimple" project. * For details, see https://github.com/egor-tensin/cimple. * Distributed under the MIT License. */ #include "command.h" #include "compiler.h" #include "event_loop.h" #include "json_rpc.h" #include "log.h" #include #include #include struct cmd_dispatcher { struct cmd_desc *cmds; size_t numof_cmds; void *ctx; }; static int copy_cmd(struct cmd_desc *dest, const struct cmd_desc *src) { dest->name = strdup(src->name); if (!dest->name) { log_errno("strdup"); return -1; } dest->handler = src->handler; return 0; } static void free_cmd(struct cmd_desc *desc) { free(desc->name); } static int copy_cmds(struct cmd_desc *dest, const struct cmd_desc *src, size_t numof_cmds) { size_t numof_copied = 0; int ret = 0; for (numof_copied = 0; numof_copied < numof_cmds; ++numof_copied) { ret = copy_cmd(&dest[numof_copied], &src[numof_copied]); if (ret < 0) goto free; } return 0; free: for (size_t i = 0; i < numof_copied; ++i) free_cmd(&dest[numof_copied]); return -1; } static void free_cmds(struct cmd_desc *cmds, size_t numof_cmds) { for (size_t i = 0; i < numof_cmds; ++i) free_cmd(&cmds[i]); } int cmd_dispatcher_create(struct cmd_dispatcher **_dispatcher, struct cmd_desc *cmds, size_t numof_cmds, void *ctx) { int ret = 0; struct cmd_dispatcher *dispatcher = malloc(sizeof(struct cmd_dispatcher)); if (!dispatcher) { log_errno("malloc"); return -1; } dispatcher->ctx = ctx; dispatcher->cmds = malloc(sizeof(struct cmd_desc) * numof_cmds); if (!dispatcher->cmds) { log_errno("malloc"); goto free; } dispatcher->numof_cmds = numof_cmds; ret = copy_cmds(dispatcher->cmds, cmds, numof_cmds); if (ret < 0) goto free_cmds; *_dispatcher = dispatcher; return 0; free_cmds: free(dispatcher->cmds); free: free(dispatcher); return -1; } void cmd_dispatcher_destroy(struct cmd_dispatcher *dispatcher) { free_cmds(dispatcher->cmds, dispatcher->numof_cmds); free(dispatcher->cmds); free(dispatcher); } static int cmd_dispatcher_handle_internal(const struct cmd_dispatcher *dispatcher, const struct jsonrpc_request *request, struct jsonrpc_response **result, void *arg) { const char *actual_cmd = jsonrpc_request_get_method(request); for (size_t i = 0; i < dispatcher->numof_cmds; ++i) { struct cmd_desc *cmd = &dispatcher->cmds[i]; if (strcmp(cmd->name, actual_cmd)) continue; return cmd->handler(request, result, arg); } log_err("Received an unknown command: %s\n", actual_cmd); return -1; } int cmd_dispatcher_handle(const struct cmd_dispatcher *dispatcher, const struct jsonrpc_request *command, struct jsonrpc_response **result) { return cmd_dispatcher_handle_internal(dispatcher, command, result, dispatcher->ctx); } static struct cmd_conn_ctx *make_conn_ctx(int fd, void *arg) { struct cmd_conn_ctx *ctx = malloc(sizeof(struct cmd_conn_ctx)); if (!ctx) { log_errno("malloc"); return NULL; } ctx->fd = fd; ctx->arg = arg; return ctx; } static int cmd_dispatcher_handle_conn_internal(int conn_fd, struct cmd_dispatcher *dispatcher) { int ret = 0; struct cmd_conn_ctx *new_ctx = make_conn_ctx(conn_fd, dispatcher->ctx); if (!new_ctx) return -1; struct jsonrpc_request *request = NULL; ret = jsonrpc_request_recv(&request, conn_fd); if (ret < 0) goto free_ctx; const int requires_response = !jsonrpc_request_is_notification(request); struct jsonrpc_response *default_response = NULL; if (requires_response) { ret = jsonrpc_response_create(&default_response, request, NULL); if (ret < 0) goto free_request; } struct jsonrpc_response *default_error = NULL; if (requires_response) { ret = jsonrpc_error_create(&default_error, request, -1, "An error occured"); if (ret < 0) goto free_default_response; } struct jsonrpc_response *response = NULL; ret = cmd_dispatcher_handle_internal(dispatcher, request, &response, new_ctx); if (requires_response) { struct jsonrpc_response *actual_response = response; if (!actual_response) { actual_response = ret < 0 ? default_error : default_response; } if (ret < 0 && !jsonrpc_response_is_error(actual_response)) { actual_response = default_error; } ret = jsonrpc_response_send(actual_response, conn_fd) < 0 ? -1 : ret; } if (response) jsonrpc_response_destroy(response); if (default_error) jsonrpc_response_destroy(default_error); free_default_response: if (default_response) jsonrpc_response_destroy(default_response); free_request: jsonrpc_request_destroy(request); free_ctx: free(new_ctx); return ret; } int cmd_dispatcher_handle_conn(int conn_fd, void *_dispatcher) { return cmd_dispatcher_handle_conn_internal(conn_fd, (struct cmd_dispatcher *)_dispatcher); } int cmd_dispatcher_handle_event(UNUSED struct event_loop *loop, int fd, short revents, void *_dispatcher) { if (!(revents & POLLIN)) { log_err("Descriptor %d is not readable\n", fd); return -1; } return cmd_dispatcher_handle_conn_internal(fd, (struct cmd_dispatcher *)_dispatcher); }