0

我目前正在做一个主要是为了好玩的项目,试图编写一个简单的终端仿真器。到目前为止,我想做的只是拥有一个只转发终端命令的“代理进程”。到目前为止,当我使用下面的代码调用 /usr/bin/nano 时,这似乎有效。

然而,当我尝试用 emacs 做同样的事情时,我遇到了一个 heisenbug:有时它工作得很好,但大多数情况下不是:调整大小工作,但是只要我输入任何内容,我的进程就会暂停,就好像我按下了 Ctrl+z (我当然没有这样做)。

emacs 的 strace 是一个序列:

ioctl(6, FIONREAD, [0])                 = 0
poll([{fd=7, events=POLLIN}], 1, 0)     = 0 (Timeout)
read(7, 0x7fff078de1d0, 16)             = -1 EAGAIN (Resource temporarily unavailable)
select(8, [6 7], NULL, NULL, {100000, 0}) = ? ERESTARTNOHAND (To be restarted)
--- SIGIO (I/O possible) @ 0 (0) ---
rt_sigreturn(0x1d)                      = -1 EINTR (Interrupted system call)

我在暂停时的流程是:

rt_sigaction(SIGTSTP, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGTSTP, {0x7f8964468b50, [], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, NULL, 8) = 0
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {0x7f8964468a80, [], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, NULL, 8) = 0
rt_sigaction(SIGTERM, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGTERM, {0x7f8964468a80, [], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, NULL, 8) = 0
rt_sigaction(SIGWINCH, NULL, {0x401088, [WINCH], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, 8) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
ioctl(1, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost -isig -icanon -echo ...}) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost -isig -icanon -echo ...}) = 0
select(4, [0 3], [], NULL, NULL)        = 1 (in [0])
read(0, 0x7fffafd46890, 4096)           = ? ERESTARTSYS (To be restarted)
--- SIGTTIN (Stopped (tty input)) @ 0 (0) ---
--- SIGTTIN (Stopped (tty input)) @ 0 (0) ---
read(0, 0x7fffafd46890, 4096)           = ? ERESTARTSYS (To be restarted)
--- SIGTTIN (Stopped (tty input)) @ 0 (0) ---
--- SIGTTIN (Stopped (tty input)) @ 0 (0) ---
read(0, 0x7fffafd46890, 4096)           = ? ERESTARTSYS (To be restarted)
--- SIGTTIN (Stopped (tty input)) @ 0 (0) ---
--- SIGTTIN (Stopped (tty input)) @ 0 (0) ---

我的源代码,使用 -lutil -lncurses 编译:

#include <assert.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#include <spawn.h>
#include <fcntl.h>
#include <sys/select.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdio.h>
#include <malloc.h>
#include <curses.h>
#include <sys/ioctl.h>
#include <signal.h>


#ifdef __GNUC__
# define likely(x)       __builtin_expect((x),1)
# define unlikely(x)     __builtin_expect((x),0)
#else
# define likely(x)       (x)
# define unlikely(x)     (x)
#endif

int mpt; /* master pty */
pid_t pid = 0; /* child pid */
bool childexit = false;

int max (int ini, ...) {
  /* the maximum of positive integers, terminated with -1 */
  va_list ap;
  va_start(ap, ini);
  int ret = -1;
  while (ini != -1) {
ret = (ret < ini) ? ini : ret;
ini = va_arg(ap, int);
  }
  va_end(ap);
  return ret;
}
/* atexit */
void close_child (void) {
  if (! childexit) {
kill (pid, SIGTERM);
  }
}
void reset_term (void) {
  endwin();
}
/* signal handlers */
void sigwinch (int sig) {
  (void) sig;
  struct winsize w;
  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) {
ioctl(mpt, TIOCSWINSZ, &w);
  }
}
void sigchld (int sig) {
  (void) sig;
  //childexit = true;
  exit(EXIT_SUCCESS);
}
void sigusr1 (int sig) {
  (void) sig;
  struct winsize w;
  ioctl(STDIN_FILENO, TIOCGWINSZ, &w);
  w.ws_row = 25;
  w.ws_col = 25;
}
int main (void) {
  atexit(close_child);
  atexit(reset_term);
  char* spawnedArgs[] = { "/usr/bin/emacs", "-nw", NULL };
  char* spawnedEnv[] = { "TERM=xterm", NULL };
  size_t slave_len = 128 * sizeof(char);
  char* slave = malloc (slave_len);
  if (slave == NULL) {
fprintf(stderr, "Cannot allocate slave var");
exit(EXIT_FAILURE);
  }
  mpt = posix_openpt(O_RDWR | O_NOCTTY);
  if (mpt < 0) {
perror("Cannot open Master");
exit(EXIT_FAILURE);
  }
  if (grantpt(mpt) < 0) {
perror("Cannot grant Terminal");
exit(EXIT_FAILURE);
  }
  if (unlockpt(mpt) < 0) {
perror("Cannot unlock Terminal");
exit(EXIT_FAILURE);
  }
  while ((ptsname_r(mpt, slave, slave_len)) != 0) {
int e = errno;
if (e == ERANGE) {
  slave_len *= 2;
  slave = realloc(slave, slave_len);
  if (slave == NULL) {
    fprintf(stderr, "Cannot reallocate slave var\n");
    exit(EXIT_FAILURE);
  }
} else {
  fprintf(stderr, "Cannot get name of terminal: %s\n", strerror(e));
  exit(EXIT_FAILURE);
}
  }
  int slavefd = open(slave, O_RDWR | O_NOCTTY);
  if (slavefd < 0) {
perror("Cannot open Slave");
exit(EXIT_FAILURE);
  }
  posix_spawn_file_actions_t action;
  posix_spawnattr_t attrs;
  sigset_t set;
  sigemptyset(&set);
  posix_spawnattr_setsigmask(&attrs, &set);
  posix_spawnattr_setflags(&attrs, POSIX_SPAWN_SETSIGMASK);
  posix_spawn_file_actions_init(&action);
  posix_spawn_file_actions_adddup2(&action, slavefd, STDOUT_FILENO);
  posix_spawn_file_actions_adddup2(&action, slavefd, STDIN_FILENO);
  posix_spawnp(&pid, spawnedArgs[0], &action, &attrs, spawnedArgs, spawnedEnv);

  fd_set rfds, wfds;
  int sel, nfds;

  char mystdin[4096], yourstdout[4096];
  int mystdindef = 0, yourstdoutdef = 0;

  signal(SIGWINCH, sigwinch);
  signal(SIGCHLD, sigchld);
  signal(SIGUSR1, sigusr1);

  initscr (); raw (); noecho ();

  while (1) {
  begin_select_loop:

nfds = 0;

FD_ZERO(&rfds);
FD_ZERO(&wfds);

/* read if buffers are empty, write if buffers are filled */

if (mystdindef == 0) {
  FD_SET(STDIN_FILENO, &rfds);
  nfds = max(nfds, STDIN_FILENO, -1);
} else {
  FD_SET(mpt, &wfds);
  nfds = max(nfds, mpt, -1);
}

if (yourstdoutdef == 0) {
  FD_SET(mpt, &rfds);
  nfds = max(nfds, mpt, -1);
} else {
  FD_SET(STDOUT_FILENO, &wfds);
  nfds = max(nfds, STDOUT_FILENO, -1);
}

sel = select(1 + nfds, &rfds, &wfds, NULL, NULL);
if (sel < 0) {
  int e = errno;
  if (unlikely(e == EINTR)) {
    /* A signal has interrupted us. Well, I guess this might be
       one of the few legit uses of goto, since C99 has no good
       mechanism for continuations yet. */
    goto begin_select_loop;
  } else {
    fprintf(stderr, "Error calling select: %s\n", strerror(e));
    exit(EXIT_FAILURE);
  }
}

if (FD_ISSET(STDIN_FILENO, &rfds)) {
  mystdindef = read (STDIN_FILENO, mystdin, sizeof(mystdin));
  if (unlikely(mystdindef == -1)) {
    perror("Reading from stdin");
    exit(EXIT_FAILURE);
  } else if (unlikely(mystdindef == 0)) {
    /* EOF */
    //exit(EXIT_SUCCESS);
  }
}

if (FD_ISSET(mpt, &rfds)) {
  yourstdoutdef = read (mpt, yourstdout, sizeof(mystdin));
  if (unlikely(yourstdoutdef == -1)) {
    perror("Reading from master");
    exit(EXIT_FAILURE);
  } else if (unlikely(yourstdoutdef == 0)) {
    /* EOF */
    //exit(EXIT_SUCCESS);
  }
}

if (FD_ISSET(STDOUT_FILENO, &wfds)) {
  int written = write(STDOUT_FILENO, yourstdout, yourstdoutdef);
  if (unlikely(written == -1)) {
    perror("Writing to stdout");
    exit(EXIT_FAILURE);
  } else if (unlikely(written < yourstdoutdef)) {
    memmove(yourstdout, yourstdout + written, yourstdoutdef -= written );
  } else {
    yourstdoutdef = 0;
  }
}

if (FD_ISSET(mpt, &wfds)) {
  int written = write(mpt, mystdin, mystdindef);
  if (unlikely(written == -1)) {
    perror("Writing to stdout");
    exit(EXIT_FAILURE);
  } else if (unlikely(written < mystdindef)) {
    memmove(mystdin, mystdin + written, mystdindef -= written );
  } else {
    mystdindef = 0;
  }
}
  }
}

我会很感激任何帮助,我真的不知道会发生什么。

4

1 回答 1

1

您不会posix_spawnattr_t通过初始化对象posix_spawnattr_init()(可能会导致使用意外属性),也不会破坏对象posix_spawnattr_tposix_spawn_file_actions_t对象。

子进程既没有伪终端作为它的标准错误(fd 2),也没有作为它的控制终端。因此,它将部分表现得好像附加到原始 tty 而不是新的 tty。

前者很容易通过另一个 dup2 修复。后者需要在子进程中调用setsid()、调用非标准函数如tcsetsid()或ioctl;TIOCSCTTY因此,您需要从posix_spawnp()fork()execve()

如果您不介意使用更多非标准函数,可以使用login_tty()或简化代码forkpty()

于 2013-02-10T18:42:47.160 回答