LCOV - code coverage report
Current view: top level - libs/libptytest - pty_sync.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 79.3 % 58 46
Test Date: 2026-05-07 15:53:08 Functions: 83.3 % 6 5

            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              : }
        

Generated by: LCOV version 2.0-1