neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

server.c (8492B)


      1 #include <inttypes.h>
      2 #include <stdbool.h>
      3 #include <stdio.h>
      4 #include <string.h>
      5 #include <uv.h>
      6 
      7 #include "nvim/ascii_defs.h"
      8 #include "nvim/channel.h"
      9 #include "nvim/eval/vars.h"
     10 #include "nvim/event/defs.h"
     11 #include "nvim/event/socket.h"
     12 #include "nvim/garray.h"
     13 #include "nvim/garray_defs.h"
     14 #include "nvim/globals.h"
     15 #include "nvim/log.h"
     16 #include "nvim/main.h"
     17 #include "nvim/memory.h"
     18 #include "nvim/msgpack_rpc/server.h"
     19 #include "nvim/os/os.h"
     20 #include "nvim/os/os_defs.h"
     21 #include "nvim/os/stdpaths_defs.h"
     22 #include "nvim/path.h"
     23 #include "nvim/types_defs.h"
     24 
     25 #define MAX_CONNECTIONS 32
     26 #define ENV_LISTEN "NVIM_LISTEN_ADDRESS"  // deprecated
     27 
     28 static garray_T watchers = GA_EMPTY_INIT_VALUE;
     29 
     30 #include "msgpack_rpc/server.c.generated.h"
     31 
     32 /// Initializes resources, handles `--listen`, starts the primary server at v:servername.
     33 ///
     34 /// @returns true on success, false on fatal error (message stored in IObuff)
     35 bool server_init(const char *listen_addr)
     36 {
     37  bool ok = true;
     38  bool must_free = false;
     39  TriState user_arg = kTrue;  // User-provided --listen arg.
     40  ga_init(&watchers, sizeof(SocketWatcher *), 1);
     41 
     42  // $NVIM_LISTEN_ADDRESS (deprecated)
     43  if (!listen_addr || listen_addr[0] == '\0') {
     44    if (os_env_exists(ENV_LISTEN, true)) {
     45      user_arg = kFalse;  // User-provided env var.
     46      listen_addr = os_getenv(ENV_LISTEN);
     47    } else {
     48      user_arg = kNone;  // Autogenerated server address.
     49      listen_addr = server_address_new(NULL);
     50    }
     51    must_free = true;
     52  }
     53 
     54  int rv = server_start(listen_addr);
     55 
     56  // TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
     57  if (os_env_exists("__NVIM_TEST_LOG", false)) {
     58    ELOG("test log message");
     59  }
     60 
     61  if (rv == 0 || user_arg == kNone) {
     62    // The autogenerated servername can fail if the user has a broken $XDG_RUNTIME_DIR. #30282
     63    // But that is not fatal (startup will continue, logged in $NVIM_LOGFILE, empty v:servername).
     64    goto end;
     65  }
     66 
     67  (void)snprintf(IObuff, IOSIZE,
     68                 user_arg ==
     69                 kTrue ? "Failed to --listen: %s: \"%s\""
     70                       : "Failed $NVIM_LISTEN_ADDRESS: %s: \"%s\"",
     71                 rv < 0 ? os_strerror(rv) : (rv == 1 ? "empty address" : "?"),
     72                 listen_addr);
     73  ok = false;
     74 
     75 end:
     76  if (os_env_exists(ENV_LISTEN, false)) {
     77    // Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter. It is "input only", it must not be
     78    // leaked to child jobs or :terminal.
     79    os_unsetenv(ENV_LISTEN);
     80  }
     81 
     82  if (must_free) {
     83    xfree((char *)listen_addr);
     84  }
     85 
     86  return ok;
     87 }
     88 
     89 /// Teardown a single server
     90 static void close_socket_watcher(SocketWatcher **watcher)
     91 {
     92  socket_watcher_close(*watcher, free_server);
     93 }
     94 
     95 /// Sets the "primary address" (v:servername and $NVIM) to the first server in
     96 /// the server list, or unsets if no servers are known.
     97 static void set_vservername(garray_T *srvs)
     98 {
     99  char *default_server = (srvs->ga_len > 0)
    100                         ? ((SocketWatcher **)srvs->ga_data)[0]->addr
    101                         : NULL;
    102  set_vim_var_string(VV_SEND_SERVER, default_server, -1);
    103 }
    104 
    105 /// Teardown the server module
    106 void server_teardown(void)
    107 {
    108  GA_DEEP_CLEAR(&watchers, SocketWatcher *, close_socket_watcher);
    109 }
    110 
    111 /// Generates unique address for local server.
    112 ///
    113 /// Named pipe format:
    114 /// - Windows: "\\.\pipe\<name>.<pid>.<counter>"
    115 /// - Other: "/tmp/nvim.user/xxx/<name>.<pid>.<counter>"
    116 char *server_address_new(const char *name)
    117  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
    118 {
    119  static uint32_t count = 0;
    120  char fmt[ADDRESS_MAX_SIZE];
    121 #ifdef MSWIN
    122  (void)get_appname(true);  // Writes appname to NameBuf.
    123  int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32,
    124                   name ? name : NameBuff, os_get_pid(), count++);
    125 #else
    126  char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir);
    127  (void)get_appname(true);  // Writes appname to NameBuf.
    128  int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32,
    129                   dir, name ? name : NameBuff, os_get_pid(), count++);
    130  xfree(dir);
    131 #endif
    132  if ((size_t)r >= sizeof(fmt)) {
    133    ELOG("truncated server address: %.40s...", fmt);
    134  }
    135  return xstrdup(fmt);
    136 }
    137 
    138 /// Check if this instance owns a pipe address (loopback).
    139 bool server_owns_pipe_address(const char *address)
    140 {
    141  bool result = false;
    142  char *path = fix_fname(address);
    143  for (int i = 0; i < watchers.ga_len; i++) {
    144    char *addr = fix_fname(((SocketWatcher **)watchers.ga_data)[i]->addr);
    145    result = strequal(path, addr);
    146    xfree(addr);
    147    if (result) {
    148      break;
    149    }
    150  }
    151  xfree(path);
    152  return result;
    153 }
    154 
    155 /// Starts listening for RPC calls.
    156 ///
    157 /// Socket type is decided by the format of `addr`:
    158 /// - TCP socket if it looks like an IPv4/6 address ("ip:[port]").
    159 ///   - If [port] is omitted, a random one is assigned.
    160 /// - Unix socket (or named pipe on Windows) otherwise.
    161 ///   - If the name doesn't contain slashes it is appended to a generated path. #8519
    162 ///
    163 /// @param addr Server address: a "ip:[port]" string or arbitrary name or filepath (max 256 bytes)
    164 ///             for the Unix socket or named pipe.
    165 /// @returns 0: success, 1: validation error, 2: already listening, -errno: failed to bind/listen.
    166 int server_start(const char *addr)
    167 {
    168  if (addr == NULL || addr[0] == NUL) {
    169    WLOG("Empty or NULL address");
    170    return 1;
    171  }
    172 
    173  bool isname = !strstr(addr, ":") && !strstr(addr, "/") && !strstr(addr, "\\");
    174  char *addr_gen = isname ? server_address_new(addr) : NULL;
    175  SocketWatcher *watcher = xmalloc(sizeof(SocketWatcher));
    176  int result = socket_watcher_init(&main_loop, watcher, isname ? addr_gen : addr);
    177  xfree(addr_gen);
    178  if (result < 0) {
    179    xfree(watcher);
    180    return result;
    181  }
    182 
    183  // Check if a watcher for the address already exists.
    184  for (int i = 0; i < watchers.ga_len; i++) {
    185    if (!strcmp(watcher->addr, ((SocketWatcher **)watchers.ga_data)[i]->addr)) {
    186      ELOG("Already listening on %s", watcher->addr);
    187      if (watcher->stream->type == UV_TCP) {
    188        uv_freeaddrinfo(watcher->uv.tcp.addrinfo);
    189      }
    190      socket_watcher_close(watcher, free_server);
    191      return 2;
    192    }
    193  }
    194 
    195  result = socket_watcher_start(watcher, MAX_CONNECTIONS, connection_cb);
    196  if (result < 0) {
    197    WLOG("Failed to start server: %s: %s", uv_strerror(result), watcher->addr);
    198    socket_watcher_close(watcher, free_server);
    199    return result;
    200  }
    201 
    202  // Add the watcher to the list.
    203  ga_grow(&watchers, 1);
    204  ((SocketWatcher **)watchers.ga_data)[watchers.ga_len++] = watcher;
    205 
    206  // Update v:servername, if not set.
    207  if (strlen(get_vim_var_str(VV_SEND_SERVER)) == 0) {
    208    set_vservername(&watchers);
    209  }
    210 
    211  return 0;
    212 }
    213 
    214 /// Stops listening on the address specified by `endpoint`.
    215 ///
    216 /// @param endpoint Address of the server.
    217 bool server_stop(char *endpoint)
    218 {
    219  SocketWatcher *watcher;
    220  bool watcher_found = false;
    221  char addr[ADDRESS_MAX_SIZE];
    222 
    223  // Trim to `ADDRESS_MAX_SIZE`
    224  xstrlcpy(addr, endpoint, sizeof(addr));
    225 
    226  int i = 0;  // Index of the server whose address equals addr.
    227  for (; i < watchers.ga_len; i++) {
    228    watcher = ((SocketWatcher **)watchers.ga_data)[i];
    229    if (strcmp(addr, watcher->addr) == 0) {
    230      watcher_found = true;
    231      break;
    232    }
    233  }
    234 
    235  if (!watcher_found) {
    236    WLOG("Not listening on %s", addr);
    237    return false;
    238  }
    239 
    240  socket_watcher_close(watcher, free_server);
    241 
    242  // Remove this server from the list by swapping it with the last item.
    243  if (i != watchers.ga_len - 1) {
    244    ((SocketWatcher **)watchers.ga_data)[i] =
    245      ((SocketWatcher **)watchers.ga_data)[watchers.ga_len - 1];
    246  }
    247  watchers.ga_len--;
    248 
    249  // Bump v:servername to the next available server, if any.
    250  if (strequal(addr, get_vim_var_str(VV_SEND_SERVER))) {
    251    set_vservername(&watchers);
    252  }
    253 
    254  return true;
    255 }
    256 
    257 /// Returns an allocated array of server addresses.
    258 /// @param[out] size The size of the returned array.
    259 char **server_address_list(size_t *size)
    260  FUNC_ATTR_NONNULL_ALL
    261 {
    262  if ((*size = (size_t)watchers.ga_len) == 0) {
    263    return NULL;
    264  }
    265 
    266  char **addrs = xcalloc((size_t)watchers.ga_len, sizeof(const char *));
    267  for (int i = 0; i < watchers.ga_len; i++) {
    268    addrs[i] = xstrdup(((SocketWatcher **)watchers.ga_data)[i]->addr);
    269  }
    270  return addrs;
    271 }
    272 
    273 static void connection_cb(SocketWatcher *watcher, int result, void *data)
    274 {
    275  if (result) {
    276    ELOG("Failed to accept connection: %s", uv_strerror(result));
    277    return;
    278  }
    279 
    280  channel_from_connection(watcher);
    281 }
    282 
    283 static void free_server(SocketWatcher *watcher, void *data)
    284 {
    285  xfree(watcher);
    286 }