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