diff options
Diffstat (limited to 'src/nvim/os/server.c')
-rw-r--r-- | src/nvim/os/server.c | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/src/nvim/os/server.c b/src/nvim/os/server.c new file mode 100644 index 0000000000..bda9602936 --- /dev/null +++ b/src/nvim/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); +} |