From d8b3df77ecc9d7ea05101c0cfdccea469c176e71 Mon Sep 17 00:00:00 2001 From: John Hood Date: Sat, 9 Mar 2024 06:20:11 -0500 Subject: [PATCH 1/6] Fix an operator precedence warning from clang. --- fs/tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/tty.c b/fs/tty.c index 9cd3a08c98..0d6cf4f63b 100644 --- a/fs/tty.c +++ b/fs/tty.c @@ -672,7 +672,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; } From ebaba89e11cc3cf12b4f45761f315ab8532af20a Mon Sep 17 00:00:00 2001 From: John Hood Date: Thu, 14 Mar 2024 23:58:13 -0400 Subject: [PATCH 2/6] Add another very simple log destination-- fprintf to stderr --- kernel/log.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/log.c b/kernel/log.c index 2b58d47dfb..6b019f658e 100644 --- a/kernel/log.c +++ b/kernel/log.c @@ -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 +static void log_line(const char *line) { + fprintf(stderr, "%s\n", line); +} #endif static void default_die_handler(const char *msg) { From edc6016db00c6fb325acfb73e0c1f017ec8bfb95 Mon Sep 17 00:00:00 2001 From: John Hood Date: Fri, 10 May 2024 22:04:35 -0400 Subject: [PATCH 3/6] Add basic LLDB config --- ish-lldb.lldb | 1 + 1 file changed, 1 insertion(+) create mode 100644 ish-lldb.lldb diff --git a/ish-lldb.lldb b/ish-lldb.lldb new file mode 100644 index 0000000000..b23802fa50 --- /dev/null +++ b/ish-lldb.lldb @@ -0,0 +1 @@ +process handle -n 0 -p 1 -s 0 SIGUSR1 SIGTTIN SIGPIPE From fc43939a21bd949019fbca6d44e747301c8da5e7 Mon Sep 17 00:00:00 2001 From: John Hood Date: Sat, 9 Mar 2024 06:11:39 -0500 Subject: [PATCH 4/6] Much improved pty close handling. Fixes mosh-server, dtach. Many pty-using programs use wait() or SIGCHLD to detect child termination, but some expect the pty master to close and return EOF when all children close the slave pty. This did not work on iSH. If the slave pty was closed by the last program exiting, the program on the master would not receive any read/poll wakeups, and the master pty would not be hung up. Reference counting wasn't working properly when the pty was in a process group or session. The fix for that is a bit simplistic, but it's a big improvement. This also improves the tty/pty code separation and fixes some other minor things. --- fs/pty.c | 38 ++++++++++++++++++++++++++++++++++---- fs/tty.c | 26 ++++++++++---------------- fs/tty.h | 1 + 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/fs/pty.c b/fs/pty.c index af0b117f87..1b272296cf 100644 --- a/fs/pty.c +++ b/fs/pty.c @@ -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); } @@ -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)) { + 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) { @@ -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); diff --git a/fs/tty.c b/fs/tty.c index 0d6cf4f63b..3a70ca2943 100644 --- a/fs/tty.c +++ b/fs/tty.c @@ -110,16 +110,7 @@ void tty_release(struct tty *tty) { cond_destroy(&tty->produced); free(tty); } else { - // bit of a hack - 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); - } } } @@ -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; @@ -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); unlock(&slave->lock); return half_closed; } @@ -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; } @@ -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; } @@ -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 = { diff --git a/fs/tty.h b/fs/tty.h index 400e408cf0..ff70dc4235 100644 --- a/fs/tty.h +++ b/fs/tty.h @@ -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); From 6e5cdaff5f749744027439fbd87d8cd422d521bb Mon Sep 17 00:00:00 2001 From: John Hood Date: Wed, 13 Mar 2024 17:01:58 -0400 Subject: [PATCH 5/6] Shutdown more cleanly when init dies. Using SIGKILL on iSh itself was never going to end well. :) --- kernel/exit.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index efac2f9c84..2a6de25af5 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -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 From 7cc11f0fb9a49bd64c61ce6f13491cd97550df05 Mon Sep 17 00:00:00 2001 From: John Hood Date: Fri, 10 May 2024 22:32:29 -0400 Subject: [PATCH 6/6] Update READMEs to say `/bin/sh` rather than `bin/login`, for CLI ish --- README.md | 2 +- README_KO.md | 4 ++-- README_ZH.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 570b209ac0..3d39691140 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/README_KO.md b/README_KO.md index 29092ef193..f4aad23198 100644 --- a/README_KO.md +++ b/README_KO.md @@ -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 이후 버전이 필요합니다. @@ -67,4 +67,4 @@ iSH에서 추가한 것 중 가장 흥미로운 것은 JIT 컴파일러 일 것 불행하게도 저는 어셈블리어로 대부분의 이러한 gadget을 작성했습니다. 이것은 성능적으로는 좋은 선택이었을 지 몰라도(실제로는 알 도리가 없지만), 가독성, 유지보수, 그리고 제 정신상태에 대해서는 좋지 않은 선택이 되었습니다. 컴파일러/어셈블러/링커로 인한 여러 고충은 말도 할 수 없을 정도입니다. 거의 무슨 제 코드의 가독성을 해치지 않으면 컴파일을 막는 그러한 악마가 있는 것 같았습니다. 이 코드를 작성하는 도중 제정신을 유지하기 위해서 저는 네이밍과 코드 구조론을 따른 최적의 선택을 하지 못하였습니다. `ss`, `s` 그리고 `a`와 같은 매크로 그리고 변수 명을 찾을 수 있을 것입니다. 주석 또한 찾기 힘들 것입니다. -그렇기에 주의 하세요: 해당 코드를 장기간 접할 경우 정신질환을 앓게되거나 GAS 매크로와 링커오류에 대한 악몽에 시달리고 또다른 부작용이 있을 수 있습니다. 암, 선천적 결함, 또는 생식기 질환을 야기한다고 질병관리청에서 인정했습니다. 암튼 그랬습니다. \ No newline at end of file +그렇기에 주의 하세요: 해당 코드를 장기간 접할 경우 정신질환을 앓게되거나 GAS 매크로와 링커오류에 대한 악몽에 시달리고 또다른 부작용이 있을 수 있습니다. 암, 선천적 결함, 또는 생식기 질환을 야기한다고 질병관리청에서 인정했습니다. 암튼 그랬습니다. diff --git a/README_ZH.md b/README_ZH.md index e067563a3e..dd7039daab 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -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 或更高版本)。 @@ -64,4 +64,4 @@ iSH 是一个运行在 iOS 上的 Linux shell。本项目使用了 x86 用户模 但不幸的是,我最开始决定用汇编语言编写几乎所有的 gadgets。这可能从性能方面来说是一个好的决定(虽然我永远也无法确定),但是对可读性、可维护性和我的理智来说,这是一个可怕的决定。我承受了大量来自编译器、汇编程序以及链接器的乱七八糟的东西。那里面就像有一个魔鬼,把我的代码搞得畸形,就算没有畸形,也会编造一些愚蠢的理由说它不能够编译。为了在编写代码时保持理智,我不得不忽略代码结构和命名方面的最佳实践。你会发现宏和变量具有诸如 `ss`、`s` 和 `a` 等描述性的名称,并且汇编器的宏嵌套层数超乎你的想象。最重要的是,代码中几乎没有任何注释。 -所以这是一个警告: 长期接触此代码可能会使你失去理智,对 GAS 宏和链接器错误产生噩梦,或是任何其他使人虚弱的副作用。在加利福尼亚,众所周知这样的代码会导致癌症、生产缺陷和重复伤害。 \ No newline at end of file +所以这是一个警告: 长期接触此代码可能会使你失去理智,对 GAS 宏和链接器错误产生噩梦,或是任何其他使人虚弱的副作用。在加利福尼亚,众所周知这样的代码会导致癌症、生产缺陷和重复伤害。