neovim

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

socket.c (11241B)


      1 #include <assert.h>
      2 #include <inttypes.h>
      3 #include <stdbool.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 #include <uv.h>
      7 
      8 #include "nvim/ascii_defs.h"
      9 #include "nvim/charset.h"
     10 #include "nvim/event/defs.h"
     11 #include "nvim/event/loop.h"
     12 #include "nvim/event/multiqueue.h"
     13 #include "nvim/event/socket.h"
     14 #include "nvim/event/stream.h"
     15 #include "nvim/gettext_defs.h"
     16 #include "nvim/log.h"
     17 #include "nvim/main.h"
     18 #include "nvim/memory.h"
     19 #include "nvim/os/fs.h"
     20 #include "nvim/os/os_defs.h"
     21 #include "nvim/path.h"
     22 #include "nvim/types_defs.h"
     23 
     24 #include "event/socket.c.generated.h"
     25 
     26 /// Checks if an address string looks like a TCP endpoint, and returns the end of the host part.
     27 ///
     28 /// @param address Address string
     29 /// @return pointer to the end of the host part of the address, or NULL if it is not a TCP address
     30 char *socket_address_tcp_host_end(const char *address)
     31 {
     32  if (address == NULL) {
     33    return NULL;
     34  }
     35 
     36  // Windows drive letter path: "X:\..." or "X:/..." is a local path, not TCP.
     37  if (ASCII_ISALPHA((uint8_t)address[0]) && address[1] == ':'
     38      && (address[2] == '\\' || address[2] == '/')) {
     39    return NULL;
     40  }
     41 
     42  char *colon = strrchr(address, ':');
     43  return colon != NULL && colon != address ? colon : NULL;
     44 }
     45 
     46 int socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint)
     47  FUNC_ATTR_NONNULL_ALL
     48 {
     49  xstrlcpy(watcher->addr, endpoint, sizeof(watcher->addr));
     50  char *addr = watcher->addr;
     51  char *host_end = socket_address_tcp_host_end(addr);
     52 
     53  if (host_end) {
     54    // Split user specified address into two strings, addr (hostname) and port.
     55    // The port part in watcher->addr will be updated later.
     56    *host_end = NUL;
     57    char *port = host_end + 1;
     58    intmax_t iport;
     59 
     60    int ok = try_getdigits(&(char *){ port }, &iport);
     61    if (!ok || iport < 0 || iport > UINT16_MAX) {
     62      ELOG("Invalid port: %s", port);
     63      return UV_EINVAL;
     64    }
     65 
     66    if (*port == NUL) {
     67      // When no port is given, (uv_)getaddrinfo expects NULL otherwise the
     68      // implementation may attempt to lookup the service by name (and fail)
     69      port = NULL;
     70    }
     71 
     72    uv_getaddrinfo_t request;
     73 
     74    int retval = uv_getaddrinfo(&loop->uv, &request, NULL, addr, port,
     75                                &(struct addrinfo){ .ai_family = AF_UNSPEC,
     76                                                    .ai_socktype = SOCK_STREAM, });
     77    if (retval != 0) {
     78      ELOG("Host lookup failed: %s", endpoint);
     79      return retval;
     80    }
     81    watcher->uv.tcp.addrinfo = request.addrinfo;
     82 
     83    uv_tcp_init(&loop->uv, &watcher->uv.tcp.handle);
     84    uv_tcp_nodelay(&watcher->uv.tcp.handle, true);
     85    watcher->stream = (uv_stream_t *)(&watcher->uv.tcp.handle);
     86  } else {
     87    uv_pipe_init(&loop->uv, &watcher->uv.pipe.handle, 0);
     88    watcher->stream = (uv_stream_t *)(&watcher->uv.pipe.handle);
     89  }
     90 
     91  watcher->stream->data = watcher;
     92  watcher->cb = NULL;
     93  watcher->close_cb = NULL;
     94  watcher->events = NULL;
     95  watcher->data = NULL;
     96 
     97  return 0;
     98 }
     99 
    100 /// Callback for closing a handle initialized by socket_connect().
    101 static void connect_close_cb(uv_handle_t *handle)
    102 {
    103  bool *closed = handle->data;
    104  *closed = true;
    105 }
    106 
    107 /// Check if a socket is alive by attempting to connect to it.
    108 /// @param loop Event loop
    109 /// @param addr Socket address to probe
    110 /// @return true if socket is alive (connection succeeded), false otherwise
    111 static bool socket_alive(Loop *loop, const char *addr)
    112 {
    113  RStream stream;
    114  const char *error = NULL;
    115 
    116  // Try to connect with a 500ms timeout (fast failure for dead sockets)
    117  bool connected = socket_connect(loop, &stream, false, addr, 500, &error);
    118  if (!connected) {
    119    return false;
    120  }
    121 
    122  // Connection succeeded - socket is alive. Close the probe connection properly.
    123  bool closed = false;
    124  stream.s.uv.pipe.data = &closed;
    125  uv_close((uv_handle_t *)&stream.s.uv.pipe, connect_close_cb);
    126  LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, -1, closed);
    127 
    128  return true;
    129 }
    130 
    131 int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
    132  FUNC_ATTR_NONNULL_ALL
    133 {
    134  watcher->cb = cb;
    135  int result = UV_EINVAL;
    136 
    137  if (watcher->stream->type == UV_TCP) {
    138    struct addrinfo *ai = watcher->uv.tcp.addrinfo;
    139 
    140    for (; ai; ai = ai->ai_next) {
    141      result = uv_tcp_bind(&watcher->uv.tcp.handle, ai->ai_addr, 0);
    142      if (result != 0) {
    143        continue;
    144      }
    145      result = uv_listen(watcher->stream, backlog, connection_cb);
    146      if (result == 0) {
    147        struct sockaddr_storage sas;
    148 
    149        // When the endpoint in socket_watcher_init() didn't specify a port
    150        // number, a free random port number will be assigned. sin_port will
    151        // contain 0 in this case, unless uv_tcp_getsockname() is used first.
    152        uv_tcp_getsockname(&watcher->uv.tcp.handle, (struct sockaddr *)&sas,
    153                           &(int){ sizeof(sas) });
    154        uint16_t port = (sas.ss_family == AF_INET) ? ((struct sockaddr_in *)(&sas))->sin_port
    155                                                   : ((struct sockaddr_in6 *)(&sas))->sin6_port;
    156        // v:servername uses the string from watcher->addr
    157        size_t len = strlen(watcher->addr);
    158        snprintf(watcher->addr + len, sizeof(watcher->addr) - len, ":%" PRIu16,
    159                 ntohs(port));
    160        break;
    161      }
    162    }
    163    uv_freeaddrinfo(watcher->uv.tcp.addrinfo);
    164  } else {
    165    result = uv_pipe_bind(&watcher->uv.pipe.handle, watcher->addr);
    166 
    167    // If bind failed with EACCES/EADDRINUSE, check if socket is stale
    168    if (result == UV_EACCES || result == UV_EADDRINUSE) {
    169      Loop *loop = watcher->stream->loop->data;
    170 
    171      if (!socket_alive(loop, watcher->addr)) {
    172        // Socket exists but is dead - remove it
    173        ILOG("Removing stale socket: %s", watcher->addr);
    174        int rm_result = os_remove(watcher->addr);
    175 
    176        if (rm_result != 0) {
    177          WLOG("Failed to remove stale socket %s: %s",
    178               watcher->addr, uv_strerror(rm_result));
    179        } else {
    180          // Close and reinit the pipe handle before retrying bind
    181          uv_loop_t *uv_loop = watcher->uv.pipe.handle.loop;
    182          bool closed = false;
    183          watcher->uv.pipe.handle.data = &closed;
    184          uv_close((uv_handle_t *)&watcher->uv.pipe.handle, connect_close_cb);
    185          LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, -1, closed);
    186 
    187          uv_pipe_init(uv_loop, &watcher->uv.pipe.handle, 0);
    188          watcher->stream = (uv_stream_t *)(&watcher->uv.pipe.handle);
    189          watcher->stream->data = watcher;
    190 
    191          // Retry bind with fresh handle
    192          result = uv_pipe_bind(&watcher->uv.pipe.handle, watcher->addr);
    193        }
    194      } else {
    195        // Socket is alive - this is a real error
    196        ELOG("Socket already in use by another Nvim instance: %s", watcher->addr);
    197      }
    198    }
    199 
    200    if (result == 0) {
    201      result = uv_listen(watcher->stream, backlog, connection_cb);
    202    }
    203  }
    204 
    205  assert(result <= 0);  // libuv should return negative error code or zero.
    206  if (result < 0) {
    207    if (result == UV_EACCES) {
    208      // Libuv converts ENOENT to EACCES for Windows compatibility, but if
    209      // the parent directory does not exist, ENOENT would be more accurate.
    210      *path_tail(watcher->addr) = NUL;
    211      if (!os_path_exists(watcher->addr)) {
    212        result = UV_ENOENT;
    213      }
    214    }
    215    return result;
    216  }
    217 
    218  return 0;
    219 }
    220 
    221 int socket_watcher_accept(SocketWatcher *watcher, RStream *stream)
    222  FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2)
    223 {
    224  uv_stream_t *client;
    225 
    226  if (watcher->stream->type == UV_TCP) {
    227    client = (uv_stream_t *)(&stream->s.uv.tcp);
    228    uv_tcp_init(watcher->uv.tcp.handle.loop, (uv_tcp_t *)client);
    229    uv_tcp_nodelay((uv_tcp_t *)client, true);
    230  } else {
    231    client = (uv_stream_t *)&stream->s.uv.pipe;
    232    uv_pipe_init(watcher->uv.pipe.handle.loop, (uv_pipe_t *)client, 0);
    233  }
    234 
    235  int result = uv_accept(watcher->stream, client);
    236 
    237  if (result) {
    238    uv_close((uv_handle_t *)client, NULL);
    239    return result;
    240  }
    241 
    242  stream_init(NULL, &stream->s, -1, false, client);
    243  return 0;
    244 }
    245 
    246 void socket_watcher_close(SocketWatcher *watcher, socket_close_cb cb)
    247  FUNC_ATTR_NONNULL_ARG(1)
    248 {
    249  watcher->close_cb = cb;
    250  uv_close((uv_handle_t *)watcher->stream, close_cb);
    251 }
    252 
    253 static void connection_event(void **argv)
    254 {
    255  SocketWatcher *watcher = argv[0];
    256  int status = (int)(uintptr_t)(argv[1]);
    257  watcher->cb(watcher, status, watcher->data);
    258 }
    259 
    260 static void connection_cb(uv_stream_t *handle, int status)
    261 {
    262  SocketWatcher *watcher = handle->data;
    263  CREATE_EVENT(watcher->events, connection_event, watcher, (void *)(uintptr_t)status);
    264 }
    265 
    266 static void close_cb(uv_handle_t *handle)
    267 {
    268  SocketWatcher *watcher = handle->data;
    269  if (watcher->close_cb) {
    270    watcher->close_cb(watcher, watcher->data);
    271  }
    272 }
    273 
    274 static void connect_cb(uv_connect_t *req, int status)
    275 {
    276  int *ret_status = req->data;
    277  *ret_status = status;
    278  uv_handle_t *handle = (uv_handle_t *)req->handle;
    279  if (status != 0 && !uv_is_closing(handle)) {
    280    uv_close(handle, connect_close_cb);
    281  }
    282 }
    283 
    284 bool socket_connect(Loop *loop, RStream *stream, bool is_tcp, const char *address, int timeout,
    285                    const char **error)
    286 {
    287  bool success = false;
    288  bool closed;
    289  int status;
    290  uv_connect_t req;
    291  req.data = &status;
    292  uv_stream_t *uv_stream;
    293 
    294  uv_tcp_t *tcp = &stream->s.uv.tcp;
    295  uv_getaddrinfo_t addr_req;
    296  addr_req.addrinfo = NULL;
    297  const struct addrinfo *addrinfo = NULL;
    298  char *addr = NULL;
    299  if (is_tcp) {
    300    addr = xstrdup(address);
    301    char *host_end = strrchr(addr, ':');
    302    if (!host_end) {
    303      *error = _("tcp address must be host:port");
    304      goto cleanup;
    305    }
    306    *host_end = NUL;
    307 
    308    const struct addrinfo hints = { .ai_family = AF_UNSPEC,
    309                                    .ai_socktype = SOCK_STREAM,
    310                                    .ai_flags = AI_NUMERICSERV };
    311    int retval = uv_getaddrinfo(&loop->uv, &addr_req, NULL,
    312                                addr, host_end + 1, &hints);
    313    if (retval != 0) {
    314      *error = _("failed to lookup host or port");
    315      goto cleanup;
    316    }
    317    addrinfo = addr_req.addrinfo;
    318 
    319 tcp_retry:
    320    uv_tcp_init(&loop->uv, tcp);
    321    uv_tcp_nodelay(tcp, true);
    322    uv_tcp_connect(&req,  tcp, addrinfo->ai_addr, connect_cb);
    323    uv_stream = (uv_stream_t *)tcp;
    324  } else {
    325    uv_pipe_t *pipe = &stream->s.uv.pipe;
    326    uv_pipe_init(&loop->uv, pipe, 0);
    327    uv_pipe_connect(&req,  pipe, address, connect_cb);
    328    uv_stream = (uv_stream_t *)pipe;
    329  }
    330  uv_stream->data = &closed;
    331  closed = false;
    332  status = 1;
    333  LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, timeout, status != 1);
    334  if (status == 0) {
    335    stream_init(NULL, &stream->s, -1, false, uv_stream);
    336    assert(uv_stream->data != &closed);  // Should have been set by stream_init().
    337    success = true;
    338  } else {
    339    if (!uv_is_closing((uv_handle_t *)uv_stream)) {
    340      uv_close((uv_handle_t *)uv_stream, connect_close_cb);
    341    }
    342    // Wait for the close callback to arrive before retrying or returning, otherwise
    343    // it may lead to a hang or stack-use-after-return.
    344    LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, -1, closed);
    345 
    346    if (is_tcp && addrinfo->ai_next) {
    347      addrinfo = addrinfo->ai_next;
    348      goto tcp_retry;
    349    } else {
    350      *error = _("connection refused");
    351    }
    352  }
    353 
    354 cleanup:
    355  xfree(addr);
    356  uv_freeaddrinfo(addr_req.addrinfo);
    357  return success;
    358 }