Line data Source code
1 : /**
2 : * @file pty_sync.c
3 : * @brief PTY output reading and synchronisation (poll + timeout).
4 : */
5 :
6 : #define _DEFAULT_SOURCE
7 : #define _XOPEN_SOURCE 600
8 :
9 : #include "pty_internal.h"
10 : #include <errno.h>
11 : #include <poll.h>
12 : #include <stdio.h>
13 : #include <unistd.h>
14 : #include <time.h>
15 :
16 : /* ── Time helpers ────────────────────────────────────────────────────── */
17 :
18 178300 : static long now_ms(void) {
19 : struct timespec ts;
20 178300 : clock_gettime(CLOCK_MONOTONIC, &ts);
21 178300 : return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
22 : }
23 :
24 : /* ── Read and feed ───────────────────────────────────────────────────── */
25 :
26 : int g_trace_active = 0;
27 0 : void pty_trace_enable(int on) { g_trace_active = on; }
28 :
29 : /** @brief Reads available data from master fd and feeds to screen. */
30 790595 : static int read_and_feed(PtySession *s) {
31 : char buf[8192];
32 790595 : ssize_t n = read(s->master_fd, buf, sizeof(buf));
33 790595 : if (n > 0) {
34 790089 : if (g_trace_active) {
35 0 : printf(" [TRACE %zd bytes]:", n);
36 0 : for (ssize_t i = 0; i < n && i < 120; i++) {
37 0 : unsigned char c = (unsigned char)buf[i];
38 0 : if (c == 0x1B) printf(" ESC");
39 0 : else if (c < 0x20 || c > 0x7E) printf(" %02X", c);
40 0 : else printf(" %c", c);
41 : }
42 0 : printf("\n"); fflush(stdout);
43 : }
44 790089 : pty_screen_feed(s->screen, buf, (size_t)n);
45 790089 : return (int)n;
46 : }
47 : /* Return -2 to distinguish EAGAIN from EIO/EOF */
48 506 : if (n < 0 && errno == EAGAIN) return -2;
49 506 : return 0;
50 : }
51 :
52 506 : int pty_drain(PtySession *s) {
53 506 : if (!s || s->master_fd < 0) return 0;
54 506 : int total = 0;
55 : /* Retry on EAGAIN — kernel may need a moment to deliver buffered PTY data
56 : * after the slave side closes (POLLHUP race on non-blocking master fds). */
57 506 : for (int attempt = 0; attempt < 3; attempt++) {
58 0 : for (;;) {
59 506 : int n = read_and_feed(s);
60 506 : if (n > 0) { total += n; continue; }
61 506 : if (n == -2) break; /* EAGAIN: no data right now, retry after sleep */
62 506 : return total; /* EIO/EOF: slave closed and buffer empty */
63 : }
64 0 : if (total > 0) break; /* Got data — no need to retry */
65 0 : usleep(5000); /* 5 ms: give kernel time to deliver buffered data */
66 : }
67 0 : return total;
68 : }
69 :
70 : /* ── Synchronisation ─────────────────────────────────────────────────── */
71 :
72 62359 : int pty_wait_for(PtySession *s, const char *text, int timeout_ms) {
73 62359 : if (!s || s->master_fd < 0) return -1;
74 :
75 62359 : long deadline = now_ms() + timeout_ms;
76 62359 : struct pollfd pfd = { .fd = s->master_fd, .events = POLLIN };
77 :
78 115678 : for (;;) {
79 : /* Check if text is already on screen */
80 178037 : if (pty_screen_contains(s, text)) return 0;
81 :
82 115941 : long remaining = deadline - now_ms();
83 115941 : if (remaining <= 0) return -1; /* Timeout */
84 :
85 115941 : int ret = poll(&pfd, 1, (int)remaining);
86 115941 : if (ret > 0 && (pfd.revents & POLLIN)) {
87 115678 : read_and_feed(s);
88 263 : } else if (ret == 0) {
89 10 : return -1; /* Timeout */
90 : } else {
91 : /* POLLERR/POLLHUP — child may have exited; drain any buffered output */
92 253 : pty_drain(s);
93 253 : if (pty_screen_contains(s, text)) return 0;
94 : /* One more short drain in case the kernel delivered data after the HUP */
95 253 : usleep(10000);
96 253 : pty_drain(s);
97 253 : return pty_screen_contains(s, text) ? 0 : -1;
98 : }
99 : }
100 : }
101 :
102 36831 : int pty_settle(PtySession *s, int quiet_ms) {
103 36831 : if (!s || s->master_fd < 0) return -1;
104 :
105 36831 : struct pollfd pfd = { .fd = s->master_fd, .events = POLLIN };
106 :
107 674411 : for (;;) {
108 711242 : int ret = poll(&pfd, 1, quiet_ms);
109 711242 : if (ret == 0) return 0; /* Quiet period elapsed — settled */
110 679522 : if (ret > 0 && (pfd.revents & POLLIN)) {
111 674411 : if (read_and_feed(s) <= 0) return -1; /* EOF — child exited */
112 : } else {
113 5111 : return -1; /* Error */
114 : }
115 : }
116 : }
|