Goを使ってPlan 9とお話
9PプロトコルでPlan 9とMacOS Xとおしゃべりするのに、Goの9pパッケージ(go9p)を使ってみようという話。go9pは標準パッケージには含まれていないが、有志によって開発が進められている。copyというbuiltinを使っているので、新しいリリースを使う必要がある。
$ hg clone http://bitbucket.org/f2f/go9p/
$ cd go9p
$ make
$ make install
ちなみに9Pの実装はいろいろあり、ここにまとめられている。例えば、C言語だとlibixpが有名で、MacOS Xでも動く。
Plan 9側の準備としてexportfsを使って自分のホームディレクトリ以下の名前空間をエクスポートする。9Pプロトコルのリスナーとしてlisten1を使う。
% aux/listen1 tcp!*!10000 exportfs -R -r $home
go9pのクライアントサンプル(ls.go)を実行してみる。-addrオプションにはPlan 9マシンのIPアドレスとlisten1で指定したポート番号を与える。VMWare上で実行しているPlan 9の名前空間が得られた。つまり、Mac OS Xからはルートディレクトリ(/)が$home(/usr/oraccha)として見える。
$ cd plan9/p/clnt/examples
$ 8g ls.go
$ 8l ls.8
$ ./8.out -addr="192.168.182.130:10000" /
9Pのやりとりを見たいので、デバッグオプション(-d、-D)を追加してみた*1。Tから始まるのがメッセージの送信、Rから始まるのが返信になる。これ以前に*versionや*attachのやりとりがあるが、表示されてない。これはclnt.Mount以前にデバッグレベルを設定する手段がないから。また、問答無用で9P2000.u拡張を利用するようだ(まぁ、通信相手がPlan 9だったら9P2000にfall backするけど)。
$ ./8.out -d -addr="192.168.182.130:10000" /
2009/12/31 11:39:23 {{{ Twalk tag 0 fid 0 newfid 1
2009/12/31 11:39:23 }}} Rwalk tag 0
2009/12/31 11:39:23 {{{ Topen tag 0 fid 1 mode 0
2009/12/31 11:39:23 }}} Ropen tag 0 qid (40fc f 'd') iounit 8192
2009/12/31 11:39:23 {{{ Tread tag 0 fid 1 offset 0 count 8192
2009/12/31 11:39:23 }}} Rread tag 0 count 442
2009/12/31 11:39:23 {{{ Tread tag 0 fid 1 offset 442 count 8192
2009/12/31 11:39:23 }}} Rread tag 0 count 0
bin
contrib
lib
src
tmp
www
2009/12/31 11:39:23 {{{ Tread tag 0 fid 1 offset 442 count 8192
2009/12/31 11:39:23 }}} Rread tag 0 count 0
2009/12/31 11:39:23 {{{ Tclunk tag 0 fid 1
2009/12/31 11:39:23 }}} Rclunk tag 0
ソースコードはこんな感じ。
package main import "flag" import "fmt" import "log" import "os" import "plan9/p" import "plan9/p/clnt" var addr = flag.String("addr", "127.0.0.1:5640", "network address") var debug = flag.Bool("d", false, "enable debugging (fcalls)") var debugall = flag.Bool("D", false, "enable debugging (raw packets)") func main() { var user p.User; var err *p.Error; var c *clnt.Clnt; var file *clnt.File; var d []*p.Dir; flag.Parse(); user = p.OsUsers.Uid2User(os.Geteuid()); c, err = clnt.Mount("tcp", *addr, "", user); if err != nil { goto error } if flag.NArg() != 1 { log.Stderr("invalid arguments"); return; } if *debug { c.Debuglevel = 1 } if *debugall { c.Debuglevel = 2 } file, err = c.FOpen(flag.Arg(0), p.OREAD); if err != nil { goto error } for { d, err = file.Readdir(0); if err != nil { goto error } if d == nil || len(d) == 0 { break } for i := 0; i < len(d); i++ { os.Stdout.WriteString(d[i].Name + "\n") } } file.Close(); return; error: log.Stderr(fmt.Sprintf("Error: %s %d", err.Error, err.Errornum)); }
とこれだけでは面白くないので、psを実装してみる。psを実装するには/procが見えればよい。NFSだと/procはexportできないけど、9Pではこんなこともできちゃう。
注意点はlisten1を実行するときに-tオプションを付けること。これがないとNoneユーザで実行されてしまい、exportfs以外の/proc/pid/statusのopenにpermission deniedで失敗してしまう。これに気づかずしばらくはまってしまった。
package main import "flag" import "fmt" import "log" import "os" import "sort" import "strconv" import "strings" import "plan9/p" import "plan9/p/clnt" var addr = flag.String("addr", "127.0.0.1:5640", "network address") var debug = flag.Bool("d", false, "enable debugging (fcalls)") var debugall = flag.Bool("D", false, "enable debugging (raw packets)") // A dirList implements sort.Interface. type dirList []*p.Dir func (d dirList) Len() int { return len(d) } func (d dirList) Less(i, j int) bool { a, err := strconv.Atoi(d[i].Name); if err != nil { return false; } b, err := strconv.Atoi(d[j].Name); if err != nil { return false; } return a < b; } func (d dirList) Swap(i, j int) { d[i], d[j] = d[j], d[i] } func ps(c *clnt.Clnt, s string) { buf := make([]byte, 4096); f, err := c.FOpen("/proc/" + s + "/status", p.OREAD); if err != nil { goto error } nr, err := f.Read(buf); if err != nil { goto error } stats := strings.Fields(string(buf[0:nr])); /* * 0 text * 1 user * 2 state * 3 cputime[6] * 9 memory * 10 basepri * 11 pri */ utime, er := strconv.Atoi(stats[3]); if er != nil { fmt.Fprintf(os.Stderr, "%v\n", er); } utime /= 1000; stime, er := strconv.Atoi(stats[4]); if er != nil { fmt.Fprintf(os.Stderr, "%v\n", er); } stime /= 1000; fmt.Printf("%-10s %8s %4d:%.2d %3d:%.2d %7sK %-8.8s %s\n", stats[1], s, utime/60, utime%60, stime/60, stime%60, stats[9], stats[2], stats[0]); return; error: log.Stderr(fmt.Sprintf("Error: %s", err.Error)); } func main() { var user p.User; var err *p.Error; var c *clnt.Clnt; var file *clnt.File; var d []*p.Dir; flag.Parse(); if *debug { c.Debuglevel = 1 } if *debugall { c.Debuglevel = 2 } user = p.OsUsers.Uid2User(os.Geteuid()); c, err = clnt.Mount("tcp", *addr, "", user); if err != nil { goto error } file, err = c.FOpen("/proc", p.OREAD); if err != nil { goto error } d, err = file.Readdir(0); file.Close(); if err != nil { goto error } if d == nil || len(d) == 0 { fmt.Fprintf(os.Stderr, "ps: empty directory /proc\n"); return; } nnp := 0; dirs := make(dirList, len(d)); for i := range d { if d[i].Name[0] < '0' || '9' < d[i].Name[0] { nnp += 1; continue; } dirs[i-nnp] = d[i]; } dirs = dirs[0:len(dirs)-nnp]; sort.Sort(dirs); for i := 0; i < len(dirs); i++ { ps(c, dirs[i].Name); } return; error: log.Stderr(fmt.Sprintf("Error: %s", err.Error)); }
*1:よく考えたらsnoopyで調べる手もあるな。ちゃんと見やすい形式に整形してくれる。