#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
+#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
// Terminal Attributes
struct termios attrs;
-
- // File Descriptor Flags
- int flags;
+ unsigned int attrs_saved:1;
// IO Flags
enum pakfire_pty_io {
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));
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);
}
// 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)
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;
// 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;
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);