После разветвления bash с помощью forkpty и execvp, bash не отвечает на SIGINT

Фон

В настоящее время я пишу эмулятор терминала для текстового редактора, который написан на Node.js (Javascript). Приложение использует C ++ для разветвления оболочки и связи с ней в бэкэнде. Код, который создает оболочку, был написан другим разработчиком, который, похоже, больше не поддерживает свой проект. У меня не было другого выбора, кроме как попытаться решить проблему самостоятельно. Я прошёл его код построчно до точки, где я понимаю большую часть того, что он делает.

проблема

Я не могу понять, почему bash не отправит сигнал SIGINT своим подпроцессам. Все остальное работает отлично. Я могу общаться с моим экземпляром оболочки в Node.js и запускать команды. Однако если я запишу код выхода прерывания сигнала (‘\ x03’) в fd файла pty, сигнал SIGINT не будет отправлен. Все что я вижу это ^C на дисплее терминала. Некоторые команды, такие как ping будет интерпретировать это и остановиться. Команды как cat, python, или же java не останавливайся и просто покажет ^C,

Пример:

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

Код

Первый forkpty используется для получения экземпляра pty. Затем экземпляр pty вызывает execvp, чтобы заменить процесс на процесс оболочки. В этом случае 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, но не решает проблему.
Я пытался писать на FD непосредственно в C ++ только в том случае, если это был Node.js. Та же проблема видения ^C но не регистрируя SIGINT,

Единственное, что работает, — это использование другой оболочки, такой как zsh. При использовании zsh SIGINT правильно отправлено и подпроцессы останавливаются. Еще одна странность заключается в том, что оболочки bash, запущенные из ZSH, не сталкиваются с этой проблемой. Другими словами, если я форк 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;

1

Решение

Краткий ответ: звонок setsid до исполнения


Сигналы отправляются в активную группу процессов сеанса, которая связана с подчиненным pty. По телефону setsidсоздается сеанс, который связан с pty, в противном случае сеанс не был создан, поэтому сигнал не будет излучаться.

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]