1 /*
2 * Copyright © 2019 Manuel Stoeckl
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the
13 * next paragraph) shall be included in all copies or substantial
14 * portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26 #include "main.h"
27
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <inttypes.h>
31 #include <poll.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/socket.h>
36 #include <sys/un.h>
37 #include <sys/wait.h>
38 #include <unistd.h>
39
conntoken_version(uint32_t header)40 static inline uint32_t conntoken_version(uint32_t header)
41 {
42 return header >> 16;
43 }
44
check_conn_header(uint32_t header,const struct main_config * config,char * err,size_t err_size)45 static int check_conn_header(uint32_t header, const struct main_config *config,
46 char *err, size_t err_size)
47 {
48 if ((header >> 16) != WAYPIPE_PROTOCOL_VERSION) {
49 const char *endian_warning = "";
50 if ((header & CONN_FIXED_BIT) == 0 &&
51 (header & CONN_UNSET_BIT) != 0) {
52 endian_warning =
53 " It is also possible that server endianness does not match client";
54 }
55
56 snprintf(err, err_size,
57 "Waypipe client is rejecting connection header %08" PRIx32
58 "; as Waypipe server (application-side) protocol version (%u) is incompatible with Waypipe client protocol version (%u, from waypipe %s). Check that both sides have compatible versions of Waypipe.%s",
59 header, conntoken_version(header),
60 WAYPIPE_PROTOCOL_VERSION, WAYPIPE_VERSION,
61 endian_warning);
62 return -1;
63 }
64
65 /* Skip the following checks if config is null
66 * (i.e., called from reconnection loop) */
67 if (!config) {
68 return 0;
69 }
70
71 /* For now, reject mismatches in compression format and video coding
72 * setting, and print an error. Adopting whatever the server asks for
73 * is a minor security issue -- e.g., video handling is a good target
74 * for exploits, and compression can cost CPU time, especially if the
75 * initial connection mechanism were to be expanded to allow setting
76 * compression level. */
77 if ((header & CONN_COMPRESSION_MASK) == CONN_ZSTD_COMPRESSION) {
78 if (config->compression != COMP_ZSTD) {
79 snprintf(err, err_size,
80 "Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=ZSTD the Waypipe server expected",
81 compression_mode_to_str(
82 config->compression));
83 return -1;
84 }
85 } else if ((header & CONN_COMPRESSION_MASK) == CONN_LZ4_COMPRESSION) {
86 if (config->compression != COMP_LZ4) {
87 snprintf(err, err_size,
88 "Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=LZ4 the Waypipe server expected",
89 compression_mode_to_str(
90 config->compression));
91 return -1;
92 }
93 } else if ((header & CONN_COMPRESSION_MASK) == CONN_NO_COMPRESSION) {
94 if (config->compression != COMP_NONE) {
95 snprintf(err, err_size,
96 "Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=NONE the Waypipe server expected",
97 compression_mode_to_str(
98 config->compression));
99 return -1;
100 }
101 } else if ((header & CONN_COMPRESSION_MASK) != 0) {
102 snprintf(err, err_size,
103 "Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the unidentified compression type the Waypipe server expected",
104 compression_mode_to_str(config->compression));
105 return -1;
106 }
107
108 if ((header & CONN_VIDEO_MASK) == CONN_VP9_VIDEO) {
109 if (!config->video_if_possible) {
110 snprintf(err, err_size,
111 "Waypipe client is rejecting connection, Waypipe client was not run with video encoding enabled, unlike Waypipe server");
112 return -1;
113 }
114 if (config->video_fmt != VIDEO_VP9) {
115 snprintf(err, err_size,
116 "Waypipe client is rejecting connection, Waypipe client was not configured for the VP9 video coding format requested by the Waypipe server");
117 return -1;
118 }
119 } else if ((header & CONN_VIDEO_MASK) == CONN_H264_VIDEO) {
120 if (!config->video_if_possible) {
121 snprintf(err, err_size,
122 "Waypipe client is rejecting connection, Waypipe client was not run with video encoding enabled, unlike Waypipe server");
123 return -1;
124 }
125 if (config->video_fmt != VIDEO_H264) {
126 snprintf(err, err_size,
127 "Waypipe client is rejecting connection, Waypipe client was not configured for the VP9 video coding format requested by the Waypipe server");
128 return -1;
129 }
130 } else if ((header & CONN_VIDEO_MASK) == CONN_NO_VIDEO) {
131 if (config->video_if_possible) {
132 snprintf(err, err_size,
133 "Waypipe client is rejecting connection, Waypipe client has video encoding enabled, but Waypipe server does not");
134 return -1;
135 }
136 } else if ((header & CONN_VIDEO_MASK) != 0) {
137 snprintf(err, err_size,
138 "Waypipe client is rejecting connection, Waypipe client was not configured for the unidentified video coding format requested by the Waypipe server");
139 return -1;
140 }
141
142 return 0;
143 }
apply_conn_header(uint32_t header,struct main_config * config)144 static void apply_conn_header(uint32_t header, struct main_config *config)
145 {
146 if (header & CONN_NO_DMABUF_SUPPORT) {
147 if (config) {
148 config->no_gpu = true;
149 }
150 }
151 // todo: consider allowing to disable video encoding
152 }
153
write_rejection_message(int channel_fd,char * msg)154 static void write_rejection_message(int channel_fd, char *msg)
155 {
156 char buf[512];
157 size_t len = print_wrapped_error(buf, sizeof(buf), msg);
158 if (!len) {
159 wp_error("Failed to print wrapped error for message of length %zu, not enough space",
160 strlen(msg));
161 return;
162 }
163 ssize_t written = write(channel_fd, buf, len);
164 if (written != (ssize_t)len) {
165 wp_error("Failed to send rejection message, only %d bytes of %d written",
166 (int)written, (int)len);
167 }
168 }
169
key_match(const uint32_t key1[static3],const uint32_t key2[static3])170 static inline bool key_match(
171 const uint32_t key1[static 3], const uint32_t key2[static 3])
172 {
173 return key1[0] == key2[0] && key1[1] == key2[1] && key1[2] == key2[2];
174 }
get_inherited_socket(const char * wayland_socket)175 static int get_inherited_socket(const char *wayland_socket)
176 {
177 uint32_t val;
178 if (parse_uint32(wayland_socket, &val) == -1 || ((int)val) < 0) {
179 wp_error("Failed to parse \"%s\" (value of WAYLAND_SOCKET) as a nonnegative integer, exiting",
180 wayland_socket);
181 return -1;
182 }
183 int fd = (int)val;
184 int flags = fcntl(fd, F_GETFL, 0);
185 if (flags == -1 && errno == EBADF) {
186 wp_error("The file descriptor WAYLAND_SOCKET=%d was invalid, exiting",
187 fd);
188 return -1;
189 }
190 return fd;
191 }
192
get_display_path(struct sockaddr_un * addr)193 static int get_display_path(struct sockaddr_un *addr)
194 {
195 const char *display = getenv("WAYLAND_DISPLAY");
196 if (!display) {
197 wp_error("WAYLAND_DISPLAY is not set, exiting");
198 return -1;
199 }
200 if (display[0] != '/') {
201 const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
202 if (!xdg_runtime_dir) {
203 wp_error("XDG_RUNTIME_DIR is not set, exiting");
204 return -1;
205 }
206 if (strlen(display) + 1 + strlen(xdg_runtime_dir) >=
207 sizeof(addr->sun_path)) {
208 wp_error("The Wayland socket path '%s/%s' is too long (%zu + 1 + %zu bytes >= %zu)",
209 xdg_runtime_dir, display,
210 strlen(xdg_runtime_dir),
211 strlen(display),
212 sizeof(addr->sun_path) - 1);
213 return -1;
214 }
215
216 multi_strcat(addr->sun_path, sizeof(addr->sun_path),
217 xdg_runtime_dir, "/", display, NULL);
218 } else {
219 if (strlen(display) >= sizeof(addr->sun_path)) {
220 wp_error("WAYLAND_DISPLAY='%s' is longer than %zu bytes (max socket path length), exiting",
221 display, sizeof(addr->sun_path) - 1);
222 return -1;
223 }
224 strcpy(addr->sun_path, display);
225 }
226 return 0;
227 }
228
run_single_client_reconnector(int channelsock,int linkfd,struct connection_token conn_id)229 static int run_single_client_reconnector(
230 int channelsock, int linkfd, struct connection_token conn_id)
231 {
232 int retcode = EXIT_SUCCESS;
233 while (!shutdown_flag) {
234 struct pollfd pf[2];
235 pf[0].fd = channelsock;
236 pf[0].events = POLLIN;
237 pf[0].revents = 0;
238 pf[1].fd = linkfd;
239 pf[1].events = 0;
240 pf[1].revents = 0;
241
242 int r = poll(pf, 2, -1);
243 if (r == -1 && errno == EINTR) {
244 continue;
245 } else if (r == -1) {
246 retcode = EXIT_FAILURE;
247 break;
248 } else if (r == 0) {
249 // Nothing to read
250 continue;
251 }
252
253 if (pf[1].revents & POLLHUP) {
254 /* Hang up, main thread has closed its link */
255 break;
256 }
257 if (!(pf[0].revents & POLLIN)) {
258 continue;
259 }
260 int newclient = accept(channelsock, NULL, NULL);
261 if (newclient == -1) {
262 if (errno == EAGAIN || errno == EWOULDBLOCK) {
263 // The wakeup may have been spurious
264 continue;
265 }
266 wp_error("Connection failure: %s", strerror(errno));
267 retcode = EXIT_FAILURE;
268 break;
269 }
270 wp_debug("Reconnection to oneshot client");
271
272 struct connection_token new_conn;
273 memset(&new_conn, 0, sizeof(new_conn));
274 if (read(newclient, &new_conn.header,
275 sizeof(new_conn.header)) !=
276 sizeof(new_conn.header)) {
277 wp_error("Failed to get connection id header");
278 goto done;
279 }
280 if (check_conn_header(new_conn.header, NULL, NULL, 0) < 0) {
281 goto done;
282 }
283 if (read(newclient, &new_conn.key, sizeof(new_conn.key)) !=
284 sizeof(new_conn.key)) {
285 wp_error("Failed to get connection id key");
286 goto done;
287 }
288 if (!key_match(new_conn.key, conn_id.key)) {
289 wp_error("Connection attempt with unmatched key");
290 goto done;
291 }
292 bool update = new_conn.header & CONN_RECONNECTABLE_BIT;
293 if (!update) {
294 wp_error("Connection token is missing update flag");
295 goto done;
296 }
297 if (send_one_fd(linkfd, newclient) == -1) {
298 wp_error("Failed to get connection id");
299 retcode = EXIT_FAILURE;
300 checked_close(newclient);
301 break;
302 }
303 done:
304 checked_close(newclient);
305 }
306 checked_close(channelsock);
307 checked_close(linkfd);
308 return retcode;
309 }
310
run_single_client(int channelsock,pid_t * eol_pid,const struct main_config * config,int disp_fd)311 static int run_single_client(int channelsock, pid_t *eol_pid,
312 const struct main_config *config, int disp_fd)
313 {
314 /* To support reconnection attempts, this mode creates a child
315 * reconnection watcher process, linked via socketpair */
316 int retcode = EXIT_SUCCESS;
317 int chanclient = -1;
318 struct connection_token conn_id;
319 memset(&conn_id, 0, sizeof(conn_id));
320 while (!shutdown_flag) {
321 int status = -1;
322 if (wait_for_pid_and_clean(eol_pid, &status, WNOHANG, NULL)) {
323 eol_pid = 0; // < in case eol_pid is recycled
324
325 wp_debug("Child (ssh) died, exiting");
326 // Copy the exit code
327 retcode = WEXITSTATUS(status);
328 break;
329 }
330
331 struct pollfd cs;
332 cs.fd = channelsock;
333 cs.events = POLLIN;
334 cs.revents = 0;
335 int r = poll(&cs, 1, -1);
336 if (r == -1) {
337 if (errno == EINTR) {
338 // If SIGCHLD, we will check the child.
339 // If SIGINT, the loop ends
340 continue;
341 }
342 retcode = EXIT_FAILURE;
343 break;
344 } else if (r == 0) {
345 // Nothing to read
346 continue;
347 }
348
349 chanclient = accept(channelsock, NULL, NULL);
350 if (chanclient == -1) {
351 if (errno == EAGAIN || errno == EWOULDBLOCK) {
352 // The wakeup may have been spurious
353 continue;
354 }
355 wp_error("Connection failure: %s", strerror(errno));
356 retcode = EXIT_FAILURE;
357 break;
358 }
359
360 char err_msg[512];
361
362 wp_debug("New connection to client");
363 if (read(chanclient, &conn_id.header, sizeof(conn_id.header)) !=
364 sizeof(conn_id.header)) {
365 wp_error("Failed to get connection id header");
366 goto fail_cc;
367 }
368
369 if (check_conn_header(conn_id.header, config, err_msg,
370 sizeof(err_msg)) < 0) {
371 wp_error("%s", err_msg);
372 write_rejection_message(chanclient, err_msg);
373 goto fail_cc;
374 }
375 if (read(chanclient, &conn_id.key, sizeof(conn_id.key)) !=
376 sizeof(conn_id.key)) {
377 wp_error("Failed to get connection id key");
378 goto fail_cc;
379 }
380 break;
381 fail_cc:
382 retcode = EXIT_FAILURE;
383 checked_close(chanclient);
384 chanclient = -1;
385 break;
386 }
387 if (retcode == EXIT_FAILURE || shutdown_flag || chanclient == -1) {
388 checked_close(channelsock);
389 checked_close(disp_fd);
390 return retcode;
391 }
392 if (conn_id.header & CONN_UPDATE_BIT) {
393 wp_error("Initial connection token had update flag set");
394 checked_close(channelsock);
395 checked_close(disp_fd);
396 return retcode;
397 }
398
399 /* Fork a reconnection handler, only if the connection is
400 * reconnectable/has a nonzero id */
401 int linkfds[2] = {-1, -1};
402 if (conn_id.header & CONN_RECONNECTABLE_BIT) {
403 if (socketpair(AF_UNIX, SOCK_STREAM, 0, linkfds) == -1) {
404 wp_error("Failed to create socketpair: %s",
405 strerror(errno));
406 checked_close(chanclient);
407 return EXIT_FAILURE;
408 }
409
410 pid_t reco_pid = fork();
411 if (reco_pid == -1) {
412 wp_error("Fork failure: %s", strerror(errno));
413 checked_close(chanclient);
414 return EXIT_FAILURE;
415 } else if (reco_pid == 0) {
416 if (linkfds[0] != -1) {
417 checked_close(linkfds[0]);
418 }
419 checked_close(chanclient);
420 checked_close(disp_fd);
421 int rc = run_single_client_reconnector(
422 channelsock, linkfds[1], conn_id);
423 exit(rc);
424 }
425 checked_close(linkfds[1]);
426 }
427 checked_close(channelsock);
428
429 struct main_config mod_config = *config;
430 apply_conn_header(conn_id.header, &mod_config);
431 return main_interface_loop(
432 chanclient, disp_fd, linkfds[0], &mod_config, true);
433 }
434
send_new_connection_fd(struct conn_map * connmap,uint32_t key[static3],int new_fd)435 void send_new_connection_fd(
436 struct conn_map *connmap, uint32_t key[static 3], int new_fd)
437 {
438 for (int i = 0; i < connmap->count; i++) {
439 if (key_match(connmap->data[i].token.key, key)) {
440 if (send_one_fd(connmap->data[i].linkfd, new_fd) ==
441 -1) {
442 wp_error("Failed to send new connection fd to subprocess: %s",
443 strerror(errno));
444 }
445 break;
446 }
447 }
448 }
449
handle_new_client_connection(struct pollfd * other_fds,int n_other_fds,int chanclient,struct conn_map * connmap,const struct main_config * config,const struct sockaddr_un * disp_addr,const struct connection_token * conn_id)450 static void handle_new_client_connection(struct pollfd *other_fds,
451 int n_other_fds, int chanclient, struct conn_map *connmap,
452 const struct main_config *config,
453 const struct sockaddr_un *disp_addr,
454 const struct connection_token *conn_id)
455 {
456 bool reconnectable = conn_id->header & CONN_RECONNECTABLE_BIT;
457
458 if (reconnectable && buf_ensure_size(connmap->count + 1,
459 sizeof(struct conn_addr),
460 &connmap->size,
461 (void **)&connmap->data) == -1) {
462 wp_error("Failed to allocate space to track connection");
463 goto fail_cc;
464 }
465 int linkfds[2] = {-1, -1};
466 if (reconnectable) {
467 if (socketpair(AF_UNIX, SOCK_STREAM, 0, linkfds) == -1) {
468 wp_error("Failed to create socketpair: %s",
469 strerror(errno));
470 goto fail_cc;
471 }
472 }
473 pid_t npid = fork();
474 if (npid == 0) {
475 // Run forked process, with the only shared
476 // state being the new channel socket
477 for (int i = 0; i < n_other_fds; i++) {
478 if (other_fds[i].fd != chanclient) {
479 checked_close(other_fds[i].fd);
480 }
481 }
482 if (reconnectable) {
483 checked_close(linkfds[0]);
484 }
485 for (int i = 0; i < connmap->count; i++) {
486 checked_close(connmap->data[i].linkfd);
487 }
488
489 int dfd = connect_to_socket(disp_addr);
490 if (dfd == -1) {
491 exit(EXIT_FAILURE);
492 }
493
494 struct main_config mod_config = *config;
495 apply_conn_header(conn_id->header, &mod_config);
496 int rc = main_interface_loop(
497 chanclient, dfd, linkfds[1], &mod_config, true);
498 check_unclosed_fds();
499 exit(rc);
500 } else if (npid == -1) {
501 wp_error("Fork failure: %s", strerror(errno));
502 goto fail_ps;
503 }
504 // Remove connection from this process
505
506 if (reconnectable) {
507 checked_close(linkfds[1]);
508 connmap->data[connmap->count++] =
509 (struct conn_addr){.linkfd = linkfds[0],
510 .token = *conn_id,
511 .pid = npid};
512 }
513
514 return;
515 fail_ps:
516 checked_close(linkfds[0]);
517 fail_cc:
518 checked_close(chanclient);
519 return;
520 }
521 #define NUM_INCOMPLETE_CONNECTIONS 63
522
drop_incoming_connection(struct pollfd * fds,struct connection_token * tokens,uint8_t * bytes_read,int index,int incomplete)523 static void drop_incoming_connection(struct pollfd *fds,
524 struct connection_token *tokens, uint8_t *bytes_read, int index,
525 int incomplete)
526 {
527 checked_close(fds[index].fd);
528 if (index != incomplete - 1) {
529 size_t shift = (size_t)(incomplete - 1 - index);
530 memmove(fds + index, fds + index + 1,
531 sizeof(struct pollfd) * shift);
532 memmove(tokens + index, tokens + index + 1,
533 sizeof(struct connection_token) * shift);
534 memmove(bytes_read + index, bytes_read + index + 1,
535 sizeof(uint8_t) * shift);
536 }
537 memset(&fds[incomplete - 1], 0, sizeof(struct pollfd));
538 memset(&tokens[incomplete - 1], 0, sizeof(struct connection_token));
539 bytes_read[incomplete - 1] = 0;
540 }
541
run_multi_client(int channelsock,pid_t * eol_pid,const struct main_config * config,const struct sockaddr_un * disp_addr)542 static int run_multi_client(int channelsock, pid_t *eol_pid,
543 const struct main_config *config,
544 const struct sockaddr_un *disp_addr)
545 {
546 struct conn_map connmap = {.data = NULL, .count = 0, .size = 0};
547
548 /* Keep track of the main socket, and all connections which have not
549 * yet fully provided their connection token. If we run out of space,
550 * the oldest incomplete connection gets dropped */
551 struct pollfd fds[NUM_INCOMPLETE_CONNECTIONS + 1];
552 struct connection_token tokens[NUM_INCOMPLETE_CONNECTIONS];
553 uint8_t bytes_read[NUM_INCOMPLETE_CONNECTIONS];
554 int incomplete = 0;
555 memset(fds, 0, sizeof(fds));
556 memset(tokens, 0, sizeof(tokens));
557 memset(bytes_read, 0, sizeof(bytes_read));
558 fds[0].fd = channelsock;
559 fds[0].events = POLLIN;
560 fds[0].revents = 0;
561
562 int retcode = EXIT_SUCCESS;
563 while (!shutdown_flag) {
564 int status = -1;
565 if (wait_for_pid_and_clean(
566 eol_pid, &status, WNOHANG, &connmap)) {
567 wp_debug("Child (ssh) died, exiting");
568 // Copy the exit code
569 retcode = WEXITSTATUS(status);
570 break;
571 }
572
573 int r = poll(fds, 1 + (nfds_t)incomplete, -1);
574 if (r == -1) {
575 if (errno == EINTR) {
576 // If SIGCHLD, we will check the child.
577 // If SIGINT, the loop ends
578 continue;
579 }
580 retcode = EXIT_FAILURE;
581 break;
582 } else if (r == 0) {
583 // Nothing to read
584 continue;
585 }
586
587 for (int i = 0; i < incomplete; i++) {
588 if (!(fds[i + 1].revents & POLLIN)) {
589 continue;
590 }
591 int cur_fd = fds[i + 1].fd;
592 char *dest = ((char *)&tokens[i]) + bytes_read[i];
593 ssize_t s = read(cur_fd, dest, 16 - bytes_read[i]);
594 if (s == -1) {
595 wp_error("Failed to read from connection: %s",
596 strerror(errno));
597 drop_incoming_connection(fds + 1, tokens,
598 bytes_read, i, incomplete);
599 incomplete--;
600 continue;
601 } else if (s == 0) {
602 /* connection closed */
603 wp_error("Connection closed early");
604 drop_incoming_connection(fds + 1, tokens,
605 bytes_read, i, incomplete);
606 incomplete--;
607 continue;
608 }
609 bytes_read[i] += (uint8_t)s;
610 if (bytes_read[i] - (uint8_t)s < 4 &&
611 bytes_read[i] >= 4) {
612 char err_msg[512];
613 /* Validate connection token header */
614 if (check_conn_header(tokens[i].header, config,
615 err_msg,
616 sizeof(err_msg)) < 0) {
617 wp_error("%s", err_msg);
618 write_rejection_message(
619 cur_fd, err_msg);
620 drop_incoming_connection(fds + 1,
621 tokens, bytes_read, i,
622 incomplete);
623 incomplete--;
624 continue;
625 }
626 }
627 if (bytes_read[i] < 16) {
628 continue;
629 }
630 /* Validate connection token key */
631 if (tokens[i].header & CONN_UPDATE_BIT) {
632 send_new_connection_fd(&connmap, tokens[i].key,
633 cur_fd);
634 drop_incoming_connection(fds + 1, tokens,
635 bytes_read, i, incomplete);
636 incomplete--;
637 continue;
638 }
639
640 /* Failures here are logged, but should not
641 * affect this process' ability to e.g. handle
642 * reconnections. */
643 handle_new_client_connection(fds, 1 + incomplete,
644 cur_fd, &connmap, config, disp_addr,
645 &tokens[i]);
646 drop_incoming_connection(fds + 1, tokens, bytes_read, i,
647 incomplete);
648 incomplete--;
649 }
650
651 /* Process new connections second, to give incomplete
652 * connections a chance to clear first */
653 if (fds[0].revents & POLLIN) {
654 int chanclient = accept(channelsock, NULL, NULL);
655 if (chanclient == -1) {
656 if (errno == EAGAIN || errno == EWOULDBLOCK) {
657 // The wakeup may have been spurious
658 continue;
659 }
660 // should errors like econnaborted exit?
661 wp_error("Connection failure: %s",
662 strerror(errno));
663 retcode = EXIT_FAILURE;
664 break;
665 }
666
667 wp_debug("New connection to client");
668 if (set_nonblocking(chanclient) == -1) {
669 wp_error("Error making new connection nonblocking: %s",
670 strerror(errno));
671 checked_close(chanclient);
672 continue;
673 }
674
675 if (incomplete == NUM_INCOMPLETE_CONNECTIONS) {
676 wp_error("Dropping oldest incomplete connection (out of %d)",
677 NUM_INCOMPLETE_CONNECTIONS);
678 drop_incoming_connection(fds + 1, tokens,
679 bytes_read, 0, incomplete);
680 incomplete--;
681 }
682 fds[1 + incomplete].fd = chanclient;
683 fds[1 + incomplete].events = POLLIN;
684 fds[1 + incomplete].revents = 0;
685 memset(&tokens[incomplete], 0,
686 sizeof(struct connection_token));
687 bytes_read[incomplete] = 0;
688 incomplete++;
689 }
690 }
691 for (int i = 0; i < incomplete; i++) {
692 checked_close(fds[i + 1].fd);
693 }
694
695 for (int i = 0; i < connmap.count; i++) {
696 checked_close(connmap.data[i].linkfd);
697 }
698 free(connmap.data);
699 checked_close(channelsock);
700 return retcode;
701 }
702
run_client(const struct sockaddr_un * socket_addr,const struct main_config * config,bool oneshot,const char * wayland_socket,pid_t eol_pid,int channelsock)703 int run_client(const struct sockaddr_un *socket_addr,
704 const struct main_config *config, bool oneshot,
705 const char *wayland_socket, pid_t eol_pid, int channelsock)
706 {
707 wp_debug("I'm a client listening on %s", socket_addr->sun_path);
708 wp_debug("version: %s", WAYPIPE_VERSION);
709
710 /* Connect to Wayland display. We don't use the wayland-client
711 * function here, because its errors aren't immediately useful,
712 * and older Wayland versions have edge cases */
713 int dispfd = -1;
714 struct sockaddr_un disp_addr;
715 memset(&disp_addr, 0, sizeof(disp_addr));
716
717 if (wayland_socket) {
718 dispfd = get_inherited_socket(wayland_socket);
719 if (dispfd == -1) {
720 if (eol_pid) {
721 waitpid(eol_pid, NULL, 0);
722 }
723 close(channelsock);
724 unlink(socket_addr->sun_path);
725 return EXIT_FAILURE;
726 }
727 /* This socket is inherited and meant to be closed by Waypipe */
728 if (dispfd >= 0 && dispfd < 256) {
729 inherited_fds[dispfd / 64] &= ~(1uLL << (dispfd % 64));
730 }
731 } else {
732 if (get_display_path(&disp_addr) == -1) {
733 if (eol_pid) {
734 waitpid(eol_pid, NULL, 0);
735 }
736 close(channelsock);
737 unlink(socket_addr->sun_path);
738 return EXIT_FAILURE;
739 }
740 }
741
742 if (oneshot) {
743 if (!wayland_socket) {
744 dispfd = connect_to_socket(&disp_addr);
745 }
746 } else {
747 int test_conn = connect_to_socket(&disp_addr);
748 if (test_conn == -1) {
749 if (eol_pid) {
750 waitpid(eol_pid, NULL, 0);
751 }
752 close(channelsock);
753 unlink(socket_addr->sun_path);
754 return EXIT_FAILURE;
755 }
756 checked_close(test_conn);
757 }
758 wp_debug("A wayland compositor is available. Proceeding.");
759
760 /* These handlers close the channelsock and dispfd */
761 int retcode;
762 if (oneshot) {
763 retcode = run_single_client(
764 channelsock, &eol_pid, config, dispfd);
765 } else {
766 retcode = run_multi_client(
767 channelsock, &eol_pid, config, &disp_addr);
768 }
769 unlink(socket_addr->sun_path);
770 int cleanup_type = shutdown_flag ? WNOHANG : 0;
771
772 int status = -1;
773 // Don't return until all child processes complete
774 if (wait_for_pid_and_clean(&eol_pid, &status, cleanup_type, NULL)) {
775 retcode = WEXITSTATUS(status);
776 }
777 return retcode;
778 }
779