Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pty close behavior, signals at exit #2387

Merged
merged 6 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Open the project in Xcode, open iSH.xcconfig, and change `ROOT_BUNDLE_IDENTIFIER

To set up your environment, cd to the project and run `meson build` to create a build directory in `build`. Then cd to the build directory and run `ninja`.

To set up a self-contained Alpine linux filesystem, download the Alpine minirootfs tarball for i386 from the [Alpine website](https://alpinelinux.org/downloads/) and run `./tools/fakefsify`, with the minirootfs tarball as the first argument and the name of the output directory as the second argument. Then you can run things inside the Alpine filesystem with `./ish -f alpine /bin/login -f root`, assuming the output directory is called `alpine`. If `tools/fakefsify` doesn't exist for you in your build directory, that might be because it couldn't find libarchive on your system (see above for ways to install it.)
To set up a self-contained Alpine linux filesystem, download the Alpine minirootfs tarball for i386 from the [Alpine website](https://alpinelinux.org/downloads/) and run `./tools/fakefsify`, with the minirootfs tarball as the first argument and the name of the output directory as the second argument. Then you can run things inside the Alpine filesystem with `./ish -f alpine /bin/sh`, assuming the output directory is called `alpine`. If `tools/fakefsify` doesn't exist for you in your build directory, that might be because it couldn't find libarchive on your system (see above for ways to install it.)

You can replace `ish` with `tools/ptraceomatic` to run the program in a real process and single step and compare the registers at each step. I use it for debugging. Requires 64-bit Linux 4.11 or later.

Expand Down
4 changes: 2 additions & 2 deletions README_KO.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Xcode로 프로젝트를 열고, iSH.xcconfig 연 후에 `ROOT_BUNDLE_IDENTIFIER

환경을 세팅하기 위해서는 프로젝트 디렉토리로 이동하고 `meson build`를 커맨드 라인에 입력하세요. 그 후 빌드 된 디렉토리로 cd 후 `ninja` 커맨드를 입력해 실행하세요.

자체적으로 컨테이너 화 된 Alpine 리눅스 파일 시스템으로 실행하고 싶다면, [Alpine 웹사이트](https://alpinelinux.org/downloads/) 에서 i386을 위한 Alpine minirootfs(Mini Root Filesystem) tarball 을 다운로드 받고 `./tools/fakefsify`으로 실행하세요. 매개인자로 다운로드 받은 minirootfs tarball 파일을 입력하고 출력 받을 디렉토리의 이름을 두번째 인자로 입력하면 됩니다. 그 후에는 `./ish -f {출력받을 디렉토리 이름} /bin/login -f root` 명령어를 사용하여 Alpine 시스템 내에서 원하는 것을 실행할 수 있습니다. 만약 `tools/fakefsify` 가 빌드 디렉토리에 존재하지 않는다면, libarchive를 찾을 수 없어서 그런 것일 수 있습니다. 위를 참고하여 시스템에 설치하는 방법을 참고해주세요.
자체적으로 컨테이너 화 된 Alpine 리눅스 파일 시스템으로 실행하고 싶다면, [Alpine 웹사이트](https://alpinelinux.org/downloads/) 에서 i386을 위한 Alpine minirootfs(Mini Root Filesystem) tarball 을 다운로드 받고 `./tools/fakefsify`으로 실행하세요. 매개인자로 다운로드 받은 minirootfs tarball 파일을 입력하고 출력 받을 디렉토리의 이름을 두번째 인자로 입력하면 됩니다. 그 후에는 `./ish -f {출력받을 디렉토리 이름} /bin/sh` 명령어를 사용하여 Alpine 시스템 내에서 원하는 것을 실행할 수 있습니다. 만약 `tools/fakefsify` 가 빌드 디렉토리에 존재하지 않는다면, libarchive를 찾을 수 없어서 그런 것일 수 있습니다. 위를 참고하여 시스템에 설치하는 방법을 참고해주세요.

실제 프로세스로 프로그램을 실행하고 각 단계의 레지스터를 비교하기 위해서 `ish`를 `tools/ptraceomatic`로 바꿔 실행할 수 있습니다. 디버깅을 위해 저는 사용합니다. 64-bit Linux 4.11 이후 버전이 필요합니다.

Expand All @@ -67,4 +67,4 @@ iSH에서 추가한 것 중 가장 흥미로운 것은 JIT 컴파일러 일 것

불행하게도 저는 어셈블리어로 대부분의 이러한 gadget을 작성했습니다. 이것은 성능적으로는 좋은 선택이었을 지 몰라도(실제로는 알 도리가 없지만), 가독성, 유지보수, 그리고 제 정신상태에 대해서는 좋지 않은 선택이 되었습니다. 컴파일러/어셈블러/링커로 인한 여러 고충은 말도 할 수 없을 정도입니다. 거의 무슨 제 코드의 가독성을 해치지 않으면 컴파일을 막는 그러한 악마가 있는 것 같았습니다. 이 코드를 작성하는 도중 제정신을 유지하기 위해서 저는 네이밍과 코드 구조론을 따른 최적의 선택을 하지 못하였습니다. `ss`, `s` 그리고 `a`와 같은 매크로 그리고 변수 명을 찾을 수 있을 것입니다. 주석 또한 찾기 힘들 것입니다.

그렇기에 주의 하세요: 해당 코드를 장기간 접할 경우 정신질환을 앓게되거나 GAS 매크로와 링커오류에 대한 악몽에 시달리고 또다른 부작용이 있을 수 있습니다. 암, 선천적 결함, 또는 생식기 질환을 야기한다고 질병관리청에서 인정했습니다. 암튼 그랬습니다.
그렇기에 주의 하세요: 해당 코드를 장기간 접할 경우 정신질환을 앓게되거나 GAS 매크로와 링커오류에 대한 악몽에 시달리고 또다른 부작용이 있을 수 있습니다. 암, 선천적 결함, 또는 생식기 질환을 야기한다고 질병관리청에서 인정했습니다. 암튼 그랬습니다.
4 changes: 2 additions & 2 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ iSH 是一个运行在 iOS 上的 Linux shell。本项目使用了 x86 用户模

在项目目录中运行命令 `meson build`,之后 `build` 目录会被创建。进入到 `build` 目录并运行命令 `ninja`。

为了建立一个自有的 Alpine linux 文件系统,请从 [Alpine 网站](https://alpinelinux.org/downloads/) 下载 `Alpine minirotfs tarball for i386` 并运行 `tools/fakefsify` 。将 minirotfs tarball 指定为第一个参数,将输出目录的名称(如`alpine`)指定为第二个参数,即 `tools/fakefsify $MinirotfsTarballFilename alpine` 然后在 Alpine 文件系统中运行 `/ish -f alpine/bin/login -f root`。如果 `build` 目录下找不到 `tools/fakefsify`,可能是系统上找不到 `libarchive` 的依赖(请参照前面的章节进行安装)。
为了建立一个自有的 Alpine linux 文件系统,请从 [Alpine 网站](https://alpinelinux.org/downloads/) 下载 `Alpine minirotfs tarball for i386` 并运行 `tools/fakefsify` 。将 minirotfs tarball 指定为第一个参数,将输出目录的名称(如`alpine`)指定为第二个参数,即 `tools/fakefsify $MinirotfsTarballFilename alpine` 然后在 Alpine 文件系统中运行 `/ish -f alpine/bin/sh`。如果 `build` 目录下找不到 `tools/fakefsify`,可能是系统上找不到 `libarchive` 的依赖(请参照前面的章节进行安装)。

除了可以使用 `ish`,你也可以使用 `tools/ptraceomatic` 替代它,以便在某个真实进程中单步比较寄存器。我通常使用它来进行调试(需要 64 位 Linux 4.11 或更高版本)。

Expand All @@ -64,4 +64,4 @@ iSH 是一个运行在 iOS 上的 Linux shell。本项目使用了 x86 用户模

但不幸的是,我最开始决定用汇编语言编写几乎所有的 gadgets。这可能从性能方面来说是一个好的决定(虽然我永远也无法确定),但是对可读性、可维护性和我的理智来说,这是一个可怕的决定。我承受了大量来自编译器、汇编程序以及链接器的乱七八糟的东西。那里面就像有一个魔鬼,把我的代码搞得畸形,就算没有畸形,也会编造一些愚蠢的理由说它不能够编译。为了在编写代码时保持理智,我不得不忽略代码结构和命名方面的最佳实践。你会发现宏和变量具有诸如 `ss`、`s` 和 `a` 等描述性的名称,并且汇编器的宏嵌套层数超乎你的想象。最重要的是,代码中几乎没有任何注释。

所以这是一个警告: 长期接触此代码可能会使你失去理智,对 GAS 宏和链接器错误产生噩梦,或是任何其他使人虚弱的副作用。在加利福尼亚,众所周知这样的代码会导致癌症、生产缺陷和重复伤害。
所以这是一个警告: 长期接触此代码可能会使你失去理智,对 GAS 宏和链接器错误产生噩梦,或是任何其他使人虚弱的副作用。在加利福尼亚,众所周知这样的代码会导致癌症、生产缺陷和重复伤害。
38 changes: 34 additions & 4 deletions fs/pty.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,30 @@ static int pty_master_init(struct tty *tty) {
return 0;
}


static void pty_hangup(struct tty *tty) {
if (tty == NULL)
return;
lock(&tty->lock);
tty_hangup(tty);
unlock(&tty->lock);
}

static struct tty *pty_hangup_other(struct tty *tty) {
struct tty *other = tty->pty.other;
if (other == NULL)
return NULL;
pty_hangup(other);
return other;
}

static void pty_slave_cleanup(struct tty *tty) {
pty_hangup_other(tty);
}

static void pty_master_cleanup(struct tty *tty) {
struct tty *slave = tty->pty.other;
struct tty *slave = pty_hangup_other(tty);
slave->pty.other = NULL;
lock(&slave->lock);
tty_hangup(slave);
unlock(&slave->lock);
tty_release(slave);
}

Expand All @@ -51,6 +69,16 @@ static int pty_slave_open(struct tty *tty) {
return 0;
}

static int pty_slave_close(struct tty *tty) {
// If userland's reference count on the pty slave will go to 0,
// hang up the pty master. But the session leader may have a
// reference, and the pty master always has a reference.
if (tty->refcount - 1 == (tty->session ? 2 : 1)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a really tricky bit. Could you add a comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

pty_hangup_other(tty);
}
return 0;
}

static int pty_master_ioctl(struct tty *tty, int cmd, void *arg) {
struct tty *slave = tty->pty.other;
switch (cmd) {
Expand Down Expand Up @@ -94,7 +122,9 @@ DEFINE_TTY_DRIVER(pty_master, &pty_master_ops, TTY_PSEUDO_MASTER_MAJOR, MAX_PTYS
const struct tty_driver_ops pty_slave_ops = {
.init = pty_return_eio,
.open = pty_slave_open,
.close = pty_slave_close,
.write = pty_write,
.cleanup = pty_slave_cleanup,
};
DEFINE_TTY_DRIVER(pty_slave, &pty_slave_ops, TTY_PSEUDO_SLAVE_MAJOR, MAX_PTYS);

Expand Down
28 changes: 11 additions & 17 deletions fs/tty.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,7 @@ void tty_release(struct tty *tty) {
cond_destroy(&tty->produced);
free(tty);
} else {
// bit of a hack
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bravo for cleaning this up

struct tty *master = NULL;
if (tty->driver == &pty_slave && tty->refcount == 1)
master = tty->pty.other;
unlock(&tty->lock);
if (master != NULL) {
lock(&master->lock);
tty_poll_wakeup(master, POLL_READ | POLL_HUP);
unlock(&master->lock);
}
}
}

Expand Down Expand Up @@ -207,11 +198,14 @@ static int tty_device_open(int major, int minor, struct fd *fd) {

static int tty_close(struct fd *fd) {
if (fd->tty != NULL) {
lock(&fd->tty->fds_lock);
struct tty *tty = fd->tty;
lock(&tty->fds_lock);
list_remove_safe(&fd->tty_other_fds);
unlock(&fd->tty->fds_lock);
unlock(&tty->fds_lock);
lock(&ttys_lock);
tty_release(fd->tty);
if (tty->driver->ops->close)
tty->driver->ops->close(tty);
tty_release(tty);
unlock(&ttys_lock);
}
return 0;
Expand Down Expand Up @@ -414,7 +408,7 @@ static bool pty_is_half_closed_master(struct tty *tty) {
struct tty *slave = tty->pty.other;
// only time one tty lock is nested in another
lock(&slave->lock);
bool half_closed = slave->ever_opened && slave->refcount == 1;
bool half_closed = slave->ever_opened && (slave->refcount == 1 || slave->hung_up);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the refcount check is still necessary? Would the new pty_slave_close do that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed when pty_slave_cleanup() runs, I think. It calls pty_hangup_other(), which calls tty_hangup() on the master. That calls tty_input_wakeup() on the master, which may result in that user program waking up in tty_read(), or calling read() again, before the pty slave device instance is fully cleaned up and the refcount decremented. That's what I remember, anyway.

unlock(&slave->lock);
return half_closed;
}
Expand Down Expand Up @@ -450,7 +444,7 @@ static ssize_t tty_read(struct fd *fd, void *buf, size_t bufsize) {
struct tty *tty = fd->tty;
lock(&pids_lock);
lock(&tty->lock);
if (tty->hung_up) {
if (tty->hung_up || pty_is_half_closed_master(tty)) {
unlock(&pids_lock);
goto error;
}
Expand Down Expand Up @@ -546,7 +540,7 @@ static ssize_t tty_read(struct fd *fd, void *buf, size_t bufsize) {
static ssize_t tty_write(struct fd *fd, const void *buf, size_t bufsize) {
struct tty *tty = fd->tty;
lock(&tty->lock);
if (tty->hung_up) {
if (tty->hung_up || pty_is_half_closed_master(tty)) {
unlock(&tty->lock);
return _EIO;
}
Expand Down Expand Up @@ -672,7 +666,7 @@ static int tiocgpgrp(struct tty *tty, pid_t_ *fg_group) {
lock(&slave->lock);
}

if (tty == slave && !tty_is_current(slave) || slave->fg_group == 0) {
if (tty == slave && (!tty_is_current(slave) || slave->fg_group == 0)) {
err = _ENOTTY;
goto error_no_ctrl_tty;
}
Expand Down Expand Up @@ -800,7 +794,7 @@ void tty_set_winsize(struct tty *tty, struct winsize_ winsize) {

void tty_hangup(struct tty *tty) {
tty->hung_up = true;
tty_poll_wakeup(tty, POLL_READ | POLL_WRITE | POLL_ERR | POLL_HUP);
tty_input_wakeup(tty);
}

struct dev_ops tty_dev = {
Expand Down
1 change: 1 addition & 0 deletions fs/tty.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ struct tty_driver {
struct tty_driver_ops {
int (*init)(struct tty *tty);
int (*open)(struct tty *tty);
int (*close)(struct tty *tty);
int (*write)(struct tty *tty, const void *buf, size_t len, bool blocking);
int (*ioctl)(struct tty *tty, int cmd, void *arg);
void (*cleanup)(struct tty *tty);
Expand Down
1 change: 1 addition & 0 deletions ish-lldb.lldb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
process handle -n 0 -p 1 -s 0 SIGUSR1 SIGTTIN SIGPIPE
28 changes: 22 additions & 6 deletions kernel/exit.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,28 @@ noreturn void do_exit_group(int status) {

// always called from init process
static void halt_system(void) {
// brutally murder everything
// which will leave everything in an inconsistent state. I will solve this problem later.
for (int i = 2; i < MAX_PID; i++) {
struct task *task = pid_get_task(i);
if (task != NULL)
pthread_kill(task->thread, SIGKILL);
for (int state = 0; state < 3; state++) {
int tasks_found = 0;
for (int i = 2; i < MAX_PID; i++) {
struct task *task = pid_get_task(i);
if (task != NULL) {
tasks_found++;
switch (state) {
case 0:
deliver_signal(task, SIGTERM_, SIGINFO_NIL);
break;
case 1:
deliver_signal(task, SIGKILL_, SIGINFO_NIL);
break;
case 2:
pthread_kill(task->thread, SIGTERM);
}
}
}
if (tasks_found == 0)
break;
if (state != 2)
sleep(1);
}

// unmount all filesystems
Expand Down
4 changes: 4 additions & 0 deletions kernel/log.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ static void log_line(const char *line) {
static void log_line(const char *line) {
os_log_fault(OS_LOG_DEFAULT, "%s", line);
}
#elif LOG_HANDLER_STDERR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unclear on the utility of this since the program in ish may also be writing to stderr, but if you found it useful it doesn't hurt to have.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If iSH is running interactively (stdin and stdout are on a tty), then emulated programs are running with both stdout and stderr on the iSH tty driver. (This will be true for iSH.app as well, but writing to stderr might be less useful there.)

I hadn't even thought of CLI iSH supporting non-interactive use but yes, it does support that, and logging to stderr would be inappropriate then.

static void log_line(const char *line) {
fprintf(stderr, "%s\n", line);
}
#endif

static void default_die_handler(const char *msg) {
Expand Down
Loading