aboutsummaryrefslogtreecommitdiffstats
path: root/sway/ipc-server.c
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2017-11-22 21:37:07 -0500
committerLibravatar Drew DeVault <sir@cmpwn.com>2017-11-22 21:37:07 -0500
commit7753a0ec750f17e1f014cda0dd2564ae0533cdd4 (patch)
tree581d84114d97edbe2ef068a7568fe44b63159ce5 /sway/ipc-server.c
parentAdd initial command subsystem (untested) (diff)
downloadsway-7753a0ec750f17e1f014cda0dd2564ae0533cdd4.tar.gz
sway-7753a0ec750f17e1f014cda0dd2564ae0533cdd4.tar.zst
sway-7753a0ec750f17e1f014cda0dd2564ae0533cdd4.zip
Wire up IPC server
Diffstat (limited to 'sway/ipc-server.c')
-rw-r--r--sway/ipc-server.c411
1 files changed, 411 insertions, 0 deletions
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
new file mode 100644
index 00000000..f3a6469b
--- /dev/null
+++ b/sway/ipc-server.c
@@ -0,0 +1,411 @@
1// See https://i3wm.org/docs/ipc.html for protocol information
2#ifndef __FreeBSD__
3// Any value will hide SOCK_CLOEXEC on FreeBSD (__BSD_VISIBLE=0)
4#define _XOPEN_SOURCE 700
5#endif
6#include <assert.h>
7#include <errno.h>
8#include <fcntl.h>
9#include <json-c/json.h>
10#include <stdbool.h>
11#include <stdint.h>
12#include <stdlib.h>
13#include <string.h>
14#include <sys/socket.h>
15#include <sys/ioctl.h>
16#include <sys/un.h>
17#include <unistd.h>
18#include <wayland-server.h>
19#include "sway/commands.h"
20#include "sway/ipc-json.h"
21#include "sway/ipc-server.h"
22#include "sway/server.h"
23#include "list.h"
24#include "log.h"
25
26static int ipc_socket = -1;
27static struct wl_event_source *ipc_event_source = NULL;
28static struct sockaddr_un *ipc_sockaddr = NULL;
29static list_t *ipc_client_list = NULL;
30
31static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'};
32
33struct ipc_client {
34 struct wl_event_source *event_source;
35 struct wl_event_source *writable_event_source;
36 struct sway_server *server;
37 int fd;
38 uint32_t payload_length;
39 uint32_t security_policy;
40 enum ipc_command_type current_command;
41 enum ipc_command_type subscribed_events;
42 size_t write_buffer_len;
43 size_t write_buffer_size;
44 char *write_buffer;
45};
46
47struct sockaddr_un *ipc_user_sockaddr(void);
48int ipc_handle_connection(int fd, uint32_t mask, void *data);
49int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data);
50int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data);
51void ipc_client_disconnect(struct ipc_client *client);
52void ipc_client_handle_command(struct ipc_client *client);
53bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length);
54
55void ipc_init(struct sway_server *server) {
56 ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
57 if (ipc_socket == -1) {
58 sway_abort("Unable to create IPC socket");
59 }
60
61 ipc_sockaddr = ipc_user_sockaddr();
62
63 // We want to use socket name set by user, not existing socket from another sway instance.
64 if (getenv("SWAYSOCK") != NULL && access(getenv("SWAYSOCK"), F_OK) == -1) {
65 strncpy(ipc_sockaddr->sun_path, getenv("SWAYSOCK"), sizeof(ipc_sockaddr->sun_path));
66 ipc_sockaddr->sun_path[sizeof(ipc_sockaddr->sun_path) - 1] = 0;
67 }
68
69 unlink(ipc_sockaddr->sun_path);
70 if (bind(ipc_socket, (struct sockaddr *)ipc_sockaddr, sizeof(*ipc_sockaddr)) == -1) {
71 sway_abort("Unable to bind IPC socket");
72 }
73
74 if (listen(ipc_socket, 3) == -1) {
75 sway_abort("Unable to listen on IPC socket");
76 }
77
78 // Set i3 IPC socket path so that i3-msg works out of the box
79 setenv("I3SOCK", ipc_sockaddr->sun_path, 1);
80 setenv("SWAYSOCK", ipc_sockaddr->sun_path, 1);
81
82 ipc_client_list = create_list();
83
84 ipc_event_source = wl_event_loop_add_fd(server->wl_event_loop, ipc_socket,
85 WL_EVENT_READABLE, ipc_handle_connection, server);
86}
87
88void ipc_terminate(void) {
89 if (ipc_event_source) {
90 wl_event_source_remove(ipc_event_source);
91 }
92 close(ipc_socket);
93 unlink(ipc_sockaddr->sun_path);
94
95 list_free(ipc_client_list);
96
97 if (ipc_sockaddr) {
98 free(ipc_sockaddr);
99 }
100}
101
102struct sockaddr_un *ipc_user_sockaddr(void) {
103 struct sockaddr_un *ipc_sockaddr = malloc(sizeof(struct sockaddr_un));
104 if (ipc_sockaddr == NULL) {
105 sway_abort("Can't allocate ipc_sockaddr");
106 }
107
108 ipc_sockaddr->sun_family = AF_UNIX;
109 int path_size = sizeof(ipc_sockaddr->sun_path);
110
111 // Env var typically set by logind, e.g. "/run/user/<user-id>"
112 const char *dir = getenv("XDG_RUNTIME_DIR");
113 if (!dir) {
114 dir = "/tmp";
115 }
116 if (path_size <= snprintf(ipc_sockaddr->sun_path, path_size,
117 "%s/sway-ipc.%i.%i.sock", dir, getuid(), getpid())) {
118 sway_abort("Socket path won't fit into ipc_sockaddr->sun_path");
119 }
120
121 return ipc_sockaddr;
122}
123
124int ipc_handle_connection(int fd, uint32_t mask, void *data) {
125 (void) fd;
126 struct sway_server *server = data;
127 sway_log(L_DEBUG, "Event on IPC listening socket");
128 assert(mask == WL_EVENT_READABLE);
129
130 int client_fd = accept(ipc_socket, NULL, NULL);
131 if (client_fd == -1) {
132 sway_log_errno(L_ERROR, "Unable to accept IPC client connection");
133 return 0;
134 }
135
136 int flags;
137 if ((flags = fcntl(client_fd, F_GETFD)) == -1
138 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) {
139 sway_log_errno(L_ERROR, "Unable to set CLOEXEC on IPC client socket");
140 close(client_fd);
141 return 0;
142 }
143 if ((flags = fcntl(client_fd, F_GETFL)) == -1
144 || fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) {
145 sway_log_errno(L_ERROR, "Unable to set NONBLOCK on IPC client socket");
146 close(client_fd);
147 return 0;
148 }
149
150 struct ipc_client *client = malloc(sizeof(struct ipc_client));
151 if (!client) {
152 sway_log(L_ERROR, "Unable to allocate ipc client");
153 close(client_fd);
154 return 0;
155 }
156 client->server = server;
157 client->payload_length = 0;
158 client->fd = client_fd;
159 client->subscribed_events = 0;
160 client->event_source = wl_event_loop_add_fd(server->wl_event_loop,
161 client_fd, WL_EVENT_READABLE, ipc_client_handle_readable, client);
162 client->writable_event_source = NULL;
163
164 client->write_buffer_size = 128;
165 client->write_buffer_len = 0;
166 client->write_buffer = malloc(client->write_buffer_size);
167 if (!client->write_buffer) {
168 sway_log(L_ERROR, "Unable to allocate ipc client write buffer");
169 close(client_fd);
170 return 0;
171 }
172
173 sway_log(L_DEBUG, "New client: fd %d", client_fd);
174 list_add(ipc_client_list, client);
175 return 0;
176}
177
178static const int ipc_header_size = sizeof(ipc_magic) + 8;
179
180int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
181 struct ipc_client *client = data;
182
183 if (mask & WL_EVENT_ERROR) {
184 sway_log(L_ERROR, "IPC Client socket error, removing client");
185 ipc_client_disconnect(client);
186 return 0;
187 }
188
189 if (mask & WL_EVENT_HANGUP) {
190 sway_log(L_DEBUG, "Client %d hung up", client->fd);
191 ipc_client_disconnect(client);
192 return 0;
193 }
194
195 sway_log(L_DEBUG, "Client %d readable", client->fd);
196
197 int read_available;
198 if (ioctl(client_fd, FIONREAD, &read_available) == -1) {
199 sway_log_errno(L_INFO, "Unable to read IPC socket buffer size");
200 ipc_client_disconnect(client);
201 return 0;
202 }
203
204 // Wait for the rest of the command payload in case the header has already been read
205 if (client->payload_length > 0) {
206 if ((uint32_t)read_available >= client->payload_length) {
207 ipc_client_handle_command(client);
208 }
209 return 0;
210 }
211
212 if (read_available < ipc_header_size) {
213 return 0;
214 }
215
216 uint8_t buf[ipc_header_size];
217 uint32_t *buf32 = (uint32_t*)(buf + sizeof(ipc_magic));
218 // Should be fully available, because read_available >= ipc_header_size
219 ssize_t received = recv(client_fd, buf, ipc_header_size, 0);
220 if (received == -1) {
221 sway_log_errno(L_INFO, "Unable to receive header from IPC client");
222 ipc_client_disconnect(client);
223 return 0;
224 }
225
226 if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) {
227 sway_log(L_DEBUG, "IPC header check failed");
228 ipc_client_disconnect(client);
229 return 0;
230 }
231
232 client->payload_length = buf32[0];
233 client->current_command = (enum ipc_command_type)buf32[1];
234
235 if (read_available - received >= (long)client->payload_length) {
236 ipc_client_handle_command(client);
237 }
238
239 return 0;
240}
241
242int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
243 struct ipc_client *client = data;
244
245 if (mask & WL_EVENT_ERROR) {
246 sway_log(L_ERROR, "IPC Client socket error, removing client");
247 ipc_client_disconnect(client);
248 return 0;
249 }
250
251 if (mask & WL_EVENT_HANGUP) {
252 sway_log(L_DEBUG, "Client %d hung up", client->fd);
253 ipc_client_disconnect(client);
254 return 0;
255 }
256
257 if (client->write_buffer_len <= 0) {
258 return 0;
259 }
260
261 sway_log(L_DEBUG, "Client %d writable", client->fd);
262
263 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len);
264
265 if (written == -1 && errno == EAGAIN) {
266 return 0;
267 } else if (written == -1) {
268 sway_log_errno(L_INFO, "Unable to send data from queue to IPC client");
269 ipc_client_disconnect(client);
270 return 0;
271 }
272
273 memmove(client->write_buffer, client->write_buffer + written, client->write_buffer_len - written);
274 client->write_buffer_len -= written;
275
276 if (client->write_buffer_len == 0 && client->writable_event_source) {
277 wl_event_source_remove(client->writable_event_source);
278 client->writable_event_source = NULL;
279 }
280
281 return 0;
282}
283
284void ipc_client_disconnect(struct ipc_client *client) {
285 if (!sway_assert(client != NULL, "client != NULL")) {
286 return;
287 }
288
289 if (client->fd != -1) {
290 shutdown(client->fd, SHUT_RDWR);
291 }
292
293 sway_log(L_INFO, "IPC Client %d disconnected", client->fd);
294 wl_event_source_remove(client->event_source);
295 if (client->writable_event_source) {
296 wl_event_source_remove(client->writable_event_source);
297 }
298 int i = 0;
299 while (i < ipc_client_list->length && ipc_client_list->items[i] != client) {
300 i++;
301 }
302 list_del(ipc_client_list, i);
303 free(client->write_buffer);
304 close(client->fd);
305 free(client);
306}
307
308void ipc_client_handle_command(struct ipc_client *client) {
309 if (!sway_assert(client != NULL, "client != NULL")) {
310 return;
311 }
312
313 char *buf = malloc(client->payload_length + 1);
314 if (!buf) {
315 sway_log_errno(L_INFO, "Unable to allocate IPC payload");
316 ipc_client_disconnect(client);
317 return;
318 }
319 if (client->payload_length > 0) {
320 // Payload should be fully available
321 ssize_t received = recv(client->fd, buf, client->payload_length, 0);
322 if (received == -1)
323 {
324 sway_log_errno(L_INFO, "Unable to receive payload from IPC client");
325 ipc_client_disconnect(client);
326 free(buf);
327 return;
328 }
329 }
330 buf[client->payload_length] = '\0';
331
332 const char *error_denied = "{ \"success\": false, \"error\": \"Permission denied\" }";
333
334 switch (client->current_command) {
335 case IPC_COMMAND:
336 {
337 struct cmd_results *results = handle_command(buf);
338 const char *json = cmd_results_to_json(results);
339 char reply[256];
340 int length = snprintf(reply, sizeof(reply), "%s", json);
341 ipc_send_reply(client, reply, (uint32_t) length);
342 free_cmd_results(results);
343 goto exit_cleanup;
344 }
345
346 case IPC_GET_VERSION:
347 {
348 json_object *version = ipc_json_get_version();
349 const char *json_string = json_object_to_json_string(version);
350 ipc_send_reply(client, json_string, (uint32_t)strlen(json_string));
351 json_object_put(version); // free
352 goto exit_cleanup;
353 }
354
355 default:
356 sway_log(L_INFO, "Unknown IPC command type %i", client->current_command);
357 goto exit_cleanup;
358 }
359
360 ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
361 sway_log(L_DEBUG, "Denied IPC client access to %i", client->current_command);
362
363exit_cleanup:
364 client->payload_length = 0;
365 free(buf);
366 return;
367}
368
369bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length) {
370 assert(payload);
371
372 char data[ipc_header_size];
373 uint32_t *data32 = (uint32_t*)(data + sizeof(ipc_magic));
374
375 memcpy(data, ipc_magic, sizeof(ipc_magic));
376 data32[0] = payload_length;
377 data32[1] = client->current_command;
378
379 while (client->write_buffer_len + ipc_header_size + payload_length >=
380 client->write_buffer_size) {
381 client->write_buffer_size *= 2;
382 }
383
384 if (client->write_buffer_size > 4e6) { // 4 MB
385 sway_log(L_ERROR, "Client write buffer too big, disconnecting client");
386 ipc_client_disconnect(client);
387 return false;
388 }
389
390 char *new_buffer = realloc(client->write_buffer, client->write_buffer_size);
391 if (!new_buffer) {
392 sway_log(L_ERROR, "Unable to reallocate ipc client write buffer");
393 ipc_client_disconnect(client);
394 return false;
395 }
396 client->write_buffer = new_buffer;
397
398 memcpy(client->write_buffer + client->write_buffer_len, data, ipc_header_size);
399 client->write_buffer_len += ipc_header_size;
400 memcpy(client->write_buffer + client->write_buffer_len, payload, payload_length);
401 client->write_buffer_len += payload_length;
402
403 if (!client->writable_event_source) {
404 client->writable_event_source = wl_event_loop_add_fd(
405 server.wl_event_loop, client->fd, WL_EVENT_WRITABLE,
406 ipc_client_handle_writable, client);
407 }
408
409 sway_log(L_DEBUG, "Added IPC reply to client %d queue: %s", client->fd, payload);
410 return true;
411}