写入 `forkpty` 子进程:`less` 寻呼机

Write to `forkpty` child process: `less` pager

我正在尝试使用 forkptyexecvp less 寻呼程序,然后从父进程以子进程 less 进程会将其作为输入。

我一直在研究如何实现这一点,但在使用 pipefork 时我无法使用 forkpty 来完成某些工作。

发生的事情是我没有看到任何输出,然后程序正常退出。

编辑:我注意到,如果我在 forkpty 之后阅读 master,我会看到 "Missing filename ("less --help" for help)" from less,但是如何来吧$ echo test | less,可以吗?把exec_argv改成"-"传给less还是和原来一样的问题(没有输出)。

我是不是遗漏了什么明显的东西?

使用 pipefork

的工作代码
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    int read_write_fds[2];

    if (pipe(read_write_fds) == -1) {
        perror("pipe");
        return EXIT_FAILURE;
    }

    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        return EXIT_FAILURE;
    }

    if (!pid) {
        if (close(read_write_fds[1]) == -1) {
            perror("CHILD: close write");
            return EXIT_FAILURE;
        }
        if (dup2(read_write_fds[0], STDIN_FILENO) == -1) {
            perror("CHILD: dup2 read STDIN_FILENO");
            return EXIT_FAILURE;
        }
        if (close(read_write_fds[0]) == -1) {
            perror("CHILD: close read");
            return EXIT_FAILURE;
        }

        char* exec_argv[] = {"less", NULL};
        execvp(exec_argv[0], exec_argv);
        perror("CHILD: execvp");
        return EXIT_FAILURE;
    }

    if (close(read_write_fds[0]) == -1) {
        perror("PARENT: close read");
        return EXIT_FAILURE;
    }

    char text[] = "Hello, world!\n";
    char* text_ptr = text;
    size_t bytes_left = sizeof(text) - 1;

    while (bytes_left > 0) {
        ssize_t bytes_written = write(read_write_fds[1], text_ptr, bytes_left);

        if (bytes_written == -1) {
            perror("PARENT: write");
            return EXIT_FAILURE;
        }

        bytes_left -= bytes_written;
        text_ptr += bytes_written;
    }

    if (close(read_write_fds[1]) == -1) {
        perror("PARENT: close write");
        return EXIT_FAILURE;
    }
    if (waitpid(pid, NULL, 0) == -1) {
        perror("PARENT: waitpid");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

使用 forkpty

的非工作代码
#include <sys/types.h>
#include <pty.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    int master;
    pid_t pid = forkpty(&master, NULL, NULL, NULL);

    if (pid == -1) {
        perror("forkpty");
        return EXIT_FAILURE;
    }

    if (!pid) {
        char* exec_argv[] = {"less", NULL};
        execvp(exec_argv[0], exec_argv);
        perror("CHILD: execvp");
        return EXIT_FAILURE;
    }

    char text[] = "Hello, world!\n";
    char* text_ptr = text;
    size_t bytes_left = sizeof(text) - 1;

    while (bytes_left > 0) {
        ssize_t bytes_written = write(master, text_ptr, bytes_left);

        if (bytes_written == -1) {
            perror("PARENT: write");
            return EXIT_FAILURE;
        }

        bytes_left -= bytes_written;
        text_ptr += bytes_written;
    }

    if (close(master) == -1) {
        perror("PARENT: close");
        return EXIT_FAILURE;
    }

    if (waitpid(pid, NULL, 0) == -1) {
        perror("PARENT: waitpid");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

(小问题:在 fork() 之后的 child 中你应该使用 _exit()。)

您在这里看不到任何输出的原因是因为 child 的标准输入、标准输出和标准错误都附加到新的 PTY。您需要从 master 读取数据并对其进行处理。

这是我提供的 link 中涉及的文件:

注意:linked 网页上还有很多其他文件,您可能会发现您需要此项目:

示例主要功能:

#include "apue.h"
#include <termios.h>

#ifdef LINUX
#define OPTSTR "+d:einv"
#else
#define OPTSTR "d:einv"
#endif

static void set_noecho(int);    /* at the end of this file */
void        do_driver(char *);  /* in the file driver.c */
void        loop(int, int);     /* in the file loop.c */

int
main(int argc, char *argv[])
{
    int             fdm, c, ignoreeof, interactive, noecho, verbose;
    pid_t           pid;
    char            *driver;
    char            slave_name[20];
    struct termios  orig_termios;
    struct winsize  size;

    interactive = isatty(STDIN_FILENO);
    ignoreeof = 0;
    noecho = 0;
    verbose = 0;
    driver = NULL;

    opterr = 0;     /* don't want getopt() writing to stderr */
    while ((c = getopt(argc, argv, OPTSTR)) != EOF) {
        switch (c) {
        case 'd':       /* driver for stdin/stdout */
            driver = optarg;
            break;

        case 'e':       /* noecho for slave pty's line discipline */
            noecho = 1;
            break;

        case 'i':       /* ignore EOF on standard input */
            ignoreeof = 1;
            break;

        case 'n':       /* not interactive */
            interactive = 0;
            break;

        case 'v':       /* verbose */
            verbose = 1;
            break;

        case '?':
            err_quit("unrecognized option: -%c", optopt);
        }
    }
    if (optind >= argc)
        err_quit("usage: pty [ -d driver -einv ] program [ arg ... ]");

    if (interactive) {  /* fetch current termios and window size */
        if (tcgetattr(STDIN_FILENO, &orig_termios) < 0)
            err_sys("tcgetattr error on stdin");
        if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0)
            err_sys("TIOCGWINSZ error");
        pid = pty_fork(&fdm, slave_name, sizeof(slave_name),
          &orig_termios, &size);
    } else {
        pid = pty_fork(&fdm, slave_name, sizeof(slave_name),
          NULL, NULL);
    }

    if (pid < 0) {
        err_sys("fork error");
    } else if (pid == 0) {      /* child */
        if (noecho)
            set_noecho(STDIN_FILENO);   /* stdin is slave pty */

        if (execvp(argv[optind], &argv[optind]) < 0)
            err_sys("can't execute: %s", argv[optind]);
    }

    if (verbose) {
        fprintf(stderr, "slave name = %s\n", slave_name);
        if (driver != NULL)
            fprintf(stderr, "driver = %s\n", driver);
    }

    if (interactive && driver == NULL) {
        if (tty_raw(STDIN_FILENO) < 0)  /* user's tty to raw mode */
            err_sys("tty_raw error");
        if (atexit(tty_atexit) < 0)     /* reset user's tty on exit */
            err_sys("atexit error");
    }

    if (driver)
        do_driver(driver);  /* changes our stdin/stdout */

    loop(fdm, ignoreeof);   /* copies stdin -> ptym, ptym -> stdout */

    exit(0);
}

static void
set_noecho(int fd)      /* turn off echo (for slave pty) */
{
    struct termios  stermios;

    if (tcgetattr(fd, &stermios) < 0)
        err_sys("tcgetattr error");

    stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);

    /*
     * Also turn off NL to CR/NL mapping on output.
     */
    stermios.c_oflag &= ~(ONLCR);

    if (tcsetattr(fd, TCSANOW, &stermios) < 0)
        err_sys("tcsetattr error");
}

循环函数示例:

#include "apue.h"

#define BUFFSIZE    512

static void sig_term(int);
static volatile sig_atomic_t    sigcaught;  /* set by signal handler */

void
loop(int ptym, int ignoreeof)
{
    pid_t   child;
    int     nread;
    char    buf[BUFFSIZE];

    if ((child = fork()) < 0) {
        err_sys("fork error");
    } else if (child == 0) {    /* child copies stdin to ptym */
        for ( ; ; ) {
            if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) < 0)
                err_sys("read error from stdin");
            else if (nread == 0)
                break;      /* EOF on stdin means we're done */
            if (writen(ptym, buf, nread) != nread)
                err_sys("writen error to master pty");
        }

        /*
         * We always terminate when we encounter an EOF on stdin,
         * but we notify the parent only if ignoreeof is 0.
         */
        if (ignoreeof == 0)
            kill(getppid(), SIGTERM);   /* notify parent */
        exit(0);    /* and terminate; child can't return */
    }

    /*
     * Parent copies ptym to stdout.
     */
    if (signal_intr(SIGTERM, sig_term) == SIG_ERR)
        err_sys("signal_intr error for SIGTERM");

    for ( ; ; ) {
        if ((nread = read(ptym, buf, BUFFSIZE)) <= 0)
            break;      /* signal caught, error, or EOF */
        if (writen(STDOUT_FILENO, buf, nread) != nread)
            err_sys("writen error to stdout");
    }

    /*
     * There are three ways to get here: sig_term() below caught the
     * SIGTERM from the child, we read an EOF on the pty master (which
     * means we have to signal the child to stop), or an error.
     */
    if (sigcaught == 0) /* tell child if it didn't send us the signal */
        kill(child, SIGTERM);

    /*
     * Parent returns to caller.
     */
}

/*
 * The child sends us SIGTERM when it gets EOF on the pty slave or
 * when read() fails.  We probably interrupted the read() of ptym.
 */
static void
sig_term(int signo)
{
    sigcaught = 1;      /* just set flag and return */
}

示例驱动程序:

#include "apue.h"

void
do_driver(char *driver)
{
    pid_t   child;
    int     pipe[2];

    /*
     * Create a full-duplex pipe to communicate with the driver.
     */
    if (fd_pipe(pipe) < 0)
        err_sys("can't create stream pipe");

    if ((child = fork()) < 0) {
        err_sys("fork error");
    } else if (child == 0) {        /* child */
        close(pipe[1]);

        /* stdin for driver */
        if (dup2(pipe[0], STDIN_FILENO) != STDIN_FILENO)
            err_sys("dup2 error to stdin");

        /* stdout for driver */
        if (dup2(pipe[0], STDOUT_FILENO) != STDOUT_FILENO)
            err_sys("dup2 error to stdout");
        if (pipe[0] != STDIN_FILENO && pipe[0] != STDOUT_FILENO)
            close(pipe[0]);

        /* leave stderr for driver alone */
        execlp(driver, driver, (char *)0);
        err_sys("execlp error for: %s", driver);
    }

    close(pipe[0]);     /* parent */
    if (dup2(pipe[1], STDIN_FILENO) != STDIN_FILENO)
        err_sys("dup2 error to stdin");
    if (dup2(pipe[1], STDOUT_FILENO) != STDOUT_FILENO)
        err_sys("dup2 error to stdout");
    if (pipe[1] != STDIN_FILENO && pipe[1] != STDOUT_FILENO)
        close(pipe[1]);

    /*
     * Parent returns, but with stdin and stdout connected
     * to the driver.
     */
}

这里有一个 makefile 将它们放在一起:

根目录=.. 平台=$(shell $(ROOT)/systype.sh) 包括 $(ROOT)/Make.defines.$(PLATFORM)

ifeq "$(PLATFORM)" "solaris"
  EXTRALIBS=-lsocket -lnsl
endif

PROGS = pty

all:    $(PROGS)

pty:    main.o loop.o driver.o $(LIBAPUE)
    $(CC) $(CFLAGS) -o pty main.o loop.o driver.o $(LDFLAGS) $(LDLIBS)

clean:
    rm -f $(PROGS) $(TEMPFILES) *.o

include $(ROOT)/Make.libapue.inc