| /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
| |
| /*** |
| This file is part of systemd. |
| |
| Copyright 2014 David Herrmann <dh.herrmann@gmail.com> |
| |
| systemd is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2.1 of the License, or |
| (at your option) any later version. |
| |
| systemd is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with systemd; If not, see <http://www.gnu.org/licenses/>. |
| ***/ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <locale.h> |
| #include <string.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include "def.h" |
| #include "pty.h" |
| #include "util.h" |
| |
| static const char sndmsg[] = "message\n"; |
| static const char rcvmsg[] = "message\r\n"; |
| static char rcvbuf[128]; |
| static size_t rcvsiz = 0; |
| static sd_event *event; |
| |
| static void run_child(Pty *pty) { |
| ssize_t r, l; |
| char buf[512]; |
| |
| r = read(0, buf, sizeof(buf)); |
| assert_se((size_t)r == strlen(sndmsg)); |
| assert_se(!strncmp(buf, sndmsg, r)); |
| |
| l = write(1, buf, r); |
| assert_se(l == r); |
| } |
| |
| static int pty_fn(Pty *pty, void *userdata, unsigned int ev, const void *ptr, size_t size) { |
| switch (ev) { |
| case PTY_DATA: |
| assert_se(rcvsiz < strlen(rcvmsg) * 2); |
| assert_se(rcvsiz + size < sizeof(rcvbuf)); |
| |
| memcpy(&rcvbuf[rcvsiz], ptr, size); |
| rcvsiz += size; |
| |
| if (rcvsiz >= strlen(rcvmsg) * 2) { |
| assert_se(rcvsiz == strlen(rcvmsg) * 2); |
| assert_se(!memcmp(rcvbuf, rcvmsg, strlen(rcvmsg))); |
| assert_se(!memcmp(&rcvbuf[strlen(rcvmsg)], rcvmsg, strlen(rcvmsg))); |
| } |
| |
| break; |
| case PTY_HUP: |
| /* This is guaranteed to appear _after_ the input queues are |
| * drained! */ |
| assert_se(rcvsiz == strlen(rcvmsg) * 2); |
| break; |
| case PTY_CHILD: |
| /* this may appear at any time */ |
| break; |
| default: |
| assert_se(0); |
| break; |
| } |
| |
| /* if we got HUP _and_ CHILD, exit */ |
| if (pty_get_fd(pty) < 0 && pty_get_child(pty) < 0) |
| sd_event_exit(event, 0); |
| |
| return 0; |
| } |
| |
| static void run_parent(Pty *pty) { |
| int r; |
| |
| /* write message to pty, ECHO mode guarantees that we get it back |
| * twice: once via ECHO, once from the run_child() fn */ |
| assert_se(pty_write(pty, sndmsg, strlen(sndmsg)) >= 0); |
| |
| r = sd_event_loop(event); |
| assert_se(r >= 0); |
| } |
| |
| static void test_pty(void) { |
| pid_t pid; |
| Pty *pty; |
| |
| rcvsiz = 0; |
| memset(rcvbuf, 0, sizeof(rcvbuf)); |
| |
| assert_se(sd_event_default(&event) >= 0); |
| |
| pid = pty_fork(&pty, event, pty_fn, NULL, 80, 25); |
| assert_se(pid >= 0); |
| |
| if (pid == 0) { |
| /* child */ |
| run_child(pty); |
| exit(0); |
| } |
| |
| /* parent */ |
| run_parent(pty); |
| |
| /* Make sure the PTY recycled the child; yeah, this is racy if the |
| * PID was already reused; but that seems fine for a test. */ |
| assert_se(waitpid(pid, NULL, WNOHANG) < 0 && errno == ECHILD); |
| |
| pty_unref(pty); |
| sd_event_unref(event); |
| } |
| |
| int main(int argc, char *argv[]) { |
| unsigned int i; |
| |
| log_parse_environment(); |
| log_open(); |
| |
| assert_se(sigprocmask_many(SIG_BLOCK, SIGCHLD, -1) >= 0); |
| |
| /* Oh, there're ugly races in the TTY layer regarding HUP vs IN. Turns |
| * out they appear only 10% of the time. I fixed all of them and |
| * don't see them, anymore. But lets be safe and run this 1000 times |
| * so we catch any new ones, in case they appear again. */ |
| for (i = 0; i < 1000; ++i) |
| test_pty(); |
| |
| return 0; |
| } |