7070import ast
7171import errno
7272import os
73+ import stat
7374import struct
7475import sys
7576import time
@@ -333,6 +334,39 @@ def __init__(
333334 if delayed :
334335 print ("" )
335336
337+ # Detect if this is a PTY device (e.g., QEMU serial output)
338+ # PTY devices don't reliably report inWaiting() status, so we need
339+ # to use blocking reads instead of checking for data availability.
340+ # Only check for actual serial devices, not exec/telnet connections.
341+ if not (
342+ device .startswith ("exec:" )
343+ or device .startswith ("execpty:" )
344+ or (device and device [0 ].isdigit () and device [- 1 ].isdigit () and device .count ("." ) == 3 )
345+ ):
346+ self .is_pty = self ._is_pty_device (device )
347+ else :
348+ self .is_pty = False
349+
350+ def _is_pty_device (self , device ):
351+ """
352+ Detect if device is a PTY (pseudo-terminal).
353+
354+ PTY devices are commonly used by emulators like QEMU. Unlike real serial
355+ devices, PTY inWaiting() may not report data availability correctly,
356+ requiring use of blocking reads instead.
357+ """
358+ try :
359+ # Linux Unix98 PTY pattern: /dev/pts/N
360+ if device .startswith ("/dev/pts/" ):
361+ st = os .stat (device )
362+ # Unix98 PTY slaves have major device number 136 on Linux
363+ if stat .S_ISCHR (st .st_mode ) and os .major (st .st_rdev ) == 136 :
364+ return True
365+ except (OSError , AttributeError ):
366+ # If detection fails or os.major not available, assume not a PTY
367+ pass
368+ return False
369+
336370 def close (self ):
337371 self .serial .close ()
338372
@@ -358,22 +392,27 @@ def read_until(
358392 while True :
359393 if data .endswith (ending ):
360394 break
361- elif self .serial .inWaiting () > 0 :
395+
396+ # PTY: always read (blocking with timeout), Serial: check inWaiting() first
397+ if self .is_pty or self .serial .inWaiting () > 0 :
362398 new_data = self .serial .read (1 )
363- if data_consumer :
364- data_consumer (new_data )
365- data = new_data
366- else :
367- data = data + new_data
368- begin_char_s = time .monotonic ()
369- else :
370- if timeout is not None and time .monotonic () >= begin_char_s + timeout :
371- break
372- if (
373- timeout_overall is not None
374- and time .monotonic () >= begin_overall_s + timeout_overall
375- ):
376- break
399+ if new_data :
400+ if data_consumer :
401+ data_consumer (new_data )
402+ data = new_data
403+ else :
404+ data = data + new_data
405+ begin_char_s = time .monotonic ()
406+
407+ # Check timeouts (applies to both PTY and real serial)
408+ if timeout is not None and time .monotonic () >= begin_char_s + timeout :
409+ break
410+ if (
411+ timeout_overall is not None
412+ and time .monotonic () >= begin_overall_s + timeout_overall
413+ ):
414+ break
415+ if not self .is_pty :
377416 time .sleep (0.01 )
378417 return data
379418
0 commit comments