diff options
author | Thiago de Arruda <tpadilha84@gmail.com> | 2014-05-07 18:00:15 -0300 |
---|---|---|
committer | Thiago de Arruda <tpadilha84@gmail.com> | 2014-05-12 21:26:49 -0300 |
commit | 12fba26110175137a9e79db8053075dc290626e5 (patch) | |
tree | d213ac60f2093c1babfb60e240ac85e44db171ff | |
parent | f9c06e47c4c672fb5f07968dd2d536fa41c1f007 (diff) | |
download | rneovim-12fba26110175137a9e79db8053075dc290626e5.tar.gz rneovim-12fba26110175137a9e79db8053075dc290626e5.tar.bz2 rneovim-12fba26110175137a9e79db8053075dc290626e5.zip |
Add server module for accepting API connections
The `NEOVIM_LISTEN_ADDRESS` environment variable can be set to customize the
address where Neovim will listen for connections. If it's not set, a random
socket/pipe will be created, and the `NEOVIM_LISTEN_ADDRESS` will be updated
accordingly.
-rw-r--r-- | src/os/event.c | 4 | ||||
-rw-r--r-- | src/os/server.c | 243 | ||||
-rw-r--r-- | src/os/server.h | 30 | ||||
-rw-r--r-- | src/os/server_defs.h | 7 |
4 files changed, 284 insertions, 0 deletions
diff --git a/src/os/event.c b/src/os/event.c index a4b9d1ac5a..2c25852778 100644 --- a/src/os/event.c +++ b/src/os/event.c @@ -8,6 +8,7 @@ #include "os/event.h" #include "os/input.h" #include "os/channel.h" +#include "os/server.h" #include "os/signal.h" #include "os/rstream.h" #include "os/job.h" @@ -39,6 +40,8 @@ void event_init() job_init(); // Channels channel_init(); + // Servers + server_init(); uv_timer_init(uv_default_loop(), &timer); // This prepare handle that actually starts the timer uv_prepare_init(uv_default_loop(), &timer_prepare); @@ -48,6 +51,7 @@ void event_teardown() { channel_teardown(); job_teardown(); + server_teardown(); } // Wait for some event diff --git a/src/os/server.c b/src/os/server.c new file mode 100644 index 0000000000..bda9602936 --- /dev/null +++ b/src/os/server.c @@ -0,0 +1,243 @@ +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> + +#include <uv.h> + +#include "os/channel_defs.h" +#include "os/channel.h" +#include "os/server.h" +#include "os/os.h" +#include "vim.h" +#include "memory.h" +#include "message.h" +#include "fileio.h" +#include "map.h" + +#define MAX_CONNECTIONS 32 +#define ADDRESS_MAX_SIZE 256 +#define NEOVIM_DEFAULT_TCP_PORT 7450 + +typedef enum { + kServerTypeTcp, + kServerTypePipe +} ServerType; + +typedef struct { + // Protocol for channels established through this server + ChannelProtocol protocol; + // Type of the union below + ServerType type; + + // This is either a tcp server or unix socket(named pipe on windows) + union { + struct { + uv_tcp_t handle; + struct sockaddr_in addr; + } tcp; + struct { + uv_pipe_t handle; + char addr[ADDRESS_MAX_SIZE]; + } pipe; + } socket; +} Server; + +static Map *servers = NULL; + +static void close_server(Map *map, const char *endpoint, void *server); +static void connection_cb(uv_stream_t *server, int status); +static void free_client(uv_handle_t *handle); +static void free_server(uv_handle_t *handle); + +void server_init() +{ + servers = map_new(); + + if (!os_getenv("NEOVIM_LISTEN_ADDRESS")) { + char *listen_address = (char *)vim_tempname('s'); + os_setenv("NEOVIM_LISTEN_ADDRESS", listen_address, 1); + free(listen_address); + } + + server_start((char *)os_getenv("NEOVIM_LISTEN_ADDRESS"), + kChannelProtocolMsgpack); +} + +void server_teardown() +{ + if (!servers) { + return; + } + + map_foreach(servers, close_server); +} + +void server_start(char *endpoint, ChannelProtocol prot) +{ + char addr[ADDRESS_MAX_SIZE]; + + // Trim to `ADDRESS_MAX_SIZE` + strncpy(addr, endpoint, sizeof(addr)); + + // Check if the server already exists + if (map_has(servers, addr)) { + EMSG2("Already listening on %s", addr); + return; + } + + ServerType server_type = kServerTypeTcp; + Server *server = xmalloc(sizeof(Server)); + char ip[16], *ip_end = strrchr(addr, ':'); + + server->protocol = prot; + + if (!ip_end) { + ip_end = strchr(addr, NUL); + } + + uint32_t addr_len = ip_end - addr; + + if (addr_len > sizeof(ip) - 1) { + // Maximum length of a ip address buffer is 15(eg: 255.255.255.255) + addr_len = sizeof(ip); + } + + // Extract the address part + strncpy(ip, addr, addr_len); + + int port = NEOVIM_DEFAULT_TCP_PORT; + + if (*ip_end == ':') { + char *port_end; + // Extract the port + port = strtol(ip_end + 1, &port_end, 10); + + errno = 0; + if (errno != 0 || port == 0 || port > 0xffff) { + // Invalid port, treat as named pipe or unix socket + server_type = kServerTypePipe; + } + } + + if (server_type == kServerTypeTcp) { + // Try to parse ip address + if (uv_ip4_addr(ip, port, &server->socket.tcp.addr)) { + // Invalid address, treat as named pipe or unix socket + server_type = kServerTypePipe; + } + } + + int result; + + if (server_type == kServerTypeTcp) { + // Listen on tcp address/port + uv_tcp_init(uv_default_loop(), &server->socket.tcp.handle); + server->socket.tcp.handle.data = server; + uv_tcp_bind(&server->socket.tcp.handle, + (const struct sockaddr *)&server->socket.tcp.addr, + 0); + result = uv_listen((uv_stream_t *)&server->socket.tcp.handle, + MAX_CONNECTIONS, + connection_cb); + if (result) { + uv_close((uv_handle_t *)&server->socket.tcp.handle, free_server); + } + } else { + // Listen on named pipe or unix socket + strcpy(server->socket.pipe.addr, addr); + uv_pipe_init(uv_default_loop(), &server->socket.pipe.handle, 0); + server->socket.pipe.handle.data = server; + uv_pipe_bind(&server->socket.pipe.handle, server->socket.pipe.addr); + result = uv_listen((uv_stream_t *)&server->socket.pipe.handle, + MAX_CONNECTIONS, + connection_cb); + + if (result) { + uv_close((uv_handle_t *)&server->socket.pipe.handle, free_server); + } + } + + if (result) { + EMSG2("Failed to start server: %s", uv_strerror(result)); + return; + } + + + server->type = server_type; + + // Add the server to the hash table + map_put(servers, addr, server); +} + +void server_stop(char *endpoint) +{ + Server *server; + char addr[ADDRESS_MAX_SIZE]; + + // Trim to `ADDRESS_MAX_SIZE` + strncpy(addr, endpoint, sizeof(addr)); + + if ((server = map_get(servers, addr)) == NULL) { + EMSG2("Not listening on %s", addr); + return; + } + + if (server->type == kServerTypeTcp) { + uv_close((uv_handle_t *)&server->socket.tcp.handle, free_server); + } else { + uv_close((uv_handle_t *)&server->socket.pipe.handle, free_server); + } + + map_del(servers, addr); +} + +static void connection_cb(uv_stream_t *server, int status) +{ + int result; + uv_stream_t *client; + Server *srv = server->data; + + if (status < 0) { + abort(); + } + + if (srv->type == kServerTypeTcp) { + client = xmalloc(sizeof(uv_tcp_t)); + uv_tcp_init(uv_default_loop(), (uv_tcp_t *)client); + } else { + client = xmalloc(sizeof(uv_pipe_t)); + uv_pipe_init(uv_default_loop(), (uv_pipe_t *)client, 0); + } + + result = uv_accept(server, client); + + if (result) { + EMSG2("Failed to accept connection: %s", uv_strerror(result)); + uv_close((uv_handle_t *)client, free_client); + return; + } + + channel_from_stream(client, srv->protocol); +} + +static void close_server(Map *map, const char *endpoint, void *srv) +{ + Server *server = srv; + + if (server->type == kServerTypeTcp) { + uv_close((uv_handle_t *)&server->socket.tcp.handle, free_server); + } else { + uv_close((uv_handle_t *)&server->socket.pipe.handle, free_server); + } +} + +static void free_client(uv_handle_t *handle) +{ + free(handle); +} + +static void free_server(uv_handle_t *handle) +{ + free(handle->data); +} diff --git a/src/os/server.h b/src/os/server.h new file mode 100644 index 0000000000..b9459a81af --- /dev/null +++ b/src/os/server.h @@ -0,0 +1,30 @@ +#ifndef NEOVIM_OS_SERVER_H +#define NEOVIM_OS_SERVER_H + +#include "os/channel_defs.h" + +/// Initializes the module +void server_init(); + +/// Teardown the server module +void server_teardown(); + +/// Starts listening on arbitrary tcp/unix addresses specified by +/// `endpoint` for API calls. The type of socket used(tcp or unix/pipe) will +/// be determined by parsing `endpoint`: If it's a valid tcp address in the +/// 'ip:port' format, then it will be tcp socket, else it will be a unix +/// socket or named pipe. +/// +/// @param endpoint Address of the server. Either a 'ip:port' string or an +/// arbitrary identifier(trimmed to 256 bytes) for the unix socket or +/// named pipe. +/// @param prot The rpc protocol to be used +void server_start(char *endpoint, ChannelProtocol prot); + +/// Stops listening on the address specified by `endpoint`. +/// +/// @param endpoint Address of the server. +void server_stop(char *endpoint); + +#endif // NEOVIM_OS_SERVER_H + diff --git a/src/os/server_defs.h b/src/os/server_defs.h new file mode 100644 index 0000000000..cbaefe3949 --- /dev/null +++ b/src/os/server_defs.h @@ -0,0 +1,7 @@ +#ifndef NEOVIM_OS_SERVER_DEFS_H +#define NEOVIM_OS_SERVER_DEFS_H + +typedef struct server Server; + +#endif // NEOVIM_OS_SERVER_DEFS_H + |