]> git.ipfire.org Git - pakfire.git/commitdiff
pty: Fix setting raw mode
authorMichael Tremer <michael.tremer@ipfire.org>
Sun, 6 Oct 2024 16:57:46 +0000 (16:57 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sun, 6 Oct 2024 16:57:46 +0000 (16:57 +0000)
If the standard input & output share the same inode, we can't overwrite
the attributes and need to take care of this.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/libpakfire/pty.c

index 6e3d2a7983331777b877b7db2d467db94c96eaed..5ac956e79ac02ae8b60bc5aa7f0b3a119c0e1c10 100644 (file)
@@ -22,6 +22,7 @@
 #include <fcntl.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
+#include <sys/stat.h>
 #include <termios.h>
 #include <unistd.h>
 
@@ -42,9 +43,7 @@ struct pakfire_pty_stdio {
 
        // Terminal Attributes
        struct termios attrs;
-
-       // File Descriptor Flags
-       int flags;
+       unsigned int attrs_saved:1;
 
        // IO Flags
        enum pakfire_pty_io {
@@ -95,16 +94,60 @@ static int pakfire_pty_has_flag(struct pakfire_pty* pty, int flag) {
        return pty->flags & flag;
 }
 
+static int pakfire_pty_same_inode(struct pakfire_pty* pty, int fd1, int fd2) {
+       struct stat stat1;
+       struct stat stat2;
+       int r;
+
+       // Stat the first file descriptor
+       r = fstat(fd1, &stat1);
+       if (r < 0) {
+               CTX_ERROR(pty->ctx, "Could not stat fd %d: %m\n", fd1);
+               return -errno;
+       }
+
+       // Stat the second file descriptor
+       r = fstat(fd2, &stat2);
+       if (r < 0) {
+               CTX_ERROR(pty->ctx, "Could not stat fd %d: %m\n", fd2);
+               return -errno;
+       }
+
+       // Mismatched mode
+       if ((stat1.st_mode ^ stat2.st_mode) & S_IFMT)
+               return 0;
+
+       // Match if the device and inode are identical
+       return (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
+}
+
 static int pakfire_pty_restore_attrs(struct pakfire_pty* pty,
                struct pakfire_pty_stdio* stdio) {
+       int flags;
        int r;
 
+       // Do nothing if the file descriptor is no longer open
+       if (stdio->fd < 0)
+               return 0;
+
+       // Don't restore anything if we didn't save things before
+       if (!stdio->attrs_saved)
+               return 0;
+
        // Skip everything if fd is not a TTY
        if (!isatty(stdio->fd))
                return 0;
 
-       // Restore the flags (and make the file descriptor blocking again)
-       r = fcntl(stdio->fd, F_SETFL, stdio->flags & ~O_NONBLOCK);
+       // Fetch the flags
+       flags = fcntl(stdio->fd, F_GETFL, 0);
+       if (flags < 0) {
+               CTX_ERROR(pty->ctx, "Could not set flags for file descriptor %d: %s\n",
+                       stdio->fd, strerror(errno));
+               return -errno;
+       }
+
+       // Turn on non-blocking mode again
+       r = fcntl(stdio->fd, F_SETFL, flags & ~O_NONBLOCK);
        if (r < 0) {
                CTX_ERROR(pty->ctx, "Could not set flags for file descriptor %d: %s\n",
                        stdio->fd, strerror(errno));
@@ -126,6 +169,24 @@ static int pakfire_pty_restore_attrs(struct pakfire_pty* pty,
        return 0;
 }
 
+static int pakfire_pty_store_attrs(struct pakfire_pty* pty,
+               struct pakfire_pty_stdio* stdio) {
+       int r;
+
+       // Store all attributes
+       r = tcgetattr(stdio->fd, &stdio->attrs);
+       if (r) {
+               CTX_ERROR(pty->ctx, "Could not fetch terminal attributes from fd %d: %s\n",
+                       stdio->fd, strerror(errno));
+               return -errno;
+       }
+
+       // Mark the attributes as saved
+       stdio->attrs_saved = 1;
+
+       return 0;
+}
+
 static int pakfire_pty_disconnect(struct pakfire_pty* pty) {
        if (pty->master.fd >= 0) {
                close(pty->master.fd);
@@ -133,10 +194,8 @@ static int pakfire_pty_disconnect(struct pakfire_pty* pty) {
        }
 
        // Restore any changed terminal attributes
-       if (pty->stdin.fd >= 0)
-               pakfire_pty_restore_attrs(pty, &pty->stdin);
-       if (pty->stdout.fd >= 0)
-               pakfire_pty_restore_attrs(pty, &pty->stdout);
+       pakfire_pty_restore_attrs(pty, &pty->stdin);
+       pakfire_pty_restore_attrs(pty, &pty->stdout);
 
        // Clear events
        if (pty->master.event)
@@ -416,59 +475,66 @@ static int pakfire_pty_stdout(sd_event_source* source, int fd, uint32_t events,
        return pakfire_pty_activity(pty, &pty->stdout, events);
 }
 
-static int pakfire_pty_enable_raw_mode(struct pakfire_pty* pty, struct pakfire_pty_stdio* stdio) {
+static int pakfire_pty_enable_raw_mode(struct pakfire_pty* pty) {
        struct termios raw_attrs;
+       int same;
        int r;
 
-       // Skip if we don't know the file descriptor
-       if (stdio->fd < 0)
-               return 0;
+       CTX_DEBUG(pty->ctx, "Enabling raw mode\n");
 
-       // Skip everything if fd is not a TTY
-       if (!isatty(stdio->fd))
-               return 0;
+       r = pakfire_pty_same_inode(pty, pty->stdin.fd, pty->stdout.fd);
+       if (r < 0)
+               return r;
 
-       CTX_DEBUG(pty->ctx, "Enabling raw mode on fd %d\n", stdio->fd);
+       // Are standard input/output the same inode?
+       same = (r > 0);
 
-       // Store flags
-       stdio->flags = fcntl(stdio->fd, F_GETFL);
-       if (stdio->flags < 0) {
-               CTX_ERROR(pty->ctx, "Could not fetch flags from fd %d: %s\n",
-                       stdio->fd, strerror(errno));
-               return -errno;
-       }
+       // Store attributes for standard input
+       if (pty->stdin.fd >= 0) {
+               r = pakfire_pty_store_attrs(pty, &pty->stdin);
+               if (r < 0)
+                       return r;
 
-       // Fetch all attributes
-       r = tcgetattr(stdio->fd, &stdio->attrs);
-       if (r) {
-               CTX_ERROR(pty->ctx, "Could not fetch terminal attributes from fd %d: %s\n",
-                       stdio->fd, strerror(errno));
-               return -errno;
+               // Copy the attributes
+               raw_attrs = pty->stdin.attrs;
+
+               // Enable raw mode
+               cfmakeraw(&raw_attrs);
+
+               if (!same)
+                       raw_attrs.c_oflag = pty->stdin.attrs.c_oflag;
+
+               // Restore the attributes
+               r = tcsetattr(pty->stdin.fd, TCSANOW, &raw_attrs);
+               if (r) {
+                       CTX_ERROR(pty->ctx, "Could not restore terminal attributes for fd %d: %s\n",
+                               pty->stdin.fd, strerror(errno));
+                       return -errno;
+               }
        }
 
-       // Copy all attributes
-       raw_attrs = stdio->attrs;
+       // Store attributes for standard output
+       if (!same && pty->stdout.fd >= 0) {
+               r = pakfire_pty_store_attrs(pty, &pty->stdout);
+               if (r < 0)
+                       return r;
 
-       // Make it RAW
-       cfmakeraw(&raw_attrs);
+               // Copy the attributes
+               raw_attrs = pty->stdout.attrs;
 
-       switch (stdio->fd) {
-               case STDIN_FILENO:
-                       raw_attrs.c_oflag = stdio->attrs.c_oflag;
-                       break;
+               // Enable raw mode
+               cfmakeraw(&raw_attrs);
 
-               case STDOUT_FILENO:
-                       raw_attrs.c_iflag = stdio->attrs.c_iflag;
-                       raw_attrs.c_lflag = stdio->attrs.c_lflag;
-                       break;
-       }
+               raw_attrs.c_iflag = pty->stdout.attrs.c_iflag;
+               raw_attrs.c_lflag = pty->stdout.attrs.c_lflag;
 
-       // Restore the attributes
-       r = tcsetattr(stdio->fd, TCSANOW, &raw_attrs);
-       if (r) {
-               CTX_ERROR(pty->ctx, "Could not restore terminal attributes for fd %d: %s\n",
-                       stdio->fd, strerror(errno));
-               return -errno;
+               // Restore the attributes
+               r = tcsetattr(pty->stdout.fd, TCSANOW, &raw_attrs);
+               if (r) {
+                       CTX_ERROR(pty->ctx, "Could not restore terminal attributes for fd %d: %s\n",
+                               pty->stdout.fd, strerror(errno));
+                       return -errno;
+               }
        }
 
        return 0;
@@ -560,8 +626,8 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) {
 
        // Connect standard input unless we are in read-only mode
        if (!pakfire_pty_has_flag(pty, PAKFIRE_PTY_READ_ONLY)) {
-               // Enable RAW mode on standard input
-               r = pakfire_pty_enable_raw_mode(pty, &pty->stdin);
+               // Enable RAW mode
+               r = pakfire_pty_enable_raw_mode(pty);
                if (r)
                        return r;
 
@@ -575,11 +641,6 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) {
                sd_event_source_set_description(pty->stdin.event, "pty-stdin");
        }
 
-       // Enable RAW mode on standard output
-       r = pakfire_pty_enable_raw_mode(pty, &pty->stdout);
-       if (r)
-               return r;
-
        // Add standard output to the event loop
        r = sd_event_add_io(pty->loop, &pty->stdout.event,
                        pty->stdout.fd, EPOLLOUT|EPOLLET, pakfire_pty_stdout, pty);