LCOV - code coverage report
Current view: top level - libs/libptytest - pty_sync.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 85.1 % 74 63
Test Date: 2026-05-06 13:17:06 Functions: 85.7 % 7 6

            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              : #include <sys/wait.h>
      16              : 
      17              : /* ── Time helpers ────────────────────────────────────────────────────── */
      18              : 
      19          924 : static long now_ms(void) {
      20              :     struct timespec ts;
      21          924 :     clock_gettime(CLOCK_MONOTONIC, &ts);
      22          924 :     return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
      23              : }
      24              : 
      25              : /* ── Read and feed ───────────────────────────────────────────────────── */
      26              : 
      27              : int g_trace_active = 0;
      28            0 : void pty_trace_enable(int on) { g_trace_active = on; }
      29              : 
      30              : /** @brief Reads available data from master fd and feeds to screen. */
      31          851 : static int read_and_feed(PtySession *s) {
      32              :     char buf[8192];
      33          851 :     ssize_t n = read(s->master_fd, buf, sizeof(buf));
      34          851 :     if (n > 0) {
      35          487 :         if (g_trace_active) {
      36            0 :             printf("  [TRACE %zd bytes]:", n);
      37            0 :             for (ssize_t i = 0; i < n && i < 120; i++) {
      38            0 :                 unsigned char c = (unsigned char)buf[i];
      39            0 :                 if (c == 0x1B) printf(" ESC");
      40            0 :                 else if (c < 0x20 || c > 0x7E) printf(" %02X", c);
      41            0 :                 else printf(" %c", c);
      42              :             }
      43            0 :             printf("\n"); fflush(stdout);
      44              :         }
      45          487 :         pty_screen_feed(s->screen, buf, (size_t)n);
      46          487 :         return (int)n;
      47              :     }
      48              :     /* Return -2 to distinguish EAGAIN from EIO/EOF */
      49          364 :     if (n < 0 && errno == EAGAIN) return -2;
      50          245 :     return 0;
      51              : }
      52              : 
      53          254 : int pty_drain(PtySession *s) {
      54          254 :     if (!s || s->master_fd < 0) return 0;
      55          254 :     int total = 0;
      56              :     /* Retry on EAGAIN — kernel may need a moment to deliver buffered PTY data
      57              :      * after the slave side closes (POLLHUP race on non-blocking master fds). */
      58          364 :     for (int attempt = 0; attempt < 3; attempt++) {
      59           64 :         for (;;) {
      60          428 :             int n = read_and_feed(s);
      61          428 :             if (n > 0) { total += n; continue; }
      62          364 :             if (n == -2) break; /* EAGAIN: no data right now, retry after sleep */
      63          245 :             return total;       /* EIO/EOF: slave closed and buffer empty */
      64              :         }
      65          119 :         if (total > 0) break;   /* Got data — no need to retry */
      66          110 :         usleep(5000);           /* 5 ms: give kernel time to deliver buffered data */
      67              :     }
      68            9 :     return total;
      69              : }
      70              : 
      71              : /* ── Synchronisation ─────────────────────────────────────────────────── */
      72              : 
      73          382 : int pty_wait_for(PtySession *s, const char *text, int timeout_ms) {
      74          382 :     if (!s || s->master_fd < 0) return -1;
      75              : 
      76          382 :     long deadline = now_ms() + timeout_ms;
      77          382 :     struct pollfd pfd = { .fd = s->master_fd, .events = POLLIN };
      78              : 
      79          406 :     for (;;) {
      80              :         /* Check if text is already on screen */
      81          788 :         if (pty_screen_contains(s, text)) return 0;
      82              : 
      83          414 :         long remaining = deadline - now_ms();
      84          414 :         if (remaining <= 0) return -1; /* Timeout */
      85              : 
      86          414 :         int ret = poll(&pfd, 1, (int)remaining);
      87          414 :         if (ret > 0 && (pfd.revents & POLLIN)) {
      88          406 :             read_and_feed(s);
      89            8 :         } else if (ret == 0) {
      90            0 :             return -1; /* Timeout */
      91              :         } else {
      92              :             /* POLLERR/POLLHUP — child may have exited; drain any buffered output */
      93            8 :             pty_drain(s);
      94            8 :             if (pty_screen_contains(s, text)) return 0;
      95              :             /* One more short drain in case the kernel delivered data after the HUP */
      96            8 :             usleep(10000);
      97            8 :             pty_drain(s);
      98            8 :             return pty_screen_contains(s, text) ? 0 : -1;
      99              :         }
     100              :     }
     101              : }
     102              : 
     103           29 : int pty_settle(PtySession *s, int quiet_ms) {
     104           29 :     if (!s || s->master_fd < 0) return -1;
     105              : 
     106           29 :     struct pollfd pfd = { .fd = s->master_fd, .events = POLLIN };
     107              : 
     108           17 :     for (;;) {
     109           46 :         int ret = poll(&pfd, 1, quiet_ms);
     110           46 :         if (ret == 0) return 0; /* Quiet period elapsed — settled */
     111           17 :         if (ret > 0 && (pfd.revents & POLLIN)) {
     112           17 :             if (read_and_feed(s) <= 0) return -1; /* EOF — child exited */
     113              :         } else {
     114            0 :             return -1; /* Error */
     115              :         }
     116              :     }
     117              : }
     118              : 
     119          119 : int pty_wait_exit(PtySession *s, int timeout_ms) {
     120          119 :     if (!s || s->child_pid <= 0) return -1;
     121              : 
     122              :     /* Drain any remaining output first so the screen buffer is up to date. */
     123          119 :     pty_drain(s);
     124              : 
     125          119 :     long deadline = now_ms() + timeout_ms;
     126            9 :     for (;;) {
     127          128 :         int status = 0;
     128          128 :         pid_t rc = waitpid(s->child_pid, &status, WNOHANG);
     129          128 :         if (rc == s->child_pid) {
     130              :             /* Drain once more to capture any output flushed just before exit. */
     131          119 :             pty_drain(s);
     132          119 :             s->child_pid = -1;
     133          124 :             if (WIFEXITED(status)) return WEXITSTATUS(status);
     134            5 :             if (WIFSIGNALED(status)) return 128 + WTERMSIG(status);
     135            0 :             return -1;
     136              :         }
     137            9 :         if (rc < 0) return -1;
     138            9 :         if (now_ms() >= deadline) return -1;
     139            9 :         usleep(10000); /* poll at 10 ms intervals */
     140              :     }
     141              : }
        

Generated by: LCOV version 2.0-1