diff --git a/test/apps/process/job_control.c b/test/apps/process/job_control.c new file mode 100644 index 000000000..f590280f5 --- /dev/null +++ b/test/apps/process/job_control.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: MPL-2.0 + +#include "../network/test.h" + +#include +#include +#include + +static pid_t sid, child; +static int master, slave; + +FN_SETUP(openpty) +{ + CHECK(openpty(&master, &slave, NULL, NULL, NULL)); +} +END_SETUP() + +FN_SETUP(run_in_new_session) +{ + int status; + + if (CHECK(fork()) != 0) { + CHECK_WITH(wait(&status), + WIFEXITED(status) && WEXITSTATUS(status) == 0); + exit(EXIT_SUCCESS); + } + + sid = CHECK(setsid()); + + if ((child = CHECK(fork())) == 0) { + child = getpid(); + + // TODO: Linux allows to specify PIDs (instead of PGIDs) as + // parameters to `TIOCSPGRP`. We may want to support this as + // well. For more details, see: + // . + CHECK(setpgrp()); + } + + signal(SIGHUP, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + + // TODO: We should forbid some TTY operations (e.g., `TIOCSPGRP`) if + // the `SIGTTOU` signal is not blocked or ignored and the current + // process is not in the foreground process group. However, this is + // not currently implemented in Asterinas yet. + signal(SIGTTOU, SIG_IGN); +} +END_SETUP() + +// From now on, we'll run all the tests twice, once as a session leader +// and once not as a session leader. Most tests will only work in one of +// the two cases, so check at the beginning and skip the test if it's +// not the expected case. +static int is_leader(void) +{ + return getpid() == sid; +} + +// TODO: Find a better way to synchronize between the two processes. +#define __NAMED_BARRIER(name) \ + FN_SETUP(name) \ + { \ + CHECK(usleep(100 * 1000)); \ + } \ + END_SETUP() +#define NAMED_BARRIER(name) __NAMED_BARRIER(__CONCAT(barrier, name)) +#define BARRIER(name) NAMED_BARRIER(__LINE__) + +FN_TEST(not_our_tty) +{ + pid_t arg; + + // Operate on the controlling session + + TEST_ERRNO(ioctl(master, TIOCNOTTY), ENOTTY); + TEST_ERRNO(ioctl(slave, TIOCNOTTY), ENOTTY); + TEST_ERRNO(ioctl(STDIN_FILENO, TIOCNOTTY), ENOTTY); + + TEST_ERRNO(ioctl(master, TIOCGSID, &arg), ENOTTY); + TEST_ERRNO(ioctl(slave, TIOCGSID, &arg), ENOTTY); + TEST_ERRNO(ioctl(STDIN_FILENO, TIOCGSID, &arg), ENOTTY); + + // Operate on the foreground process group + + arg = 0xdeadbeef; + TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == 0); + TEST_ERRNO(ioctl(slave, TIOCGPGRP, &arg), ENOTTY); + TEST_ERRNO(ioctl(STDIN_FILENO, TIOCGPGRP, &arg), ENOTTY); + + TEST_ERRNO(ioctl(master, TIOCSPGRP, &arg), ENOTTY); + TEST_ERRNO(ioctl(slave, TIOCSPGRP, &arg), ENOTTY); + TEST_ERRNO(ioctl(STDIN_FILENO, TIOCSPGRP, &arg), ENOTTY); +} +END_TEST() + +FN_TEST(nonleader_set_tty) +{ + if (is_leader()) + return; + + // Only session leaders can use `TIOCSCTTY`, but we're not the leader + TEST_ERRNO(ioctl(master, TIOCSCTTY, 0), EPERM); + TEST_ERRNO(ioctl(slave, TIOCSCTTY, 0), EPERM); + TEST_ERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0), EPERM); +} +END_TEST() + +BARRIER() + +FN_TEST(leader_set_unset_tty) +{ + pid_t arg; + + if (!is_leader()) + return; + + // We can use `TIOCSCTTY` on the master PTY + TEST_SUCC(ioctl(master, TIOCSCTTY, 0)); + arg = 0xdeadbeef; + TEST_RES(ioctl(master, TIOCGSID, &arg), arg == sid); + arg = 0xdeadbeef; + TEST_RES(ioctl(slave, TIOCGSID, &arg), arg == sid); + + // `TIOCSCTTY` on the same TTY will succeed + TEST_SUCC(ioctl(master, TIOCSCTTY, 0)); + TEST_SUCC(ioctl(slave, TIOCSCTTY, 0)); + + // `TIOCSCTTY` on a different TTY will fail + TEST_ERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0), EPERM); + + // `TIOCNOTTY` to clear the associated TTY + TEST_ERRNO(ioctl(master, TIOCNOTTY), ENOTTY); + TEST_SUCC(ioctl(slave, TIOCNOTTY)); + TEST_ERRNO(ioctl(master, TIOCGSID, &arg), ENOTTY); + TEST_ERRNO(ioctl(slave, TIOCGSID, &arg), ENOTTY); + + // We can also use `TIOCSCTTY` on the slave PTY + TEST_SUCC(ioctl(slave, TIOCSCTTY, 0)); + arg = 0xdeadbeef; + TEST_RES(ioctl(master, TIOCGSID, &arg), arg == sid); + arg = 0xdeadbeef; + TEST_RES(ioctl(slave, TIOCGSID, &arg), arg == sid); +} +END_TEST() + +BARRIER() + +FN_TEST(nonleader_unset_tty) +{ + if (is_leader()) + return; + + // Only session leaders can use `TIOCNOTTY`, but we're not the leader + TEST_ERRNO(ioctl(master, TIOCNOTTY), ENOTTY); + TEST_ERRNO(ioctl(slave, TIOCNOTTY), ENOTTY); + TEST_ERRNO(ioctl(STDIN_FILENO, TIOCNOTTY), ENOTTY); +} +END_TEST() + +FN_TEST(query_foreground) +{ + pid_t arg; + + // All processes can use `TIOCGPGRP` on the master PTY + TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == sid); + + // Only session leaders can use `TIOCGPGRP` on the slave PTY + if (is_leader()) + TEST_RES(ioctl(slave, TIOCGPGRP, &arg), arg == sid); + else + TEST_ERRNO(ioctl(slave, TIOCGPGRP, &arg), ENOTTY); +} +END_TEST() + +BARRIER() + +FN_TEST(leader_set_foreground) +{ + pid_t arg; + + if (!is_leader()) + return; + + // Only session leaders can use `TIOCSPGRP`, and we're the leader + + arg = child; + TEST_SUCC(ioctl(master, TIOCSPGRP, &arg)); + arg = 0xdeadbeef; + TEST_RES(ioctl(slave, TIOCGPGRP, &arg), arg == child); + + arg = sid; + TEST_SUCC(ioctl(master, TIOCSPGRP, &arg)); + arg = 0xdeadbeef; + TEST_RES(ioctl(slave, TIOCGPGRP, &arg), arg == sid); + + arg = child; + TEST_SUCC(ioctl(slave, TIOCSPGRP, &arg)); + arg = 0xdeadbeef; + TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == child); + + arg = sid; + TEST_SUCC(ioctl(slave, TIOCSPGRP, &arg)); + arg = 0xdeadbeef; + TEST_RES(ioctl(master, TIOCGPGRP, &arg), arg == sid); +} +END_TEST() + +BARRIER() + +FN_TEST(nonleader_set_foreground) +{ + pid_t arg = child; + + if (is_leader()) + return; + + // Only session leaders can use `TIOCSPGRP`, but we're not the leader + + TEST_ERRNO(ioctl(master, TIOCSPGRP, &arg), ENOTTY); + TEST_ERRNO(ioctl(slave, TIOCSPGRP, &arg), ENOTTY); +} +END_TEST() + +FN_SETUP(cleanup) +{ + int status; + + if (!is_leader()) + return; + + CHECK_WITH(wait(&status), + WIFEXITED(status) && WEXITSTATUS(status) == 0); +} +END_SETUP() diff --git a/test/apps/scripts/process.sh b/test/apps/scripts/process.sh index 131be32f4..1cb307716 100755 --- a/test/apps/scripts/process.sh +++ b/test/apps/scripts/process.sh @@ -30,6 +30,7 @@ mmap/mmap_and_fork mmap/mmap_shared_filebacked mmap/mmap_readahead process/group_session +process/job_control pthread/pthread_test pty/open_pty sched/sched_attr