aboutsummaryrefslogblamecommitdiffstatshomepage
path: root/src/command.c
blob: 9946c131f2db255e6f4219e7b40e20acdd1f087c (plain) (tree)
1
2
3
4
5
6
7
8
9
10







                                                          

                       
                     
                
 
                 


                   


                              


                  
                                                                      





                                       
                                     


                 
                                           
 
                         

 
                                                                                          
 
                                

                    

                                                                           






                                  
                                                 
                                              



                  
                                                               
 

                                               

 

                                                                                     
 

                    
                                                                                  
                          


                                    

                              
 

                                                                        


                                    
                                            
 
                                                            
                    
                               
 
                                  

                 

                               






                         
                                                              
 

                                                            


                         
                                                                                  

                                                                                      
 
                                                                     
 

                                                             
 
                                                  

                                 
                                                          

         
                                                                 
                  

 

                                                                                                  



                                                                                            













                                                                       
                                                                                              
 

                    

                                                                               
                          
 

                                                      
                    

                              







                                                                                
 


                                                                                            
                            














                                                                                      

         
                     







                                                           
 

                                         



                      

                   
 




                                                                                                  

                                                                                      
 




                                                               
                                                                                             
 
/*
 * Copyright (c) 2023 Egor Tensin <Egor.Tensin@gmail.com>
 * 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 <poll.h>
#include <stdlib.h>
#include <string.h>

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);
}