LCOV - code coverage report
Current view: top level - libs/libptytest - pty_sync.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 79.7 % 74 59
Test Date: 2026-04-20 19:54:22 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          947 : static long now_ms(void) {
      20              :     struct timespec ts;
      21          947 :     clock_gettime(CLOCK_MONOTONIC, &ts);
      22          947 :     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          890 : static int read_and_feed(PtySession *s) {
      32              :     char buf[8192];
      33          890 :     ssize_t n = read(s->master_fd, buf, sizeof(buf));
      34          890 :     if (n > 0) {
      35          515 :         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          515 :         pty_screen_feed(s->screen, buf, (size_t)n);
      46          515 :         return (int)n;
      47              :     }
      48              :     /* Return -2 to distinguish EAGAIN from EIO/EOF */
      49          375 :     if (n < 0 && errno == EAGAIN) return -2;
      50          248 :     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          375 :     for (int attempt = 0; attempt < 3; attempt++) {
      59           68 :         for (;;) {
      60          443 :             int n = read_and_feed(s);
      61          443 :             if (n > 0) { total += n; continue; }
      62          375 :             if (n == -2) break; /* EAGAIN: no data right now, retry after sleep */
      63          248 :             return total;       /* EIO/EOF: slave closed and buffer empty */
      64              :         }
      65          127 :         if (total > 0) break;   /* Got data — no need to retry */
      66          121 :         usleep(5000);           /* 5 ms: give kernel time to deliver buffered data */
      67              :     }
      68            6 :     return total;
      69              : }
      70              : 
      71              : /* ── Synchronisation ─────────────────────────────────────────────────── */
      72              : 
      73          388 : int pty_wait_for(PtySession *s, const char *text, int timeout_ms) {
      74          388 :     if (!s || s->master_fd < 0) return -1;
      75              : 
      76          388 :     long deadline = now_ms() + timeout_ms;
      77          388 :     struct pollfd pfd = { .fd = s->master_fd, .events = POLLIN };
      78              : 
      79          425 :     for (;;) {
      80              :         /* Check if text is already on screen */
      81          813 :         if (pty_screen_contains(s, text)) return 0;
      82              : 
      83          426 :         long remaining = deadline - now_ms();
      84          426 :         if (remaining <= 0) return -1; /* Timeout */
      85              : 
      86          426 :         int ret = poll(&pfd, 1, (int)remaining);
      87          426 :         if (ret > 0 && (pfd.revents & POLLIN)) {
      88          425 :             read_and_feed(s);
      89            1 :         } else if (ret == 0) {
      90            1 :             return -1; /* Timeout */
      91              :         } else {
      92              :             /* POLLERR/POLLHUP — child may have exited; drain any buffered output */
      93            0 :             pty_drain(s);
      94            0 :             if (pty_screen_contains(s, text)) return 0;
      95              :             /* One more short drain in case the kernel delivered data after the HUP */
      96            0 :             usleep(10000);
      97            0 :             pty_drain(s);
      98            0 :             return pty_screen_contains(s, text) ? 0 : -1;
      99              :         }
     100              :     }
     101              : }
     102              : 
     103           31 : int pty_settle(PtySession *s, int quiet_ms) {
     104           31 :     if (!s || s->master_fd < 0) return -1;
     105              : 
     106           31 :     struct pollfd pfd = { .fd = s->master_fd, .events = POLLIN };
     107              : 
     108           22 :     for (;;) {
     109           53 :         int ret = poll(&pfd, 1, quiet_ms);
     110           53 :         if (ret == 0) return 0; /* Quiet period elapsed — settled */
     111           22 :         if (ret > 0 && (pfd.revents & POLLIN)) {
     112           22 :             if (read_and_feed(s) <= 0) return -1; /* EOF — child exited */
     113              :         } else {
     114            0 :             return -1; /* Error */
     115              :         }
     116              :     }
     117              : }
     118              : 
     119          127 : int pty_wait_exit(PtySession *s, int timeout_ms) {
     120          127 :     if (!s || s->child_pid <= 0) return -1;
     121              : 
     122              :     /* Drain any remaining output first so the screen buffer is up to date. */
     123          127 :     pty_drain(s);
     124              : 
     125          127 :     long deadline = now_ms() + timeout_ms;
     126            6 :     for (;;) {
     127          133 :         int status = 0;
     128          133 :         pid_t rc = waitpid(s->child_pid, &status, WNOHANG);
     129          133 :         if (rc == s->child_pid) {
     130              :             /* Drain once more to capture any output flushed just before exit. */
     131          127 :             pty_drain(s);
     132          127 :             s->child_pid = -1;
     133          132 :             if (WIFEXITED(status)) return WEXITSTATUS(status);
     134            5 :             if (WIFSIGNALED(status)) return 128 + WTERMSIG(status);
     135            0 :             return -1;
     136              :         }
     137            6 :         if (rc < 0) return -1;
     138            6 :         if (now_ms() >= deadline) return -1;
     139            6 :         usleep(10000); /* poll at 10 ms intervals */
     140              :     }
     141              : }
        

Generated by: LCOV version 2.0-1