Detectar cuando un proceso hijo está esperando entrada
Estoy escribiendo un programa de Python para ejecutar un código arbitrario subido por el usuario (y, por lo tanto, en el peor de los casos, inseguro, erróneo y fallido) en un servidor Linux. Dejando de lado las preguntas de seguridad, mi objetivo es determinar si el código (que podría estar en cualquier idioma, compilado o interpretado) escribe las cosas correctas parastdout
, stderr
y otros archivos en la entrada dada introducida en el programastdin
. Después de esto, necesito mostrar los resultados al usuario.
Actualmente, mi solución es generar el proceso hijo usandosubprocess.Popen(...)
con manejadores de archivos para elstdout
, stderr
ystdin
. El archivo detrás de lastdin
el asa contiene las entradas que el programa lee durante la operación, y una vez que el programa ha terminado, elstdout
ystderr
Los archivos se leen y se verifican para ver si están correctos.
De lo contrario, este enfoque funciona perfectamente, pero cuando muestro los resultados, no puedo combinar las entradas y salidas dadas para que las entradas aparezcan en los mismos lugares que lo harían al ejecutar el programa desde un terminal. Es decir. para un programa como
print "Hello."
name = raw_input("Type your name: ")
print "Nice to meet you, %s!" % (name)
El contenido del archivo que contiene el programa.stdout
sería, después de correr, ser:
Hello.
Type your name:
Nice to meet you, Anonymous!
Dado que el contenido del archivo que contiene elstdin
fueronAnonymous<LF>
. Entonces, en resumen, para el código de ejemplo dado (y, equivalentemente, paraalguna otro código) Quiero lograr un resultado como:
Hello.
Type your name: Anonymous
Nice to meet you, Anonymous!
Por lo tanto, el problema es detectar cuando el programa está esperando una entrada.
Métodos probadosHe intentado los siguientes métodos para resolver el problema:
Popen.communicate (...)Esto permite que el proceso padre envíe por separado los datos a lo largo de untubo, pero solo se puede llamar una vez y, por lo tanto, no es adecuado para programas con múltiples salidas y entradas, como se puede deducir de la documentación.
Leyendo directamente dePopen.stdout yPopen.stderr y escribiendo aPopen.stdinLa documentación advierte contra esto, y laPopen.stdout
s .read()
y.readline()
Las llamadas parecen bloquearse infinitamente cuando los programas comienzan a esperar por la entrada.
select.select(...)
para ver si los manejadores de archivos están listos para E / SEsto no parece mejorar nada. Al parecer, las tuberías están siempre listas para leer o escribir, por lo queselect.select(...)
No ayuda mucho aquí.
Como se sugiere enesta respuesta, He intentado crear un separadoHilo() que almacena los resultados de la lectura de lastdout
en unaCola(). Las líneas de salida antes de una línea que requieren la entrada del usuario se muestran bien, pero la línea en la que el programa comienza a esperar la entrada del usuario ("Type your name: "
en el ejemplo anterior) nunca se lee.
Como se indicaaquí, He intentadopty.openpty()
para crear un pseudo terminal con descriptores de archivo maestro y esclavo. Después de eso, he dado el descriptor del archivo esclavo como un argumento para elsubprocess.Popen(...)
llamadastdout
, stderr
ystdin
parámetros Leyendo a través del descriptor de archivo maestro abierto conos.fdopen(...)
produce el mismo resultado que al utilizar un hilo diferente: la línea que exige la entrada no se lee.
Editar: Usando el ejemplo de @Antti Haapala depty.fork()
para la creación de procesos hijo en lugar desubprocess.Popen(...)
Parece que también me permite leer la salida creada porraw_input(...)
.
También he probado elread()
, read_nonblocking()
yreadline()
métodos (documentadosaquí) de un proceso generado con pexpect, pero el mejor resultado, que obtuve conread_nonblocking()
, es el mismo que antes: la línea con salidas antes de querer que el usuario ingrese algo no se lee. es lo mismo que con un PTY creado conpty.fork()
: la línea de entrada exigentehace leer
Editar: Mediante el usosys.stdout.write(...)
ysys.stdout.flush()
en lugar deprint
en midominar El programa, que crea el elemento secundario, parece corregir la línea de aviso que no se muestra, aunque en realidad se leyó en ambos casos.
También he intentadoselect.poll(...)
, pero parecía que la tubería o los descriptores de archivos maestros PTY están siempre listos para escribir.
read()
El envoltorio de llamadas del sistema de glibc podría reemplazarse para comunicar las entradas al programa maestro. Sin embargo, esto no funciona con programas vinculados estáticamente o ensamblados. (Aunque ahora que lo pienso, cualquier llamada de este tipo podría ser interceptada desde el código fuente y reemplazada por la versión parcheada deread()
- podría ser minucioso implementar aún.)Modificando el código del kernel de Linux para comunicar elread()
Las llamadas al programa son probablemente una locura ...PTYsCreo que el PTY es el camino a seguir, ya que simula una terminal y se ejecutan programas interactivos en todas partes. La pregunta es, ¿cómo?