Merge pull request #2331 from splinterofchaos/serverlisten

vimL: serverlisten({addr}), list(), and stop({addr})
This commit is contained in:
Scott Prager
2015-04-14 15:45:39 -04:00
8 changed files with 243 additions and 40 deletions

View File

@@ -87,6 +87,7 @@
#include "nvim/os/rstream_defs.h"
#include "nvim/os/time.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/os/dl.h"
@@ -6605,6 +6606,9 @@ static struct fst {
{"searchpair", 3, 7, f_searchpair},
{"searchpairpos", 3, 7, f_searchpairpos},
{"searchpos", 1, 4, f_searchpos},
{"serverlist", 0, 0, f_serverlist},
{"serverstart", 0, 1, f_serverstart},
{"serverstop", 1, 1, f_serverstop},
{"setbufvar", 3, 3, f_setbufvar},
{"setcmdpos", 1, 1, f_setcmdpos},
{"setline", 2, 2, f_setline},
@@ -13292,6 +13296,69 @@ static void f_searchpos(typval_T *argvars, typval_T *rettv)
list_append_number(rettv->vval.v_list, (varnumber_T)n);
}
/// "serverlist()" function
static void f_serverlist(typval_T *argvars, typval_T *rettv)
{
size_t n;
char **addrs = server_address_list(&n);
// Copy addrs into a linked list.
list_T *l = rettv_list_alloc(rettv);
for (size_t i = 0; i < n; i++) {
listitem_T *li = listitem_alloc();
li->li_tv.v_type = VAR_STRING;
li->li_tv.v_lock = 0;
li->li_tv.vval.v_string = (char_u *) addrs[i];
list_append(l, li);
}
xfree(addrs);
}
/// "serverstart()" function
static void f_serverstart(typval_T *argvars, typval_T *rettv)
{
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL; // Will hold the address of the new server.
if (check_restricted() || check_secure()) {
return;
}
// If the user supplied an address, use it, otherwise use a temp.
if (argvars[0].v_type != VAR_UNKNOWN) {
if (argvars[0].v_type != VAR_STRING) {
EMSG(_(e_invarg));
return;
} else {
rettv->vval.v_string = vim_strsave(get_tv_string(argvars));
}
} else {
rettv->vval.v_string = vim_tempname();
}
int result = server_start((char *) rettv->vval.v_string);
if (result != 0) {
EMSG2("Failed to start server: %s", uv_strerror(result));
}
}
/// "serverstop()" function
static void f_serverstop(typval_T *argvars, typval_T *rettv)
{
if (check_restricted() || check_secure()) {
return;
}
if (argvars[0].v_type == VAR_UNKNOWN || argvars[0].v_type != VAR_STRING) {
EMSG(_(e_invarg));
return;
}
if (argvars[0].vval.v_string) {
server_stop((char *) argvars[0].vval.v_string);
}
}
/*
* "setbufvar()" function
*/

View File

@@ -9,11 +9,11 @@
#include "nvim/msgpack_rpc/server.h"
#include "nvim/os/os.h"
#include "nvim/ascii.h"
#include "nvim/garray.h"
#include "nvim/vim.h"
#include "nvim/memory.h"
#include "nvim/log.h"
#include "nvim/tempfile.h"
#include "nvim/map.h"
#include "nvim/path.h"
#define MAX_CONNECTIONS 32
@@ -27,6 +27,9 @@ typedef enum {
} ServerType;
typedef struct {
// The address of a pipe, or string value of a tcp address.
char addr[ADDRESS_MAX_SIZE];
// Type of the union below
ServerType type;
@@ -38,12 +41,11 @@ typedef struct {
} tcp;
struct {
uv_pipe_t handle;
char addr[ADDRESS_MAX_SIZE];
} pipe;
} socket;
} Server;
static PMap(cstr_t) *servers = NULL;
static garray_T servers = GA_EMPTY_INIT_VALUE;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/server.c.generated.h"
@@ -52,33 +54,40 @@ static PMap(cstr_t) *servers = NULL;
/// Initializes the module
bool server_init(void)
{
servers = pmap_new(cstr_t)();
ga_init(&servers, sizeof(Server *), 1);
if (!os_getenv(LISTEN_ADDRESS_ENV_VAR)) {
char *listen_address = (char *)vim_tempname();
os_setenv(LISTEN_ADDRESS_ENV_VAR, listen_address, 1);
xfree(listen_address);
bool must_free = false;
const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR);
if (listen_address == NULL || *listen_address == NUL) {
must_free = true;
listen_address = (char *)vim_tempname();
}
return server_start((char *)os_getenv(LISTEN_ADDRESS_ENV_VAR)) == 0;
bool ok = (server_start(listen_address) == 0);
if (must_free) {
xfree((char *) listen_address);
}
return ok;
}
/// Retrieve the file handle from a server.
static uv_handle_t *server_handle(Server *server)
{
return server->type == kServerTypeTcp
? (uv_handle_t *)&server->socket.tcp.handle
: (uv_handle_t *) &server->socket.pipe.handle;
}
/// Teardown a single server
static void server_close_cb(Server **server)
{
uv_close(server_handle(*server), free_server);
}
/// 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);
}
});
GA_DEEP_CLEAR(&servers, Server *, server_close_cb);
}
/// Starts listening on arbitrary tcp/unix addresses specified by
@@ -106,9 +115,11 @@ int server_start(const char *endpoint)
}
// Check if the server already exists
if (pmap_has(cstr_t)(servers, addr)) {
ELOG("Already listening on %s", addr);
return 1;
for (int i = 0; i < servers.ga_len; i++) {
if (strcmp(addr, ((Server **)servers.ga_data)[i]->addr) == 0) {
ELOG("Already listening on %s", addr);
return 1;
}
}
ServerType server_type = kServerTypeTcp;
@@ -154,6 +165,8 @@ int server_start(const char *endpoint)
int result;
uv_stream_t *stream = NULL;
xstrlcpy(server->addr, addr, sizeof(server->addr));
if (server_type == kServerTypeTcp) {
// Listen on tcp address/port
uv_tcp_init(uv_default_loop(), &server->socket.tcp.handle);
@@ -163,10 +176,8 @@ int server_start(const char *endpoint)
stream = (uv_stream_t *)&server->socket.tcp.handle;
} 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);
result = uv_pipe_bind(&server->socket.pipe.handle,
server->socket.pipe.addr);
result = uv_pipe_bind(&server->socket.pipe.handle, server->addr);
stream = (uv_stream_t *)&server->socket.pipe.handle;
}
@@ -193,9 +204,17 @@ int server_start(const char *endpoint)
return result;
}
// Update $NVIM_LISTEN_ADDRESS, if not set.
const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR);
if (listen_address == NULL || *listen_address == NUL) {
os_setenv(LISTEN_ADDRESS_ENV_VAR, addr, 1);
}
server->type = server_type;
// Add the server to the hash table
pmap_put(cstr_t)(servers, addr, server);
// Add the server to the list.
ga_grow(&servers, 1);
((Server **)servers.ga_data)[servers.ga_len++] = server;
return 0;
}
@@ -211,18 +230,49 @@ void server_stop(char *endpoint)
// Trim to `ADDRESS_MAX_SIZE`
xstrlcpy(addr, endpoint, sizeof(addr));
if ((server = pmap_get(cstr_t)(servers, addr)) == NULL) {
int i = 0; // The index of the server whose address equals addr.
for (; i < servers.ga_len; i++) {
server = ((Server **)servers.ga_data)[i];
if (strcmp(addr, server->addr) == 0) {
break;
}
}
if (i == servers.ga_len) {
ELOG("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);
// If we are invalidating the listen address, unset it.
const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR);
if (listen_address && strcmp(addr, listen_address) == 0) {
os_unsetenv(LISTEN_ADDRESS_ENV_VAR);
}
pmap_del(cstr_t)(servers, addr);
uv_close(server_handle(server), free_server);
// Remove this server from the list by swapping it with the last item.
if (i != servers.ga_len - 1) {
((Server **)servers.ga_data)[i] =
((Server **)servers.ga_data)[servers.ga_len - 1];
}
servers.ga_len--;
}
/// Returns an allocated array of server addresses.
/// @param[out] size The size of the returned array.
char **server_address_list(size_t *size)
FUNC_ATTR_NONNULL_ALL
{
if ((*size = (size_t) servers.ga_len) == 0) {
return NULL;
}
char **addrs = xcalloc((size_t) servers.ga_len, sizeof(const char **));
for (int i = 0; i < servers.ga_len; i++) {
addrs[i] = xstrdup(((Server **)servers.ga_data)[i]->addr);
}
return addrs;
}
static void connection_cb(uv_stream_t *server, int status)

View File

@@ -37,6 +37,19 @@ int os_setenv(const char *name, const char *value, int overwrite)
return setenv(name, value, overwrite);
}
/// Unset environment variable
///
/// For systems where unsetenv() is not available the value will be set as an
/// empty string
int os_unsetenv(const char *name)
{
#ifdef HAVE_UNSETENV
return unsetenv(name);
#else
return os_setenv(name, "", 1);
#endif
}
char *os_getenvname_at_index(size_t index)
{
# if defined(HAVE__NSGETENVIRON)