ユーザーがアップロードした任意の (したがって、最悪の場合、安全でなく、エラーが発生し、クラッシュする) コードを Linux サーバーで実行するための Python プログラムを作成しています。セキュリティの問題はさておき、私の目的は、コード(コンパイルまたは解釈された任意の言語である可能性があります)が正しいものを に書き込むかどうかstdout
、stderr
およびプログラムのstdin
. この後、結果をユーザーに表示する必要があります。
現在の解決策
現在、私の解決策は、 、およびsubprocess.Popen(...)
のファイルハンドルを使用して子プロセスを生成することです。ハンドルの後ろのファイルには、プログラムが操作中に読み取る入力が含まれており、プログラムが終了した後、ファイルとファイルが読み取られて正確性がチェックされます。stdout
stderr
stdin
stdin
stdout
stderr
問題
このアプローチはそれ以外の場合は完全に機能しますが、結果を表示するときに、指定された入力と出力を組み合わせて、端末からプログラムを実行したときと同じ場所に入力が表示されるようにすることはできません。つまり、次のようなプログラムの場合
print "Hello."
name = raw_input("Type your name: ")
print "Nice to meet you, %s!" % (name)
stdout
実行後のプログラムの内容を含むファイルの内容は次のようになります。
Hello.
Type your name:
Nice to meet you, Anonymous!
を含むファイルの内容stdin
がAnonymous<LF>
. したがって、要するに、指定されたサンプルコード (および同等に、他のコード) に対して、次のような結果を達成したいと考えています。
Hello.
Type your name: Anonymous
Nice to meet you, Anonymous!
したがって、問題は、プログラムがいつ入力を待っているかを検出することです。
試した方法
問題を解決するために、次の方法を試しました。
Popen.communicate(...)
これにより、親プロセスはパイプに沿って個別にデータを送信できますが、一度しか呼び出すことができないため、ドキュメントから推測できるように、複数の出力と入力を持つプログラムには適していません。
Popen.stdoutとPopen.stderrから直接読み取り、Popen.stdinに書き込む
ドキュメントはこれに対して警告しており、プログラムが入力を待機し始めると、Popen.stdout
s.read()
と.readline()
呼び出しが無限にブロックされるようです。
select.select(...)
ファイルハンドルが I/O の準備ができているかどうかを確認するために使用します
これでは何も改善されないようです。どうやら、パイプは常に読み取りまたは書き込みの準備ができているようです。そのため、select.select(...)
ここではあまり役に立ちません。
ノンブロッキング読み取りに別のスレッドを使用する
この回答で示唆されているように、からの読み取り結果を Queue() に格納する別のThread()を作成しようとしました。ユーザー入力を要求する行の前の出力行は適切に表示されますが、プログラムがユーザー入力の待機を開始する行 (上記の例) は読み取られません。stdout
"Type your name: "
子プロセスのファイル ハンドルとしてPTYスレーブを使用する
hereの指示に従ってpty.openpty()
、マスターとスレーブのファイル記述子を使用して疑似端末を作成しようとしました。その後、スレーブ ファイル記述子をsubprocess.Popen(...)
呼び出しのstdout
,stderr
およびstdin
パラメータの引数として指定しました。で開かれたマスター ファイル記述子を読み取るとos.fdopen(...)
、別のスレッドを使用した場合と同じ結果が得られます。入力を要求する行は読み取られません。
編集: @Antti Haapala のpty.fork()
子プロセス作成の例を代わりにsubprocess.Popen(...)
使用すると、 によって作成された出力も読み取ることができるようですraw_input(...)
。
pexpect の使用
また、pexpect で生成されたプロセスのメソッド (ドキュメントはこちら) も試しましたが、 で得た最良の結果はread()
以前と同じです:read_nonblocking()
ユーザーに何かを入力してもらう前の出力の行は読んでください。で作成された PTY と同じです。入力を要求する行は読み取られます。readline()
read_nonblocking()
pty.fork()
編集:子を作成するマスタープログラムでing の代わりにsys.stdout.write(...)
andを使用すると、プロンプト行が表示されない問題が修正されたように見えましたが、実際にはどちらの場合も読み取られました。sys.stdout.flush()
print
その他
も試しましselect.poll(...)
たが、パイプまたは PTY マスター ファイル記述子は常に書き込みの準備ができているようです。
ノート
その他のソリューション
- また、新しい出力が生成されずに時間が経過したときに入力をフィードしようとすることも頭に浮かびました。ただし、プログラムが重い計算を実行している最中かどうかを知る方法がないため、これは危険です。
- @Antti Haapala が回答で述べたように、
read()
glibc のシステム コール ラッパーを置き換えて、入力をマスター プログラムに伝えることができます。ただし、これは静的にリンクされたプログラムやアセンブリ プログラムでは機能しません。(ただし、今考えてみると、そのような呼び出しはソースコードから傍受され、パッチが適用されたバージョンのread()
- に置き換えられる可能性がありますが、それでも実装するのは骨の折れる可能性があります。) - システムコールをプログラムに伝えるためにLinuxカーネルコードを変更するの
read()
は、おそらく非常識です...
PTY
PTY は端末を偽装し、あらゆる場所の端末で対話型プログラムが実行されるため、PTY が適していると思います。問題は、どうやって?