Home > Blockchain >  How to use a pseudo-terminal returned from posix_openpt?
How to use a pseudo-terminal returned from posix_openpt?

Time:11-02

I'm trying to use posix_openpt on Mac. The issue I'm seeing is that I get a file descriptor back from posix_openpt. I use the file descriptor for reading and create a copy using dup for writing. The issue I'm running into is that when I write to the master file descriptor, I read that data back out from the master. So no data ends up at the slave. I confirmed this by using posix_spawnp to run a program with stdin/stdout/stderr set to the slave file. The program hangs indefinitely waiting for input. Here is my code (note, all error handling was removed for legibility):

    int master_fd = posix_openpt(O_RDWR);
    grantpt(master_fd);
    unlockpt(master_fd);

    char *slave_filename_orig = ptsname(master_fd);
    size_t slave_filename_len = strlen(slave_filename_orig);
    char slave_filename[slave_filename_len   1];
    strcpy(slave_filename, slave_filename_orig);

    posix_spawn_file_actions_t fd_actions;
    posix_spawn_file_actions_init(&fd_actions);
    posix_spawn_file_actions_addopen(&fd_actions, STDIN_FILENO, slave_filename, O_RDONLY, 0644);
    posix_spawn_file_actions_addopen(&fd_actions, STDOUT_FILENO, slave_filename, O_WRONLY, 0644);
    posix_spawn_file_actions_adddup2(&fd_actions, STDOUT_FILENO, STDERR_FILENO);

    pid_t pid;
    posix_spawnp(&pid, "wc", &fd_actions, NULL, NULL, NULL);

    int master_fd_write = dup(master_fd);
    char *data = "hello world";
    write(master_fd_write, data, strlen(data));
    close(master_fd_write);

    char buffer[1024];
    read(master_fd, buffer, 1024); // <- Issue Here
    // buffer now contains hello world. It should contain the output of `wc`

CodePudding user response:

(Note: The above was only tested on Linux; I don't have a Mac to work on, but I have no reason to believe it's any different in the details here.)

There are several problems with your code:

At least on Linux, calling posix_spawn() with a null pointer causes a crash. You need to provide all the arguments. Even if Macs accept it the way you have it, doing this is a Good Idea.

Next, wc reading from standard input will wait until an attempt to read more data gives an End Of File condition before it prints out the statistics it gathers; your code doesn't do this. With a pty, if you write a specific byte (Typically with the value 4, but it can be different, so best to use what the terminal says instead of hardcoding it) to it, the terminal driver will recognize that as signalling EOF without having to close the master like you would when using a pipe (Making it impossible to read the output of wc).

Second, the terminal's default settings include echoing the input; that's what you're reading.

A cleaned up version that addresses these issues and more (Like yours, with most error checking omitted; real code should be checking all these functions for errors):

#define _XOPEN_SOURCE 700
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <fcntl.h>
#include <spawn.h>
#include <termios.h>
#include <unistd.h>
#include <wait.h>

int main(void) {
  int master_fd = posix_openpt(O_RDWR);
  grantpt(master_fd);
  unlockpt(master_fd);

  char *slave_filename_orig = ptsname(master_fd);
  size_t slave_filename_len = strlen(slave_filename_orig);
  char slave_filename[slave_filename_len   1];
  strcpy(slave_filename, slave_filename_orig);
  //printf("slave pty filename: %s\n", slave_filename);

  // Open the slave pty in this process
  int slave_fd = open(slave_filename, O_RDWR);

  // Set up slave pty to not echo input
  struct termios tty_attrs;
  tcgetattr(slave_fd, &tty_attrs);
  tty_attrs.c_lflag &= ~ECHO;
  tcsetattr(slave_fd, TCSANOW, &tty_attrs);

  posix_spawn_file_actions_t fd_actions;
  posix_spawn_file_actions_init(&fd_actions);
  // Use adddup2 instead of addopen since we already have the pty open.
  posix_spawn_file_actions_adddup2(&fd_actions, slave_fd, STDIN_FILENO);
  posix_spawn_file_actions_adddup2(&fd_actions, slave_fd, STDOUT_FILENO);
  // Also close the master and original slave fd in the child
  posix_spawn_file_actions_addclose(&fd_actions, master_fd);
  posix_spawn_file_actions_addclose(&fd_actions, slave_fd);

  posix_spawnattr_t attrs;
  posix_spawnattr_init(&attrs);

  pid_t pid;
  extern char **environ;
  char *const spawn_argv[] = {"wc" , NULL};
  posix_spawnp(&pid, "wc", &fd_actions, &attrs, spawn_argv, environ);
  close(slave_fd); // No longer needed in the parent process

  const char *data = "hello world\n";
  ssize_t len = strlen(data);
  if (write(master_fd, data, len) != len) {
    perror("write");
  }
  // Send the terminal's end of file interrupt
  cc_t tty_eof = tty_attrs.c_cc[VEOF];
  if (write(master_fd, &tty_eof, sizeof tty_eof) != sizeof tty_eof) {
    perror("write EOF");
  }

  // Wait for wc to exit
  int status;
  waitpid(pid, &status, 0);

  char buffer[1024];
  ssize_t bytes = read(master_fd, buffer, 1024);
  if (bytes > 0) {
    fwrite(buffer, 1, bytes, stdout);
  }

  close(master_fd);

  return 0;
}

When compiled and run, outputs

      1       2      12

CodePudding user response:

There are two problems with this code.

First, you are seeing "hello world" on master_fd because by default terminals echo. You need to set the terminal to raw mode to suppress that.

Second, wc won't output anything until it sees an EOF, and it will not see an EOF until you close the master. Not just master_fd_write mind you, but all copies of master_fd, including master_fd itself. However, once you close the master, you cannot read from it.

Choose some other program that wc to demonstrate the functionality of posix_openpt.

Edit: It is possible to raise the end-of-file condition on the slave without closing the master by writing ^D (EOT, ascii 4).

  • Related