1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <assert.h> |
5 | #include <stdlib.h> |
6 | #include <string.h> |
7 | #include <inttypes.h> |
8 | |
9 | #include "nvim/msgpack_rpc/channel.h" |
10 | #include "nvim/msgpack_rpc/server.h" |
11 | #include "nvim/os/os.h" |
12 | #include "nvim/event/socket.h" |
13 | #include "nvim/ascii.h" |
14 | #include "nvim/eval.h" |
15 | #include "nvim/garray.h" |
16 | #include "nvim/vim.h" |
17 | #include "nvim/main.h" |
18 | #include "nvim/memory.h" |
19 | #include "nvim/log.h" |
20 | #include "nvim/fileio.h" |
21 | #include "nvim/path.h" |
22 | #include "nvim/strings.h" |
23 | |
24 | #define MAX_CONNECTIONS 32 |
25 | #define LISTEN_ADDRESS_ENV_VAR "NVIM_LISTEN_ADDRESS" |
26 | |
27 | static garray_T watchers = GA_EMPTY_INIT_VALUE; |
28 | |
29 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
30 | # include "msgpack_rpc/server.c.generated.h" |
31 | #endif |
32 | |
33 | /// Initializes the module |
34 | bool server_init(const char *listen_addr) |
35 | { |
36 | ga_init(&watchers, sizeof(SocketWatcher *), 1); |
37 | |
38 | // $NVIM_LISTEN_ADDRESS |
39 | const char *env_addr = os_getenv(LISTEN_ADDRESS_ENV_VAR); |
40 | int rv = listen_addr == NULL ? 1 : server_start(listen_addr); |
41 | |
42 | if (0 != rv) { |
43 | rv = env_addr == NULL ? 1 : server_start(env_addr); |
44 | if (0 != rv) { |
45 | listen_addr = server_address_new(); |
46 | if (listen_addr == NULL) { |
47 | return false; |
48 | } |
49 | rv = server_start(listen_addr); |
50 | xfree((char *)listen_addr); |
51 | } |
52 | } |
53 | |
54 | return rv == 0; |
55 | } |
56 | |
57 | /// Teardown a single server |
58 | static void close_socket_watcher(SocketWatcher **watcher) |
59 | { |
60 | socket_watcher_close(*watcher, free_server); |
61 | } |
62 | |
63 | /// Set v:servername to the first server in the server list, or unset it if no |
64 | /// servers are known. |
65 | static void set_vservername(garray_T *srvs) |
66 | { |
67 | char *default_server = (srvs->ga_len > 0) |
68 | ? ((SocketWatcher **)srvs->ga_data)[0]->addr |
69 | : NULL; |
70 | set_vim_var_string(VV_SEND_SERVER, default_server, -1); |
71 | } |
72 | |
73 | /// Teardown the server module |
74 | void server_teardown(void) |
75 | { |
76 | GA_DEEP_CLEAR(&watchers, SocketWatcher *, close_socket_watcher); |
77 | } |
78 | |
79 | /// Generates unique address for local server. |
80 | /// |
81 | /// In Windows this is a named pipe in the format |
82 | /// \\.\pipe\nvim-<PID>-<COUNTER>. |
83 | /// |
84 | /// For other systems it is a path returned by vim_tempname(). |
85 | /// |
86 | /// This function is NOT thread safe |
87 | char *server_address_new(void) |
88 | { |
89 | #ifdef WIN32 |
90 | static uint32_t count = 0; |
91 | char template[ADDRESS_MAX_SIZE]; |
92 | snprintf(template, ADDRESS_MAX_SIZE, |
93 | "\\\\.\\pipe\\nvim-%" PRIu64 "-%" PRIu32, os_get_pid(), count++); |
94 | return xstrdup(template); |
95 | #else |
96 | return (char *)vim_tempname(); |
97 | #endif |
98 | } |
99 | |
100 | /// Check if this instance owns a pipe address. |
101 | /// The argument must already be resolved to an absolute path! |
102 | bool server_owns_pipe_address(const char *path) |
103 | { |
104 | for (int i = 0; i < watchers.ga_len; i++) { |
105 | if (!strcmp(path, ((SocketWatcher **)watchers.ga_data)[i]->addr)) { |
106 | return true; |
107 | } |
108 | } |
109 | return false; |
110 | } |
111 | |
112 | /// Starts listening for API calls. |
113 | /// |
114 | /// The socket type is determined by parsing `endpoint`: If it's a valid IPv4 |
115 | /// or IPv6 address in 'ip:[port]' format, then it will be a TCP socket. |
116 | /// Otherwise it will be a Unix socket or named pipe (Windows). |
117 | /// |
118 | /// If no port is given, a random one will be assigned. |
119 | /// |
120 | /// @param endpoint Address of the server. Either a 'ip:[port]' string or an |
121 | /// arbitrary identifier (trimmed to 256 bytes) for the Unix |
122 | /// socket or named pipe. |
123 | /// @returns 0: success, 1: validation error, 2: already listening, |
124 | /// -errno: failed to bind or listen. |
125 | int server_start(const char *endpoint) |
126 | { |
127 | if (endpoint == NULL || endpoint[0] == '\0') { |
128 | WLOG("Empty or NULL endpoint" ); |
129 | return 1; |
130 | } |
131 | |
132 | SocketWatcher *watcher = xmalloc(sizeof(SocketWatcher)); |
133 | |
134 | int result = socket_watcher_init(&main_loop, watcher, endpoint); |
135 | if (result < 0) { |
136 | xfree(watcher); |
137 | return result; |
138 | } |
139 | |
140 | // Check if a watcher for the endpoint already exists |
141 | for (int i = 0; i < watchers.ga_len; i++) { |
142 | if (!strcmp(watcher->addr, ((SocketWatcher **)watchers.ga_data)[i]->addr)) { |
143 | ELOG("Already listening on %s" , watcher->addr); |
144 | if (watcher->stream->type == UV_TCP) { |
145 | uv_freeaddrinfo(watcher->uv.tcp.addrinfo); |
146 | } |
147 | socket_watcher_close(watcher, free_server); |
148 | return 2; |
149 | } |
150 | } |
151 | |
152 | result = socket_watcher_start(watcher, MAX_CONNECTIONS, connection_cb); |
153 | if (result < 0) { |
154 | WLOG("Failed to start server: %s" , uv_strerror(result)); |
155 | socket_watcher_close(watcher, free_server); |
156 | return result; |
157 | } |
158 | |
159 | // Update $NVIM_LISTEN_ADDRESS, if not set. |
160 | const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR); |
161 | if (listen_address == NULL) { |
162 | os_setenv(LISTEN_ADDRESS_ENV_VAR, watcher->addr, 1); |
163 | } |
164 | |
165 | // Add the watcher to the list. |
166 | ga_grow(&watchers, 1); |
167 | ((SocketWatcher **)watchers.ga_data)[watchers.ga_len++] = watcher; |
168 | |
169 | // Update v:servername, if not set. |
170 | if (STRLEN(get_vim_var_str(VV_SEND_SERVER)) == 0) { |
171 | set_vservername(&watchers); |
172 | } |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | /// Stops listening on the address specified by `endpoint`. |
178 | /// |
179 | /// @param endpoint Address of the server. |
180 | bool server_stop(char *endpoint) |
181 | { |
182 | SocketWatcher *watcher; |
183 | bool watcher_found = false; |
184 | char addr[ADDRESS_MAX_SIZE]; |
185 | |
186 | // Trim to `ADDRESS_MAX_SIZE` |
187 | xstrlcpy(addr, endpoint, sizeof(addr)); |
188 | |
189 | int i = 0; // Index of the server whose address equals addr. |
190 | for (; i < watchers.ga_len; i++) { |
191 | watcher = ((SocketWatcher **)watchers.ga_data)[i]; |
192 | if (strcmp(addr, watcher->addr) == 0) { |
193 | watcher_found = true; |
194 | break; |
195 | } |
196 | } |
197 | |
198 | if (!watcher_found) { |
199 | WLOG("Not listening on %s" , addr); |
200 | return false; |
201 | } |
202 | |
203 | // Unset $NVIM_LISTEN_ADDRESS if it is the stopped address. |
204 | const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR); |
205 | if (listen_address && STRCMP(addr, listen_address) == 0) { |
206 | os_unsetenv(LISTEN_ADDRESS_ENV_VAR); |
207 | } |
208 | |
209 | socket_watcher_close(watcher, free_server); |
210 | |
211 | // Remove this server from the list by swapping it with the last item. |
212 | if (i != watchers.ga_len - 1) { |
213 | ((SocketWatcher **)watchers.ga_data)[i] = |
214 | ((SocketWatcher **)watchers.ga_data)[watchers.ga_len - 1]; |
215 | } |
216 | watchers.ga_len--; |
217 | |
218 | // If v:servername is the stopped address, re-initialize it. |
219 | if (STRCMP(addr, get_vim_var_str(VV_SEND_SERVER)) == 0) { |
220 | set_vservername(&watchers); |
221 | } |
222 | |
223 | return true; |
224 | } |
225 | |
226 | /// Returns an allocated array of server addresses. |
227 | /// @param[out] size The size of the returned array. |
228 | char **server_address_list(size_t *size) |
229 | FUNC_ATTR_NONNULL_ALL |
230 | { |
231 | if ((*size = (size_t)watchers.ga_len) == 0) { |
232 | return NULL; |
233 | } |
234 | |
235 | char **addrs = xcalloc((size_t)watchers.ga_len, sizeof(const char *)); |
236 | for (int i = 0; i < watchers.ga_len; i++) { |
237 | addrs[i] = xstrdup(((SocketWatcher **)watchers.ga_data)[i]->addr); |
238 | } |
239 | return addrs; |
240 | } |
241 | |
242 | static void connection_cb(SocketWatcher *watcher, int result, void *data) |
243 | { |
244 | if (result) { |
245 | ELOG("Failed to accept connection: %s" , uv_strerror(result)); |
246 | return; |
247 | } |
248 | |
249 | channel_from_connection(watcher); |
250 | } |
251 | |
252 | static void free_server(SocketWatcher *watcher, void *data) |
253 | { |
254 | xfree(watcher); |
255 | } |
256 | |