1 /* ide-pty-intercept.c
2 *
3 * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #ifndef _GNU_SOURCE
22 # define _GNU_SOURCE
23 #endif
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <glib-unix.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/ioctl.h>
35 #include <termios.h>
36 #include <unistd.h>
37
38 #include "ide-pty-intercept.h"
39
40 /*
41 * We really don't need all that much. A PTY on Linux has a some amount of
42 * kernel memory that is non-pageable and therefore small in size. 4k is what
43 * it appears to be. Anything more than that is really just an opportunity for
44 * us to break some deadlock scenarios.
45 */
46 #define CHANNEL_BUFFER_SIZE (4096 * 4)
47 #define SLAVE_READ_PRIORITY G_PRIORITY_HIGH
48 #define SLAVE_WRITE_PRIORITY G_PRIORITY_DEFAULT_IDLE
49 #define MASTER_READ_PRIORITY G_PRIORITY_DEFAULT_IDLE
50 #define MASTER_WRITE_PRIORITY G_PRIORITY_HIGH
51
52 static void _ide_pty_intercept_side_close (IdePtyInterceptSide *side);
53 static gboolean _ide_pty_intercept_in_cb (GIOChannel *channel,
54 GIOCondition condition,
55 gpointer user_data);
56 static gboolean _ide_pty_intercept_out_cb (GIOChannel *channel,
57 GIOCondition condition,
58 gpointer user_data);
59 static void clear_source (guint *source_id);
60
61 static gboolean
_ide_pty_intercept_set_raw(IdePtyFd fd)62 _ide_pty_intercept_set_raw (IdePtyFd fd)
63 {
64 struct termios t;
65
66 if (tcgetattr (fd, &t) == -1)
67 return FALSE;
68
69 t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
70 t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK);
71 t.c_oflag &= ~(OPOST);
72 t.c_cc[VMIN] = 1;
73 t.c_cc[VTIME] = 0;
74
75 if (tcsetattr (fd, TCSAFLUSH, &t) == -1)
76 return FALSE;
77
78 return TRUE;
79 }
80
81 /**
82 * ide_pty_intercept_create_slave:
83 * @master_fd: a pty master
84 * @blocking: use %FALSE to set O_NONBLOCK
85 *
86 * This creates a new slave to the PTY master @master_fd.
87 *
88 * This uses grantpt(), unlockpt(), and ptsname() to open a new
89 * PTY slave.
90 *
91 * Returns: a FD for the slave PTY that should be closed with close().
92 * Upon error, %IDE_PTY_FD_INVALID (-1) is returned.
93 *
94 * Since: 3.32
95 */
96 IdePtyFd
ide_pty_intercept_create_slave(IdePtyFd master_fd,gboolean blocking)97 ide_pty_intercept_create_slave (IdePtyFd master_fd,
98 gboolean blocking)
99 {
100 g_auto(IdePtyFd) ret = IDE_PTY_FD_INVALID;
101 gint extra = blocking ? 0 : O_NONBLOCK;
102 #if defined(HAVE_PTSNAME_R) || defined(__FreeBSD__) || defined(__DragonFly__)
103 char name[256];
104 #else
105 const char *name;
106 #endif
107
108 g_assert (master_fd != -1);
109
110 if (grantpt (master_fd) != 0)
111 return IDE_PTY_FD_INVALID;
112
113 if (unlockpt (master_fd) != 0)
114 return IDE_PTY_FD_INVALID;
115
116 #ifdef HAVE_PTSNAME_R
117 if (ptsname_r (master_fd, name, sizeof name - 1) != 0)
118 return IDE_PTY_FD_INVALID;
119 name[sizeof name - 1] = '\0';
120 #elif defined(__FreeBSD__) || defined(__DragonFly__)
121 if (fdevname_r (master_fd, name + 5, sizeof name - 6) == NULL)
122 return IDE_PTY_FD_INVALID;
123 memcpy (name, "/dev/", 5);
124 name[sizeof name - 1] = '\0';
125 #else
126 if (NULL == (name = ptsname (master_fd)))
127 return IDE_PTY_FD_INVALID;
128 #endif
129
130 ret = open (name, O_NOCTTY | O_RDWR | O_CLOEXEC | extra);
131
132 if (ret == IDE_PTY_FD_INVALID && errno == EINVAL)
133 {
134 gint flags;
135
136 ret = open (name, O_NOCTTY | O_RDWR | O_CLOEXEC);
137 if (ret == IDE_PTY_FD_INVALID && errno == EINVAL)
138 ret = open (name, O_NOCTTY | O_RDWR);
139
140 if (ret == IDE_PTY_FD_INVALID)
141 return IDE_PTY_FD_INVALID;
142
143 /* Add FD_CLOEXEC if O_CLOEXEC failed */
144 flags = fcntl (ret, F_GETFD, 0);
145 if ((flags & FD_CLOEXEC) == 0)
146 {
147 if (fcntl (ret, F_SETFD, flags | FD_CLOEXEC) < 0)
148 return IDE_PTY_FD_INVALID;
149 }
150
151 if (!blocking)
152 {
153 if (!g_unix_set_fd_nonblocking (ret, TRUE, NULL))
154 return IDE_PTY_FD_INVALID;
155 }
156 }
157
158 return pty_fd_steal (&ret);
159 }
160
161 /**
162 * ide_pty_intercept_create_master:
163 *
164 * Creates a new PTY master using posix_openpt(). Some fallbacks are
165 * provided for non-Linux systems where O_CLOEXEC and O_NONBLOCK may
166 * not be supported.
167 *
168 * Returns: a FD that should be closed with close() if successful.
169 * Upon error, %IDE_PTY_FD_INVALID (-1) is returned.
170 *
171 * Since: 3.32
172 */
173 IdePtyFd
ide_pty_intercept_create_master(void)174 ide_pty_intercept_create_master (void)
175 {
176 g_auto(IdePtyFd) master_fd = IDE_PTY_FD_INVALID;
177
178 master_fd = posix_openpt (O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
179
180 #ifndef __linux__
181 /* Fallback for operating systems that don't support
182 * O_NONBLOCK and O_CLOEXEC when opening.
183 */
184 if (master_fd == IDE_PTY_FD_INVALID && errno == EINVAL)
185 {
186 master_fd = posix_openpt (O_RDWR | O_NOCTTY | O_CLOEXEC);
187
188 if (master_fd == IDE_PTY_FD_INVALID && errno == EINVAL)
189 {
190 gint flags;
191
192 master_fd = posix_openpt (O_RDWR | O_NOCTTY);
193 if (master_fd == -1)
194 return IDE_PTY_FD_INVALID;
195
196 flags = fcntl (master_fd, F_GETFD, 0);
197 if (flags < 0)
198 return IDE_PTY_FD_INVALID;
199
200 if (fcntl (master_fd, F_SETFD, flags | FD_CLOEXEC) < 0)
201 return IDE_PTY_FD_INVALID;
202 }
203
204 if (!g_unix_set_fd_nonblocking (master_fd, TRUE, NULL))
205 return IDE_PTY_FD_INVALID;
206 }
207 #endif
208
209 return pty_fd_steal (&master_fd);
210 }
211
212 static void
clear_source(guint * source_id)213 clear_source (guint *source_id)
214 {
215 guint id = *source_id;
216 *source_id = 0;
217 if (id != 0)
218 g_source_remove (id);
219 }
220
221 static void
_ide_pty_intercept_side_close(IdePtyInterceptSide * side)222 _ide_pty_intercept_side_close (IdePtyInterceptSide *side)
223 {
224 g_assert (side != NULL);
225
226 clear_source (&side->in_watch);
227 clear_source (&side->out_watch);
228 g_clear_pointer (&side->channel, g_io_channel_unref);
229 g_clear_pointer (&side->out_bytes, g_bytes_unref);
230 }
231
232 static gboolean
_ide_pty_intercept_out_cb(GIOChannel * channel,GIOCondition condition,gpointer user_data)233 _ide_pty_intercept_out_cb (GIOChannel *channel,
234 GIOCondition condition,
235 gpointer user_data)
236 {
237 IdePtyIntercept *self = user_data;
238 IdePtyInterceptSide *us, *them;
239 GIOStatus status;
240 const gchar *wrbuf;
241 gsize n_written = 0;
242 gsize len = 0;
243
244 g_assert (channel != NULL);
245 g_assert (condition & (G_IO_ERR | G_IO_HUP | G_IO_OUT));
246
247 if (channel == self->master.channel)
248 {
249 us = &self->master;
250 them = &self->slave;
251 }
252 else
253 {
254 us = &self->slave;
255 them = &self->master;
256 }
257
258 if ((condition & G_IO_OUT) == 0 ||
259 us->out_bytes == NULL ||
260 us->channel == NULL ||
261 them->channel == NULL)
262 goto close_and_cleanup;
263
264 wrbuf = g_bytes_get_data (us->out_bytes, &len);
265 status = g_io_channel_write_chars (us->channel, wrbuf, len, &n_written, NULL);
266 if (status != G_IO_STATUS_NORMAL)
267 goto close_and_cleanup;
268
269 g_assert (n_written > 0);
270 g_assert (them->in_watch == 0);
271
272 /*
273 * If we didn't write all of our data, wait until another G_IO_OUT
274 * condition to write more data.
275 */
276 if (n_written < len)
277 {
278 g_autoptr(GBytes) bytes = g_steal_pointer (&us->out_bytes);
279 us->out_bytes = g_bytes_new_from_bytes (bytes, n_written, len - n_written);
280 return G_SOURCE_CONTINUE;
281 }
282
283 g_clear_pointer (&us->out_bytes, g_bytes_unref);
284
285 /*
286 * We wrote all the data to this side, so now we can wait for more
287 * data from the input peer.
288 */
289 us->out_watch = 0;
290 them->in_watch =
291 g_io_add_watch_full (them->channel,
292 them->read_prio,
293 G_IO_IN | G_IO_ERR | G_IO_HUP,
294 _ide_pty_intercept_in_cb,
295 self, NULL);
296
297 return G_SOURCE_REMOVE;
298
299 close_and_cleanup:
300
301 _ide_pty_intercept_side_close (us);
302 _ide_pty_intercept_side_close (them);
303
304 return G_SOURCE_REMOVE;
305 }
306
307 /*
308 * _ide_pty_intercept_in_cb:
309 *
310 * This function is called when we have received a condition that specifies
311 * the channel has data to read. We read that data and then setup a watch
312 * onto the other other side so that we can write that data.
313 *
314 * If the other-side of the of the connection can write, then we write
315 * that data immediately.
316 *
317 * The in watch is disabled until we have completed the write.
318 */
319 static gboolean
_ide_pty_intercept_in_cb(GIOChannel * channel,GIOCondition condition,gpointer user_data)320 _ide_pty_intercept_in_cb (GIOChannel *channel,
321 GIOCondition condition,
322 gpointer user_data)
323 {
324 IdePtyIntercept *self = user_data;
325 IdePtyInterceptSide *us, *them;
326 GIOStatus status = G_IO_STATUS_AGAIN;
327 gchar buf[4096];
328 gchar *wrbuf = buf;
329 gsize n_read;
330
331 g_assert (channel != NULL);
332 g_assert (condition & (G_IO_ERR | G_IO_HUP | G_IO_IN));
333 g_assert (IDE_IS_PTY_INTERCEPT (self));
334
335 if (channel == self->master.channel)
336 {
337 us = &self->master;
338 them = &self->slave;
339 }
340 else
341 {
342 us = &self->slave;
343 them = &self->master;
344 }
345
346 g_assert (us->in_watch != 0);
347 g_assert (them->out_watch == 0);
348
349 if (condition & (G_IO_ERR | G_IO_HUP) || us->channel == NULL || them->channel == NULL)
350 goto close_and_cleanup;
351
352 g_assert (condition & G_IO_IN);
353
354 while (status == G_IO_STATUS_AGAIN)
355 {
356 n_read = 0;
357 status = g_io_channel_read_chars (us->channel, buf, sizeof buf, &n_read, NULL);
358 }
359
360 if (status == G_IO_STATUS_EOF)
361 goto close_and_cleanup;
362
363 if (n_read > 0 && us->callback != NULL)
364 us->callback (self, us, (const guint8 *)buf, n_read, us->callback_data);
365
366 while (n_read > 0)
367 {
368 gsize n_written = 0;
369
370 status = g_io_channel_write_chars (them->channel, buf, n_read, &n_written, NULL);
371
372 wrbuf += n_written;
373 n_read -= n_written;
374
375 if (n_read > 0 && status == G_IO_STATUS_AGAIN)
376 {
377 /* If we get G_IO_STATUS_AGAIN here, then we are in a situation where
378 * the other side is not in a position to handle the data. We need to
379 * setup a G_IO_OUT watch on the FD to wait until things are writeable.
380 *
381 * We'll cancel our G_IO_IN condition, and wait for the out condition
382 * to make forward progress.
383 */
384 them->out_bytes = g_bytes_new (wrbuf, n_read);
385 them->out_watch = g_io_add_watch_full (them->channel,
386 them->write_prio,
387 G_IO_OUT | G_IO_ERR | G_IO_HUP,
388 _ide_pty_intercept_out_cb,
389 self, NULL);
390 us->in_watch = 0;
391
392 return G_SOURCE_REMOVE;
393 }
394
395 if (status != G_IO_STATUS_NORMAL)
396 goto close_and_cleanup;
397
398 g_io_channel_flush (them->channel, NULL);
399 }
400
401 return G_SOURCE_CONTINUE;
402
403 close_and_cleanup:
404
405 _ide_pty_intercept_side_close (us);
406 _ide_pty_intercept_side_close (them);
407
408 return G_SOURCE_REMOVE;
409 }
410
411 /**
412 * ide_pty_intercept_set_size:
413 *
414 * Proxies a winsize across to the inferior. If the PTY is the
415 * controlling PTY for the process, then SIGWINCH will be signaled
416 * in the inferior process.
417 *
418 * Since we can't track SIGWINCH cleanly in here, we rely on the
419 * external consuming program to notify us of SIGWINCH so that we
420 * can copy the new size across.
421 *
422 * Since: 3.32
423 */
424 gboolean
ide_pty_intercept_set_size(IdePtyIntercept * self,guint rows,guint columns)425 ide_pty_intercept_set_size (IdePtyIntercept *self,
426 guint rows,
427 guint columns)
428 {
429
430 g_return_val_if_fail (IDE_IS_PTY_INTERCEPT (self), FALSE);
431
432 if (self->master.channel != NULL)
433 {
434 IdePtyFd fd = g_io_channel_unix_get_fd (self->master.channel);
435 struct winsize ws = {0};
436
437 ws.ws_col = columns;
438 ws.ws_row = rows;
439
440 return ioctl (fd, TIOCSWINSZ, &ws) == 0;
441 }
442
443 return FALSE;
444 }
445
446 static guint
_g_io_add_watch_full_with_context(GMainContext * main_context,GIOChannel * channel,gint priority,GIOCondition condition,GIOFunc func,gpointer user_data,GDestroyNotify notify)447 _g_io_add_watch_full_with_context (GMainContext *main_context,
448 GIOChannel *channel,
449 gint priority,
450 GIOCondition condition,
451 GIOFunc func,
452 gpointer user_data,
453 GDestroyNotify notify)
454 {
455 GSource *source;
456 guint id;
457
458 g_return_val_if_fail (channel != NULL, 0);
459
460 source = g_io_create_watch (channel, condition);
461
462 if (priority != G_PRIORITY_DEFAULT)
463 g_source_set_priority (source, priority);
464 g_source_set_callback (source, (GSourceFunc)func, user_data, notify);
465
466 id = g_source_attach (source, main_context);
467 g_source_unref (source);
468
469 return id;
470 }
471
472 /**
473 * ide_pty_intercept_init:
474 * @self: a location of memory to store a #IdePtyIntercept
475 * @fd: the PTY master fd, possibly from a #VtePty
476 * @main_context: (nullable): a #GMainContext or %NULL for thread-default
477 *
478 * Creates a enw #IdePtyIntercept using the PTY master fd @fd.
479 *
480 * A new PTY slave is created that will communicate with @fd.
481 * Additionally, a new PTY master is created that can communicate
482 * with another side, and will pass that information to @fd after
483 * extracting any necessary information.
484 *
485 * Returns: %TRUE if successful; otherwise %FALSE
486 *
487 * Since: 3.32
488 */
489 gboolean
ide_pty_intercept_init(IdePtyIntercept * self,int fd,GMainContext * main_context)490 ide_pty_intercept_init (IdePtyIntercept *self,
491 int fd,
492 GMainContext *main_context)
493 {
494 g_auto(IdePtyFd) slave_fd = IDE_PTY_FD_INVALID;
495 g_auto(IdePtyFd) master_fd = IDE_PTY_FD_INVALID;
496 struct winsize ws;
497
498 g_return_val_if_fail (self != NULL, FALSE);
499 g_return_val_if_fail (fd != -1, FALSE);
500
501 memset (self, 0, sizeof *self);
502 self->magic = IDE_PTY_INTERCEPT_MAGIC;
503
504 slave_fd = ide_pty_intercept_create_slave (fd, FALSE);
505 if (slave_fd == IDE_PTY_FD_INVALID)
506 return FALSE;
507
508 /* Do not perform additional processing on the slave_fd created
509 * from the master we were provided. Otherwise, it will be happening
510 * twice instead of just once.
511 */
512 if (!_ide_pty_intercept_set_raw (slave_fd))
513 return FALSE;
514
515 master_fd = ide_pty_intercept_create_master ();
516 if (master_fd == IDE_PTY_FD_INVALID)
517 return FALSE;
518
519 /* Copy the win size across */
520 if (ioctl (slave_fd, TIOCGWINSZ, &ws) >= 0)
521 ioctl (master_fd, TIOCSWINSZ, &ws);
522
523 if (main_context == NULL)
524 main_context = g_main_context_get_thread_default ();
525
526 self->master.read_prio = MASTER_READ_PRIORITY;
527 self->master.write_prio = MASTER_WRITE_PRIORITY;
528 self->slave.read_prio = SLAVE_READ_PRIORITY;
529 self->slave.write_prio = SLAVE_WRITE_PRIORITY;
530
531 self->master.channel = g_io_channel_unix_new (pty_fd_steal (&master_fd));
532 self->slave.channel = g_io_channel_unix_new (pty_fd_steal (&slave_fd));
533
534 g_io_channel_set_close_on_unref (self->master.channel, TRUE);
535 g_io_channel_set_close_on_unref (self->slave.channel, TRUE);
536
537 g_io_channel_set_encoding (self->master.channel, NULL, NULL);
538 g_io_channel_set_encoding (self->slave.channel, NULL, NULL);
539
540 g_io_channel_set_buffer_size (self->master.channel, CHANNEL_BUFFER_SIZE);
541 g_io_channel_set_buffer_size (self->slave.channel, CHANNEL_BUFFER_SIZE);
542
543 self->master.in_watch =
544 _g_io_add_watch_full_with_context (main_context,
545 self->master.channel,
546 self->master.read_prio,
547 G_IO_IN | G_IO_ERR | G_IO_HUP,
548 _ide_pty_intercept_in_cb,
549 self, NULL);
550
551 self->slave.in_watch =
552 _g_io_add_watch_full_with_context (main_context,
553 self->slave.channel,
554 self->slave.read_prio,
555 G_IO_IN | G_IO_ERR | G_IO_HUP,
556 _ide_pty_intercept_in_cb,
557 self, NULL);
558
559 return TRUE;
560 }
561
562 /**
563 * ide_pty_intercept_clear:
564 * @self: a #IdePtyIntercept
565 *
566 * Cleans up a #IdePtyIntercept previously initialized with
567 * ide_pty_intercept_init().
568 *
569 * This diconnects any #GIOChannel that have been attached and
570 * releases any allocated memory.
571 *
572 * It is invalid to use @self after calling this function.
573 *
574 * Since: 3.32
575 */
576 void
ide_pty_intercept_clear(IdePtyIntercept * self)577 ide_pty_intercept_clear (IdePtyIntercept *self)
578 {
579 g_return_if_fail (IDE_IS_PTY_INTERCEPT (self));
580
581 clear_source (&self->slave.in_watch);
582 clear_source (&self->slave.out_watch);
583 g_clear_pointer (&self->slave.channel, g_io_channel_unref);
584 g_clear_pointer (&self->slave.out_bytes, g_bytes_unref);
585
586 clear_source (&self->master.in_watch);
587 clear_source (&self->master.out_watch);
588 g_clear_pointer (&self->master.channel, g_io_channel_unref);
589 g_clear_pointer (&self->master.out_bytes, g_bytes_unref);
590
591 memset (self, 0, sizeof *self);
592 }
593
594 /**
595 * ide_pty_intercept_get_fd:
596 * @self: a #IdePtyIntercept
597 *
598 * Gets a master PTY fd created by the #IdePtyIntercept. This is suitable
599 * to use to create a slave fd which can be passed to a child process.
600 *
601 * Returns: A FD of a PTY master if successful, otherwise -1.
602 *
603 * Since: 3.32
604 */
605 IdePtyFd
ide_pty_intercept_get_fd(IdePtyIntercept * self)606 ide_pty_intercept_get_fd (IdePtyIntercept *self)
607 {
608 g_return_val_if_fail (IDE_IS_PTY_INTERCEPT (self), IDE_PTY_FD_INVALID);
609 g_return_val_if_fail (self->master.channel != NULL, IDE_PTY_FD_INVALID);
610
611 return g_io_channel_unix_get_fd (self->master.channel);
612 }
613
614 /**
615 * ide_pty_intercept_set_callback:
616 * @self: a IdePtyIntercept
617 * @side: the side containing the data to watch
618 * @callback: (scope notified): the callback to execute when data is received
619 * @user_data: closure data for @callback
620 *
621 * This sets the callback to execute every time data is received
622 * from a particular side of the intercept.
623 *
624 * You may only set one per side.
625 *
626 * Since: 3.32
627 */
628 void
ide_pty_intercept_set_callback(IdePtyIntercept * self,IdePtyInterceptSide * side,IdePtyInterceptCallback callback,gpointer callback_data)629 ide_pty_intercept_set_callback (IdePtyIntercept *self,
630 IdePtyInterceptSide *side,
631 IdePtyInterceptCallback callback,
632 gpointer callback_data)
633 {
634 g_return_if_fail (IDE_IS_PTY_INTERCEPT (self));
635 g_return_if_fail (side == &self->master || side == &self->slave);
636
637 side->callback = callback;
638 side->callback_data = callback_data;
639 }
640