Wykrywanie, gdy proces potomny oczekuje na dane wejściowe
Piszę program w języku Python, aby uruchamiać przesłane przez użytkownika dowolne (a więc, w najgorszym przypadku, niebezpieczne, błędne i zawieszające się) kody na serwerze Linux. Pomijając pytania dotyczące bezpieczeństwa, moim celem jest określenie, czy kod (który może być w jakimkolwiek języku, skompilowany lub zinterpretowany) zapisuje poprawne rzeczy dostdout
, stderr
oraz inne pliki na danym wejściu wprowadzone do programustdin
. Następnie muszę wyświetlić wyniki użytkownikowi.
Obecnie moim rozwiązaniem jest zrobienie procesu potomnego za pomocąsubprocess.Popen(...)
z uchwytami do plików dlastdout
, stderr
istdin
. Plik zastdin
uchwyt zawiera dane wejściowe, które program odczytuje podczas działania i po zakończeniu programustdout
istderr
pliki są odczytywane i sprawdzane pod kątem poprawności.
Podejście to działa doskonale w inny sposób, ale gdy wyświetlam wyniki, nie mogę połączyć danych wejściowych i wyjściowych, aby wejścia pojawiły się w tych samych miejscach, w jakich byłyby podczas uruchamiania programu z terminala. To znaczy. dla takiego programu
print "Hello."
name = raw_input("Type your name: ")
print "Nice to meet you, %s!" % (name)
zawartość pliku zawierającego programstdout
po biegu byłby:
Hello.
Type your name:
Nice to meet you, Anonymous!
biorąc pod uwagę, że zawartość pliku zawierastdin
byliAnonymous<LF>
. Krótko mówiąc, dla danego przykładowego kodu (i, równoważnie, dlakażdy inny kod) Chcę osiągnąć wynik taki jak:
Hello.
Type your name: Anonymous
Nice to meet you, Anonymous!
Problem polega na wykryciu, kiedy program oczekuje na dane wejściowe.
Wypróbowane metodyWypróbowałem następujące metody rozwiązywania problemu:
Popen.communicate (...)Pozwala to procesowi nadrzędnemu na oddzielne wysyłanie danych wzdłużrura, ale można go wywołać tylko raz, a zatem nie nadaje się do programów z wieloma wyjściami i wejściami - tak jak można wywnioskować z dokumentacji.
Bezpośredni odczyt zPopen.stdout iPopen.stderr i piszę doPopen.stdinDokumentacja ostrzega przed tym iPopen.stdout
s .read()
i.readline()
wywołania wydają się blokować w nieskończoność, gdy programy zaczynają czekać na wejście.
select.select(...)
aby sprawdzić, czy uchwyty plików są gotowe do wejścia / wyjściaTo chyba nic nie poprawia. Najwyraźniej rury są zawsze gotowe do odczytu lub zapisu, więcselect.select(...)
niewiele tu pomaga.
Jak sugerowano wta odpowiedź, Próbowałem stworzyć osobneWątek() który przechowuje wyniki z odczytu zstdout
w aKolejka(). Linie wyjściowe przed linią wymagającą danych wejściowych użytkownika są wyświetlane ładnie, ale linia, na której program zaczyna czekać na dane wprowadzone przez użytkownika ("Type your name: "
w powyższym przykładzie nigdy się nie czyta.
Zgodnie z zaleceniamitutaj, Próbowałempty.openpty()
aby utworzyć pseudo terminal z deskryptorami plików master i slave. Następnie podałem deskryptor pliku slave jako argument dlasubprocess.Popen(...)
dzwonistdout
, stderr
istdin
parametry. Odczytywanie deskryptora pliku głównego otwartego za pomocąos.fdopen(...)
daje taki sam wynik, jak użycie innego wątku: wejście wymagające linii nie jest odczytywane.
Edytować: Korzystanie z przykładu @Antti Haapalapty.fork()
do tworzenia procesu potomnego zamiastsubprocess.Popen(...)
wydaje mi się, że pozwala mi także odczytać dane wyjściowe utworzone przezraw_input(...)
.
Próbowałem takżeread()
, read_nonblocking()
ireadline()
metody (udokumentowanetutaj) procesu zrodzonego z pexpect, ale najlepszy wynik, który otrzymałemread_nonblocking()
, jest taki sam jak poprzednio: linia z wyjściami, zanim użytkownik chce wprowadzić coś, nie jest czytana. jest taki sam jak w przypadku PTY utworzonego za pomocąpty.fork()
: wejście wymagające liniirobi czytać.
Edytować: Używającsys.stdout.write(...)
isys.stdout.flush()
zamiastprint
w moimmistrz program, który tworzy dziecko, wydawał się naprawiać wiersz zachęty, który nie był wyświetlany - w rzeczywistości jednak został odczytany w obu przypadkach.
Próbowałem teżselect.poll(...)
, ale wydawało się, że deskryptory pliku głównego rury lub PTY są zawsze gotowe do pisania.
read()
systemowy wrapper z glibc może zostać zastąpiony, aby przekazać dane wejściowe do programu głównego. Nie działa to jednak w przypadku programów połączonych statycznie lub programów montażowych. (Chociaż teraz, gdy o tym myślę, wszystkie takie połączenia mogłyby zostać przechwycone z kodu źródłowego i zastąpione poprawioną wersjąread()
- może być żmudne w dalszym ciągu.)Modyfikowanie kodu jądra Linuksa w celu komunikowania sięread()
wywołania systemowe do programu są prawdopodobnie szalone ...PTYsMyślę, że PTY jest dobrym rozwiązaniem, ponieważ fałszuje terminal, a interaktywne programy są uruchamiane wszędzie na terminalach. Pytanie brzmi, jak?