Infernoでファイルサーバ

ファイルサーバとしてサービスを実装していくのはPlan9と一緒で、bind/mountやsrvバイス(#s)等の仕組みも同じ。srvバイスが変換する通信プロトコルが9PかStyxかの違いはあるが(といってもほとんど違いはないみたいだが)。ファイルサーバの実装という視点から見ると、Plan9の場合は「ファイルサーバの雛形」に書いたが、ファイルに対するopen-close-read-writeのコールバック関数を実装していくことになる。一方、Infernoの場合はこれまたチャネルを活用する。

srvバイスとファイルサーバのやり取りをラップしてくれているのが、Sys->file2chan関数である。引数にsrvバイスがmountされたディレクトリとファイル名を取り、FileIO ADTへのリファレンスを返す。FileIOはread、writeという二つのチャネルからなる。

include "sys.m";
sys := load Sys Sys->PATH;

Rread: type chan of (array of byte, string);
Rwrite: type chan of (int, string);
FileIO: adt
{
read: chan of (int, int, int, Rread);
write: chan of (int, array of byte, int, Rwrite);
};

file2chan: fn(dir, file: string): ref FileIO;

使い方はこんな感じ。read、writeの戻り値はそれぞれ4つの要素からなるタプルになる。Limboではタプルを使って、多値の戻り値を実現している。

  sys->bind("#s", "/usr/oraccha", sys->MBEFORE);
  channel := sys->file2chan("/usr/oraccha", "test.file");

  for(;;) alt {
    (offset, count, fid, rc) := <- channel.read =>
      end := offset + count;
      rc <- (buf[offset:end], nil);

    (offset, data, fid, wc) := <- channel.write =>
      buf[offset:] = data;
      wc <- (count, nil);
  }

アプリケーションプロセスが"/usr/oraccha/test.file"をreadした場合はchannel.readが、writeした場合はchannel.writeがタプルを返す。rcとwcはsrvバイスとのチャネルでデータはarray of byteでやり取りされる。タプルの2番目の要素はエラー文字列で、エラーがないときはnilを渡す。

追記:なんて書いたけど、Styxのメッセージをもっとダイレクトにハンドリングする手段もあるみたい。ramfileとmemfsという似たようなコマンドがあるけど、前者はfile2chanを使っていて、後者はstyxlibモジュールを使っている。