写入 `forkpty` 子进程:`less` 寻呼机
Write to `forkpty` child process: `less` pager
我正在尝试使用 forkpty
到 execvp
less
寻呼程序,然后从父进程以子进程 less
进程会将其作为输入。
我一直在研究如何实现这一点,但在使用 pipe
和 fork
时我无法使用 forkpty
来完成某些工作。
发生的事情是我没有看到任何输出,然后程序正常退出。
编辑:我注意到,如果我在 forkpty
之后阅读 master
,我会看到 "Missing filename ("less --help" for help)" from less
,但是如何来吧$ echo test | less
,可以吗?把exec_argv
改成"-"
传给less
还是和原来一样的问题(没有输出)。
我是不是遗漏了什么明显的东西?
使用 pipe
和 fork
的工作代码
#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
我正在尝试使用 forkpty
到 execvp
less
寻呼程序,然后从父进程以子进程 less
进程会将其作为输入。
我一直在研究如何实现这一点,但在使用 pipe
和 fork
时我无法使用 forkpty
来完成某些工作。
发生的事情是我没有看到任何输出,然后程序正常退出。
编辑:我注意到,如果我在 forkpty
之后阅读 master
,我会看到 "Missing filename ("less --help" for help)" from less
,但是如何来吧$ echo test | less
,可以吗?把exec_argv
改成"-"
传给less
还是和原来一样的问题(没有输出)。
我是不是遗漏了什么明显的东西?
使用 pipe
和 fork
的工作代码
#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