diff options
author | Thiago de Arruda <tpadilha84@gmail.com> | 2014-10-20 07:35:10 -0300 |
---|---|---|
committer | Thiago de Arruda <tpadilha84@gmail.com> | 2014-10-21 11:05:49 -0300 |
commit | b280308ac649da61e2a0f40a222eae21af5352c9 (patch) | |
tree | 1ef8433c2aa55e1a5013c05836dc0cbf931a101c /src/nvim/msgpack_rpc/server.c | |
parent | 6e268cd0d40a3652a68b486bdbb421d39295ab48 (diff) | |
download | rneovim-b280308ac649da61e2a0f40a222eae21af5352c9.tar.gz rneovim-b280308ac649da61e2a0f40a222eae21af5352c9.tar.bz2 rneovim-b280308ac649da61e2a0f40a222eae21af5352c9.zip |
msgpack-rpc: Create subdirectory for msgpack-rpc modules
Create the msgpack_rpc subdirectory and move all modules that deal with
msgpack-rpc to it. Also merge msgpack_rpc.c into msgpack_rpc/helpers.c
Diffstat (limited to 'src/nvim/msgpack_rpc/server.c')
-rw-r--r-- | src/nvim/msgpack_rpc/server.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c new file mode 100644 index 0000000000..33e01fe562 --- /dev/null +++ b/src/nvim/msgpack_rpc/server.c @@ -0,0 +1,273 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include <uv.h> + +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/server.h" +#include "nvim/os/os.h" +#include "nvim/ascii.h" +#include "nvim/vim.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/tempfile.h" +#include "nvim/map.h" +#include "nvim/path.h" + +#define MAX_CONNECTIONS 32 +#define ADDRESS_MAX_SIZE 256 +#define NEOVIM_DEFAULT_TCP_PORT 7450 +#define LISTEN_ADDRESS_ENV_VAR "NVIM_LISTEN_ADDRESS" + +typedef enum { + kServerTypeTcp, + kServerTypePipe +} ServerType; + +typedef struct { + // 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 PMap(cstr_t) *servers = NULL; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "msgpack_rpc/server.c.generated.h" +#endif + +/// Initializes the module +bool server_init(void) +{ + servers = pmap_new(cstr_t)(); + + if (!os_getenv(LISTEN_ADDRESS_ENV_VAR)) { + char *listen_address = (char *)vim_tempname(); + os_setenv(LISTEN_ADDRESS_ENV_VAR, listen_address, 1); + free(listen_address); + } + + return server_start((char *)os_getenv(LISTEN_ADDRESS_ENV_VAR)) == 0; +} + +/// Teardown the server module +void server_teardown(void) +{ + if (!servers) { + return; + } + + Server *server; + + map_foreach_value(servers, server, { + 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); + } + }); +} + +/// 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. The port is optional +/// and if omitted will default to NEOVIM_DEFAULT_TCP_PORT. Otherwise 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. +/// @returns zero if successful, one on a regular error, and negative errno +/// on failure to bind or connect. +int server_start(const char *endpoint) + FUNC_ATTR_NONNULL_ALL +{ + char addr[ADDRESS_MAX_SIZE]; + + // Trim to `ADDRESS_MAX_SIZE` + if (xstrlcpy(addr, endpoint, sizeof(addr)) >= sizeof(addr)) { + // TODO(aktau): since this is not what the user wanted, perhaps we + // should return an error here + EMSG2("Address was too long, truncated to %s", addr); + } + + // Check if the server already exists + if (pmap_has(cstr_t)(servers, addr)) { + EMSG2("Already listening on %s", addr); + return 1; + } + + ServerType server_type = kServerTypeTcp; + Server *server = xmalloc(sizeof(Server)); + char ip[16], *ip_end = strrchr(addr, ':'); + + if (!ip_end) { + ip_end = strchr(addr, NUL); + } + + uint32_t addr_len = ip_end - addr; + + if (addr_len > sizeof(ip) - 1) { + // Maximum length of an IP address buffer is 15(eg: 255.255.255.255) + addr_len = sizeof(ip) - 1; + } + + // Extract the address part + xstrlcpy(ip, addr, addr_len + 1); + + int port = NEOVIM_DEFAULT_TCP_PORT; + + if (*ip_end == ':') { + // Extract the port + long lport = strtol(ip_end + 1, NULL, 10); // NOLINT + if (lport <= 0 || lport > 0xffff) { + // Invalid port, treat as named pipe or unix socket + server_type = kServerTypePipe; + } else { + port = (int) lport; + } + } + + 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; + result = uv_tcp_bind(&server->socket.tcp.handle, + (const struct sockaddr *)&server->socket.tcp.addr, + 0); + if (result == 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 + xstrlcpy(server->socket.pipe.addr, addr, sizeof(server->socket.pipe.addr)); + uv_pipe_init(uv_default_loop(), &server->socket.pipe.handle, 0); + server->socket.pipe.handle.data = server; + result = uv_pipe_bind(&server->socket.pipe.handle, + server->socket.pipe.addr); + if (result == 0) { + 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); + } + } + } + + assert(result <= 0); // libuv should have returned -errno or zero. + if (result < 0) { + if (result == -EACCES) { + // Libuv converts ENOENT to EACCES for Windows compatibility, but if + // the parent directory does not exist, ENOENT would be more accurate. + *path_tail((char_u *) addr) = NUL; + if (!os_file_exists((char_u *) addr)) { + result = -ENOENT; + } + } + EMSG2("Failed to start server: %s", uv_strerror(result)); + free(server); + return result; + } + + server->type = server_type; + + // Add the server to the hash table + pmap_put(cstr_t)(servers, addr, server); + + return 0; +} + +/// Stops listening on the address specified by `endpoint`. +/// +/// @param endpoint Address of the server. +void server_stop(char *endpoint) +{ + Server *server; + char addr[ADDRESS_MAX_SIZE]; + + // Trim to `ADDRESS_MAX_SIZE` + xstrlcpy(addr, endpoint, sizeof(addr)); + + if ((server = pmap_get(cstr_t)(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); + } + + pmap_del(cstr_t)(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); +} + +static void free_client(uv_handle_t *handle) +{ + free(handle); +} + +static void free_server(uv_handle_t *handle) +{ + free(handle->data); +} |