7070import ast
7171import errno
7272import os
73+ import stat
7374import struct
7475import sys
7576import time
@@ -333,6 +334,41 @@ 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+ if device .startswith ("execpty:" ):
341+ # execpty: explicitly uses PTY devices
342+ self .is_pty = True
343+ elif device .startswith ("exec:" ) or (
344+ device and device [0 ].isdigit () and device [- 1 ].isdigit () and device .count ("." ) == 3
345+ ):
346+ # exec: (non-PTY) and telnet connections are not PTYs
347+ self .is_pty = False
348+ else :
349+ # For direct serial device paths, auto-detect
350+ self .is_pty = self ._is_pty_device (device )
351+
352+ def _is_pty_device (self , device ):
353+ """
354+ Detect if device is a PTY (pseudo-terminal).
355+
356+ PTY devices are commonly used by emulators like QEMU. Unlike real serial
357+ devices, PTY inWaiting() may not report data availability correctly,
358+ requiring use of blocking reads instead.
359+ """
360+ try :
361+ # Linux Unix98 PTY pattern: /dev/pts/N
362+ if device .startswith ("/dev/pts/" ):
363+ st = os .stat (device )
364+ # Unix98 PTY slaves have major device number 136 on Linux
365+ if stat .S_ISCHR (st .st_mode ) and os .major (st .st_rdev ) == 136 :
366+ return True
367+ except (OSError , AttributeError ):
368+ # If detection fails or os.major not available, assume not a PTY
369+ pass
370+ return False
371+
336372 def close (self ):
337373 self .serial .close ()
338374
@@ -358,22 +394,27 @@ def read_until(
358394 while True :
359395 if data .endswith (ending ):
360396 break
361- elif self .serial .inWaiting () > 0 :
397+
398+ # PTY: always read (blocking with timeout), Serial: check inWaiting() first
399+ if self .is_pty or self .serial .inWaiting () > 0 :
362400 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
401+ if new_data :
402+ if data_consumer :
403+ data_consumer (new_data )
404+ data = new_data
405+ else :
406+ data = data + new_data
407+ begin_char_s = time .monotonic ()
408+
409+ # Check timeouts (applies to both PTY and real serial)
410+ if timeout is not None and time .monotonic () >= begin_char_s + timeout :
411+ break
412+ if (
413+ timeout_overall is not None
414+ and time .monotonic () >= begin_overall_s + timeout_overall
415+ ):
416+ break
417+ if not self .is_pty :
377418 time .sleep (0.01 )
378419 return data
379420
0 commit comments