c++ - 用 forkpty 和 execvp fork bash 后,bash 不响应 SIGINT

标签 c++ node.js bash fork execvp

背景

我目前正在为用 Node.js (Javascript) 编写的文本编辑器编写终端模拟器。该应用程序使用 C++ 派生一个 shell 并在后端与其通信。 fork shell 的代码是由另一个似乎不再维护他的项目的开发人员编写的。这让我别无选择,只能尝试自己解决问题。我已经逐行浏览了他的代码,直到我理解了它的大部分功能。

问题

我不明白为什么 bash 不会将 SIGINT 信号发送到它的子进程。其他一切都完美无缺。我可以在 Node.js 中与我的 shell 实例通信并运行命令。但是,如果我将信号中断转义代码 ('\x03') 写入 pty 的 fd,则不会发送 SIGINT 信号。我所看到的只是终端显示中的 ^Cping 等某些命令将解释它并停止。 catpythonjava 等命令不会停止,只会显示 ^C

例子:

user:example user$ cat
^C^C^C^C^C^C^C^C

代码

首先使用forkpty获取一个pty实例。然后,pty 实例调用 execvp 将进程替换为 shell 进程。在这种情况下,execvp 启动 execvp('/bin/bash', {'/bin/bash', '--login'})

NAN_METHOD(PtyFork) {
  Nan::HandleScope scope;

  if (info.Length() != 9
      || !info[0]->IsString() // file
      || !info[1]->IsArray() // args
      || !info[2]->IsArray() // env
      || !info[3]->IsString() // cwd
      || !info[4]->IsNumber() // cols
      || !info[5]->IsNumber() // rows
      || !info[6]->IsNumber() // uid
      || !info[7]->IsNumber() // gid
      || !info[8]->IsFunction() // onexit
  ) {
    return Nan::ThrowError(
      "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, onexit)");
  }

  // file
  String::Utf8Value file(info[0]->ToString());

  // args
  int i = 0;
  Local<Array> argv_ = Local<Array>::Cast(info[1]);
  int argc = argv_->Length();
  int argl = argc + 1 + 1;
  char **argv = new char*[argl];
  argv[0] = strdup(*file);
  argv[argl-1] = NULL;
  for (; i < argc; i++) {
    String::Utf8Value arg(argv_->Get(Nan::New<Integer>(i))->ToString());
    argv[i+1] = strdup(*arg);
  }

  // env
  i = 0;
  Local<Array> env_ = Local<Array>::Cast(info[2]);
  int envc = env_->Length();
  char **env = new char*[envc+1];
  env[envc] = NULL;
  for (; i < envc; i++) {
    String::Utf8Value pair(env_->Get(Nan::New<Integer>(i))->ToString());
    env[i] = strdup(*pair);
  }

  // cwd
  String::Utf8Value cwd_(info[3]->ToString());
  char *cwd = strdup(*cwd_);

  // size
  struct winsize winp;
  winp.ws_col = info[4]->IntegerValue();
  winp.ws_row = info[5]->IntegerValue();
  winp.ws_xpixel = 0;
  winp.ws_ypixel = 0;

  // uid / gid
  int uid = info[6]->IntegerValue();
  int gid = info[7]->IntegerValue();

  // fork the pty
  int master = -1;
  char name[40];
  pid_t pid = pty_forkpty(&master, name, NULL, &winp);

  if (pid) {
    for (i = 0; i < argl; i++) free(argv[i]);
    delete[] argv;
    for (i = 0; i < envc; i++) free(env[i]);
    delete[] env;
    free(cwd);
  }

  switch (pid) {
    case -1:
      return Nan::ThrowError("forkpty(3) failed.");
    case 0:
      if (strlen(cwd)) chdir(cwd);

      if (uid != -1 && gid != -1) {
        if (setgid(gid) == -1) {
          perror("setgid(2) failed.");
          _exit(1);
        }
        if (setuid(uid) == -1) {
          perror("setuid(2) failed.");
          _exit(1);
        }
      }

      pty_execvpe(argv[0], argv, env);

      perror("execvp(3) failed.");
      _exit(1);
    default:
      if (pty_nonblock(master) == -1) {
        return Nan::ThrowError("Could not set master fd to nonblocking.");
      }

      Local<Object> obj = Nan::New<Object>();
      Nan::Set(obj,
        Nan::New<String>("fd").ToLocalChecked(),
        Nan::New<Number>(master));
      Nan::Set(obj,
        Nan::New<String>("pid").ToLocalChecked(),
        Nan::New<Number>(pid));
      Nan::Set(obj,
        Nan::New<String>("pty").ToLocalChecked(),
        Nan::New<String>(name).ToLocalChecked());

      pty_baton *baton = new pty_baton();
      baton->exit_code = 0;
      baton->signal_code = 0;
      baton->cb.Reset(Local<Function>::Cast(info[8]));
      baton->pid = pid;
      baton->async.data = baton;

      uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid);

      uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton));

      return info.GetReturnValue().Set(obj);
  }

  return info.GetReturnValue().SetUndefined();
}

/**
 * execvpe
 */

// execvpe(3) is not portable.
// http://www.gnu.org/software/gnulib/manual/html_node/execvpe.html
static int
pty_execvpe(const char *file, char **argv, char **envp) {
  char **old = environ;
  environ = envp;
  int ret = execvp(file, argv);
  environ = old;
  return ret;
}

/**
 * Nonblocking FD
 */

static int
pty_nonblock(int fd) {
  int flags = fcntl(fd, F_GETFL, 0);
  if (flags == -1) return -1;
  return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

/**
 * pty_waitpid
 * Wait for SIGCHLD to read exit status.
 */

static void
pty_waitpid(void *data) {
  int ret;
  int stat_loc;

  pty_baton *baton = static_cast<pty_baton*>(data);

  errno = 0;

  if ((ret = waitpid(baton->pid, &stat_loc, 0)) != baton->pid) {
    if (ret == -1 && errno == EINTR) {
      return pty_waitpid(baton);
    }
    if (ret == -1 && errno == ECHILD) {
      // XXX node v0.8.x seems to have this problem.
      // waitpid is already handled elsewhere.
      ;
    } else {
      assert(false);
    }
  }

  if (WIFEXITED(stat_loc)) {
    baton->exit_code = WEXITSTATUS(stat_loc); // errno?
  }

  if (WIFSIGNALED(stat_loc)) {
    baton->signal_code = WTERMSIG(stat_loc);
  }

  uv_async_send(&baton->async);
}

/**
 * pty_after_waitpid
 * Callback after exit status has been read.
 */

static void
#if NODE_VERSION_AT_LEAST(0, 11, 0)
pty_after_waitpid(uv_async_t *async) {
#else
pty_after_waitpid(uv_async_t *async, int unhelpful) {
#endif
  Nan::HandleScope scope;
  pty_baton *baton = static_cast<pty_baton*>(async->data);

  Local<Value> argv[] = {
    Nan::New<Integer>(baton->exit_code),
    Nan::New<Integer>(baton->signal_code),
  };

  Local<Function> cb = Nan::New<Function>(baton->cb);
  baton->cb.Reset();
  memset(&baton->cb, -1, sizeof(baton->cb));
  Nan::Callback(cb).Call(Nan::GetCurrentContext()->Global(), 2, argv);

  uv_close((uv_handle_t *)async, pty_after_close);
}

/**
 * pty_after_close
 * uv_close() callback - free handle data
 */

static void
pty_after_close(uv_handle_t *handle) {
  uv_async_t *async = (uv_async_t *)handle;
  pty_baton *baton = static_cast<pty_baton*>(async->data);
  delete baton;
}

static pid_t
pty_forkpty(int *amaster, char *name,
            const struct termios *termp,
            const struct winsize *winp) {
  return forkpty(amaster, name, (termios *)termp, (winsize *)winp);
}

我尝试过的

我尝试使用 execvp 来调用 exec -l bash。这启动了 bash 但没有解决问题。
我试过直接在 C++ 中写入 fd,以防它是 Node.js。查看 ^C 但未注册 SIGINT 的问题相同。

唯一可行的是使用另一个 shell,例如 zsh。使用 zsh 时,SIGINT 被正确发送并且子进程停止。其他奇怪的是从 ZSH 启动的 bash shell 没有遇到这个问题。换句话说,如果我 fork zsh 并调用命令 exec bash(或 exec -l bash),那么新的 bash 实例将发送 SIGINT 信号就像它应该的那样。

我知道这不仅仅是我的问题,因为我的应用程序是公开的,并且有多个用户报告了这个问题。一些在 Linux 上,一些在 OS X 上。这是一个主要问题,所以如果有人能指出这里发生了什么,我们将不胜感激。

附加信息

打印出stty -a:

user:example user$ stty -a
speed 9600 baud; 15 rows; 159 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
        -echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo
        -extproc
iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel -iutf8
        -ignbrk brkint -inpck -ignpar -parmrk
oflags: opost onlcr -oxtabs -onocr -onlret
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow
        -dtrflow -mdmbuf
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
        eol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;
        min = 1; quit = ^\; reprint = ^R; start = ^Q; status = ^T;
        stop = ^S; susp = ^Z; time = 0; werase = ^W;

最佳答案

简答:在exec之前调用setsid


信号被发送到 session 的事件进程组,该进程组关联到从属 pty。通过调用 setsid,创建一个与 pty 相关联的 session ,否则不会创建任何 session ,因此不会发出任何信号。

关于c++ - 用 forkpty 和 execvp fork bash 后,bash 不响应 SIGINT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33141651/

相关文章:

Node.js - 为什么 Mongoose 不保存数据?

linux - 如何根据其他文件中的 ID 选择文件的子集?

linux - Cron-Bash : How to babysit an app on linux

c++ - 从 gcc 3.3.3 移植到 4.1.0,C++ 模板,对 undefined reference

C++ 覆盖嵌套类只能部分说服编译器

C++ 字符串文字相等性检查?

c++ - 编码解码器 YUV 到 RGB

node.js - 使用 grunt-mocha-test 创建测试组

javascript - node.js opc ua 许多监控项

linux - inotifywait 没有按预期工作