Slide 1

Slide 1 text

PTY on Rust 2015-07-12 Rust of Us - Chapter 2 @hibariya

Slide 2

Slide 2 text

やりたいこと ● Rust で ● PTY を使って ● シェルの入出力を好きなようにする

Slide 3

Slide 3 text

こういうこと

Slide 4

Slide 4 text

こういうこと

Slide 5

Slide 5 text

入出力を好きなようにできると嬉しい ● 何か入出力があるたびに音を鳴らせる ● 出力を記録してあとで再現したりできる ● ターミナルの表示を遠方の人と共有できる

Slide 6

Slide 6 text

方法 ● パイプを使う ● 親の端末につなぐ ● システムコールを監視する ● 新たな端末を割り付ける

Slide 7

Slide 7 text

方法 ● パイプを使う ● 親の端末につなぐ ● システムコールを監視する ● 新たな端末を割り付ける

Slide 8

Slide 8 text

パイプを使う 端末 <> 親 <> パイプ <> 子

Slide 9

Slide 9 text

パイプを使う ● うまくいかない ● デフォルトの端末はカノニカルモード (入力は行 ごとに親プロセスへ送られる) なので、改行が来 るまで入力を子プロセスへ送ることができない ● vi や nano が使えない ● 端末に接続されていないのも都合が悪い

Slide 10

Slide 10 text

方法 ● パイプを使う ● 親の端末につなぐ ● システムコールを監視する ● 新たな端末を割り付ける

Slide 11

Slide 11 text

親の端末につなぐ 端末 <> 親 <> 子

Slide 12

Slide 12 text

親の端末につなぐ ● うまくいかない ● 入力はすぐに子プロセスへ届くが... ● 親が子の入出力を捕捉できない ● (方法を知らないだけ?)

Slide 13

Slide 13 text

方法 ● パイプを使う ● 親の端末につなぐ ● システムコールを監視する ● 新たな端末を割り付ける

Slide 14

Slide 14 text

システムコールを監視 自分 -> プロセス (write(2) を監視!)

Slide 15

Slide 15 text

システムコールを監視 github.com/hibariya/process_tail ● ちょっと微妙 ● 既に走っているプロセスにも使える ● Linux と Mac OS X で実装が別 ● 環境によって微妙に動きが違うのができた ● ちょっと重い気がする

Slide 16

Slide 16 text

方法 ● パイプを使う ● 親の端末につなぐ ● システムコールを監視する ● 新たな端末を割り付ける

Slide 17

Slide 17 text

新たな端末を割り付ける 端末 <> 親 <> 端末 <> 子 (親の端末は raw モードに)

Slide 18

Slide 18 text

新たな端末を割り付ける note.hibariya.org/articles/20150628/pty.html ● うまくいく ● 既に走っているプロセスには使えない ● 端末を想定しているコマンドも動作する ● Linux と Mac OS X でほぼ同じ実装 ● 比較的シンプル

Slide 19

Slide 19 text

色々試した結果 ● パイプを使う ● 親の端末につなぐ ● システムコールを監視する ● 新たな端末を割り付ける

Slide 20

Slide 20 text

PTY (Pseudo terminal) ● 疑似端末 ● 端末をソフトウェアでエミュレートしたもの ● ターミナルエミュレータなどが使っている ● /dev/pts/4 みたいな名前がついている

Slide 21

Slide 21 text

新たな端末を割り付ける 端末 <> 親 <> 端末 <> 子 (親の端末は raw モードに)

Slide 22

Slide 22 text

Ruby で書くと1行 output, input, pid = PTY.spawn('bash')

Slide 23

Slide 23 text

Rust ではどうすれば ● わからない ● PTY.spawn は無かった ● libc の API を使えばやれそう ● PTY の使い方を調べるところから

Slide 24

Slide 24 text

情報源 ● script(1) のソース ● Ruby の ext/pty/pty.c ● APUE 第3版 18, 19章

Slide 25

Slide 25 text

PTY のしくみ ● マスタとスレーブがある ● 双方向パイプのように振る舞う ● 親から子へ入力、子から親へ出力できる

Slide 26

Slide 26 text

PTY のしくみ マスタ <> スレーブ <> ラインディシプリン

Slide 27

Slide 27 text

PTY.spawn(‘bash’) のようなこと 1. 親でマスタ側を開いて fork 2. 子を新しいセッションに入れる 3. 子でスレーブ側を開く 4. 子でスレーブを標準入出力にする 5. 子で (bash を) exec 6. 親でマスタ越しに入出力を操作

Slide 28

Slide 28 text

PTY.spawn(‘bash’) のようなこと 1. 親でマスタ側を開いて fork 2. 子を新しいセッションに入れる 3. 子でスレーブ側を開く 4. 子でスレーブを標準入出力にする 5. 子で (bash を) exec 6. 親でマスタ越しに入出力を操作

Slide 29

Slide 29 text

親でマスタ側を開いて fork ● posix_openpt (3) ● スレーブを開くために grantpt (3), unlockpt (3) も呼んでおく

Slide 30

Slide 30 text

PTY.spawn(‘bash’) のようなこと 1. 親でマスタ側を開いて fork 2. 子を新しいセッションに入れる 3. 子でスレーブ側を開く 4. 子でスレーブを標準入出力にする 5. 子で (bash を) exec 6. 親でマスタ越しに入出力を操作

Slide 31

Slide 31 text

子を新しいセッションに入れる ● setsid (2) ● セッションひとつにつき端末ひとつまで

Slide 32

Slide 32 text

PTY.spawn(‘bash’) のようなこと 1. 親でマスタ側を開いて fork 2. 子を新しいセッションに入れる 3. 子でスレーブ側を開く 4. 子でスレーブを標準入出力にする 5. 子で (bash を) exec 6. 親でマスタ越しに入出力を操作

Slide 33

Slide 33 text

子でスレーブ側を開く ● ptsname (3) でスレーブの名前を得る ● こっちは open(2) で開く

Slide 34

Slide 34 text

PTY.spawn(‘bash’) のようなこと 1. 親でマスタ側を開いて fork 2. 子を新しいセッションに入れる 3. 子でスレーブ側を開く 4. 子でスレーブを標準入出力にする 5. 子で (bash を) exec 6. 親でマスタ越しに入出力を操作

Slide 35

Slide 35 text

子でスレーブを標準入出力にする ● dup (2) ● この時点で入出力が端末につながる

Slide 36

Slide 36 text

PTY.spawn(‘bash’) のようなこと 1. 親でマスタ側を開いて fork 2. 子を新しいセッションに入れる 3. 子でスレーブ側を開く 4. 子でスレーブを標準入出力にする 5. 子で (bash を) exec 6. 親でマスタ越しに入出力を操作

Slide 37

Slide 37 text

PTY.spawn(‘bash’) のようなこと 1. 親でマスタ側を開いて fork 2. 子を新しいセッションに入れる 3. 子でスレーブ側を開く 4. 子でスレーブを標準入出力にする 5. 子で (bash を) exec 6. 親でマスタ越しに入出力を操作

Slide 38

Slide 38 text

できた ● note.hibariya.org/articles/20150628/pty.html ● Rust から同じものを FFI で呼べばよさそう

Slide 39

Slide 39 text

PTY on Rust FFI でライブラリの API を呼ぶ extern crate libc; #[link(name = "c")] extern { pub fn posix_openpt(flags: libc::c_int) -> libc::c_int; pub fn grantpt(fd: libc::c_int) -> libc::c_int; pub fn unlockpt(fd: libc::c_int) -> libc::c_int; pub fn ptsname(fd: libc::c_int) -> *mut libc::c_schar; }

Slide 40

Slide 40 text

PTY on Rust ● 目的の動作をするものはできた ● unsafe 祭りになった ● もっと簡単に書けるようにしたい

Slide 41

Slide 41 text

Crate にした ● github.com/hibariya/pty-rs ● let (child_process, pty_master) = pty::fork(); ● おかしなところにパッチがほしい ● もっといい API があれば改善したい

Slide 42

Slide 42 text

extern crate pty;