aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/CMakeLists.txt1
-rw-r--r--config/config.h.in1
-rw-r--r--runtime/doc/eval.txt20
-rw-r--r--src/nvim/eval.c67
-rw-r--r--src/nvim/msgpack_rpc/server.c122
-rw-r--r--src/nvim/os/env.c13
-rw-r--r--test/functional/server/server_spec.lua42
-rw-r--r--test/unit/os/env_spec.lua17
8 files changed, 243 insertions, 40 deletions
diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt
index 0d3ef62297..ed1f422070 100644
--- a/config/CMakeLists.txt
+++ b/config/CMakeLists.txt
@@ -58,6 +58,7 @@ check_function_exists(setenv HAVE_SETENV)
if(NOT HAVE_SETENV)
message(SEND_ERROR "setenv() function not found on your system.")
endif()
+check_function_exists(unsetenv HAVE_UNSETENV)
check_function_exists(setpgid HAVE_SETPGID)
check_function_exists(setsid HAVE_SETSID)
check_function_exists(sigaction HAVE_SIGACTION)
diff --git a/config/config.h.in b/config/config.h.in
index 04db12fbe3..9b2abfa19d 100644
--- a/config/config.h.in
+++ b/config/config.h.in
@@ -37,6 +37,7 @@
// TODO: add proper cmake check
// #define HAVE_SELINUX 1
#cmakedefine HAVE_SETENV
+#cmakedefine HAVE_UNSETENV
#cmakedefine HAVE_SETPGID
#cmakedefine HAVE_SETSID
#cmakedefine HAVE_SIGACTION
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index fc4bebbd6e..a9ed4acb19 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -5373,13 +5373,25 @@ server2client( {clientid}, {string}) *server2client()*
:echo server2client(expand("<client>"), "HELLO")
<
serverlist() *serverlist()*
- Return a list of available server names, one per line.
- When there are no servers or the information is not available
- an empty string is returned. See also |clientserver|.
- {only available when compiled with the |+clientserver| feature}
+ Returns a list of available server names in a list.
+ When there are no servers an empty string is returned.
Example: >
:echo serverlist()
<
+serverlisten([{address}]) *serverlisten()*
+ Opens a Unix or TCP socket at {address} for clients to connect
+ to and returns {address}. If no address is given, it is
+ equivalent to >
+ :call serverlisten(tempname())
+< If |$NVIM_LISTEN_ADDRESS| is not set, it will be set to
+ {address}.
+
+serverstop({address}) *serverstop()*
+ Closes the Unix or TCP socket at {address}. Does nothing if
+ {address} is empty, or does not refer to a server. If
+ {address} equals |$NVIM_LISTEN_ADDRESS|, the listen address
+ will be unset.
+
setbufvar({expr}, {varname}, {val}) *setbufvar()*
Set option or local variable {varname} in buffer {expr} to
{val}.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 1616bf60e2..1dab9df9cb 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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
*/
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index c03531ca0c..8fb2902b0b 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -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;
}
-/// Teardown the server module
-void server_teardown(void)
+/// Retrieve the file handle from a server.
+static uv_handle_t *server_handle(Server *server)
{
- if (!servers) {
- return;
- }
+ return server->type == kServerTypeTcp
+ ? (uv_handle_t *)&server->socket.tcp.handle
+ : (uv_handle_t *) &server->socket.pipe.handle;
+}
- Server *server;
+/// Teardown a single server
+static void server_close_cb(Server **server)
+{
+ uv_close(server_handle(*server), free_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);
- }
- });
+/// Teardown the server module
+void server_teardown(void)
+{
+ 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)
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 37158f4d3c..be4b22de3a 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -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)
diff --git a/test/functional/server/server_spec.lua b/test/functional/server/server_spec.lua
new file mode 100644
index 0000000000..5dd8197d52
--- /dev/null
+++ b/test/functional/server/server_spec.lua
@@ -0,0 +1,42 @@
+
+local helpers = require('test.functional.helpers')
+local nvim, eq, neq, ok, eval
+ = helpers.nvim, helpers.eq, helpers.neq, helpers.ok, helpers.eval
+local clear = helpers.clear
+
+describe('server*() functions', function()
+ before_each(clear)
+
+ it('set $NVIM_LISTEN_ADDRESS on first serverstart()', function()
+ -- Ensure the listen address is unset.
+ nvim('command', 'let $NVIM_LISTEN_ADDRESS = ""')
+ nvim('command', 'let s = serverstart()')
+ eq(1, eval('$NVIM_LISTEN_ADDRESS == s'))
+ nvim('command', 'call serverstop(s)')
+ eq(0, eval('$NVIM_LISTEN_ADDRESS == s'))
+ end)
+
+ it('let the user retrieve the list of servers', function()
+ -- There should already be at least one server.
+ local n = eval('len(serverlist())')
+
+ -- Add a few
+ local servs = {'should-not-exist', 'another-one-that-shouldnt'}
+ for _, s in ipairs(servs) do
+ eq(s, eval('serverstart("'..s..'")'))
+ end
+
+ local new_servs = eval('serverlist()')
+
+ -- Exactly #servs servers should be added.
+ eq(n + #servs, #new_servs)
+ -- The new servers should be at the end of the list.
+ for i = 1, #servs do
+ eq(servs[i], new_servs[i + n])
+ nvim('command', 'call serverstop("'..servs[i]..'")')
+ end
+ -- After calling serverstop() on the new servers, they should no longer be
+ -- in the list.
+ eq(n, eval('len(serverlist())'))
+ end)
+end)
diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua
index 5896f5ddd4..9d936c2564 100644
--- a/test/unit/os/env_spec.lua
+++ b/test/unit/os/env_spec.lua
@@ -3,6 +3,7 @@ local helpers = require('test.unit.helpers')
local cimport = helpers.cimport
local internalize = helpers.internalize
local eq = helpers.eq
+local neq = helpers.neq
local ffi = helpers.ffi
local lib = helpers.lib
local cstr = helpers.cstr
@@ -21,6 +22,10 @@ describe('env function', function()
return env.os_setenv((to_cstr(name)), (to_cstr(value)), override)
end
+ function os_unsetenv(name, value, override)
+ return env.os_unsetenv((to_cstr(name)))
+ end
+
function os_getenv(name)
local rval = env.os_getenv((to_cstr(name)))
if rval ~= NULL then
@@ -68,6 +73,18 @@ describe('env function', function()
end)
end)
+ describe('os_unsetenv', function()
+ it('unsets environment variable', function()
+ local name = 'TEST_UNSETENV'
+ local value = 'TESTVALUE'
+ os_setenv(name, value, 1)
+ os_unsetenv(name)
+ neq(os_getenv(name), value)
+ -- Depending on the platform the var might be unset or set as ''
+ assert.True(os_getenv(name) == nil or os_getenv(name) == '')
+ end)
+ end)
+
describe('os_getenvname_at_index', function()
it('returns names of environment variables', function()
local test_name = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N'