主頁 | 自己紹介 | 日記 | 農業 | 台所 | 電算機 | | 本棚 | | Git

9P

はじめに

9Pはコンピュータ上の色々なものをファイルとして扱うためのプロトコルである。このプロトコルを使えば、ディスクに記録されたデータだけでなく、マウスやキーボードといった入力機器や、ネットワーク、プロセスの情報等もファイルとして扱える。Unixの後継OSとして米AT&Tのベル研究所で開発されたPlan9というOSのために設計された。

Plan9というOSはネットワークを介して複数のコンピュータを繋いで使うように設計されているので、この9Pもローカルだけでなくネットワーク越しに使うことを前提にしている。

プロトコルの概要

9Pの通信ではクライアントがサーバーに対してリクエストを送り、サーバーがそれに対してリプライを返すことを繰り返す。クライアントからのリクエストをTメッセージ、サーバーからのリプライをRメッセージと言う。Tメッセージにはファイルにアクセスするための色々なものが用意されている。例えばファイルを開くためのTopenや、開いたファイルに書き込むためのTwrite等である。それぞれのTメッセージには対応するRメッセージがある。Topenに対してRopen、Twriteに対してRwrite等である。

通信は必要な情報をプロトコルの定める方法でバイト列に変換して行う。2バイト以上のデータはリトルエンディアンになるように配置する。テキストデータはUTF-8にエンコードする。テキストデータは長さの情報も一緒に送るので最後のヌル文字は含めない。

各メッセージは4バイトの整数から始まる。これは、この4バイトを含むメッセージの長さをバイト単位で示したものである。次にメッセージの種類を記述する1バイトのデータが来る。次はメッセージのタグである。クライアントはサーバーからの返事を待たずに別のメッセージを送れるので、サーバーからのRメッセージがどのTメッセージに対する返事なのかを識別する必要がある。そのために使うのがタグである。クライアントがTメッセージを送る際にタグを付け、サーバーはそれに対するRメッセージに同じタグを付ける。その後ろには、メッセージの種類によって固有の情報が付けられる。

以下は実際の9P通信のログである。実際はバイナリの通信だが、ログとして見易いように変換してある。また、先頭にくるメッセージサイズは省略してある。この例では、サーバーのルートディレクトリにあるhelloというファイルの内容を読みこんでいる:

<-- Tversion Tag 65535 msize 8192 version '9P2000'
--> Rversion Tag 65535 msize 8192 version '9P2000'
<-- Tattach Tag 0 fid 0 afid -1 uname kenji aname
--> Rattach Tag 0 qid (0000000000000000 0 d)
<-- Twalk Tag 0 fid 0 newfid 1 nwname 1 0:hello
--> Rwalk Tag 0 nwqid 1 0:(0000000000000001 0 )
<-- Tstat Tag 0 fid 1
--> Rstat Tag 0 stat 'hello' 'kenji' 'kenji' '' q (0000000000000001 0 ) m 0644 at 1730004808 mt 1730004808 l 7 t 0 d 0
<-- Twalk Tag 0 fid 1 newfid 2 nwname 0
--> Rwalk Tag 0 nwqid 0
<-- Topen Tag 0 fid 2 mode 0x0
--> Ropen Tag 0 qid (0000000000000001 0 ) iounit 8169
<-- Tread Tag 0 fid 2 offset 0 count 4096
--> Rread Tag 0 count 7 ' 776f726c 64210a'
<-- Tread Tag 0 fid 2 offset 7 count 4096
--> Rread Tag 0 count 0 ''
<-- Tclunk Tag 0 fid 2
--> Rclunk Tag 0

まず最初にクライアントがサーバーに対してTversionを送り、プロトコルのバージョンと、メッセージの最大サイズを交渉する。Tversionのタグは65535と決められている。msizeの8192はメッセージの最大サイズ(バイト)で、versionの9P2000がプロトコルのバージョンである。サーバーからRversionで同じmsizeversionが返ってきたのでこのサイズとバージョンで以降の通信を行う。

次のTattachで、クライアントがサーバーにルートディレクトリのfidを要求する。afid以降は認証用の情報である(後述)。fidはコネクションにおいてファイルと紐付けられる整数で、Unixにおけるファイルディスクリプタのようなものである。ファイルの読み書きはこのfidを使って行われる。サーバーからのRattachにはルートディレクトリのqidが含まれる。これはサーバー上でファイルを一意に識別するもので、Unixにおけるinodeに相当するものである。以上でサーバーに繋いでセッションを確立できた。

次にTwalkで目的のファイル(hello)までファイルツリーを辿る。ここでは起点として先程得られたルートディレクトリ(fid = 1)を起点として、このディレクトリ内のhelloファイルを要求してfid = 2を割り当てようとしている。サーバーはルートディレクトリ内に要求されたファイルが見付かったので、そのファイルのqidを返す。

目的のファイルにfidが割り当てられたので、Tstatでこのファイルの属性を要求している。おそらくファイルのパーミッションを調べるためである。

次にTopenを使ってこのファイルを開き、Treadを使って内容を読む。二回目のTreadに対して0バイトのデータが返ってきたので、ファイルはこれで終りである。

最後に、必要なくなったfidはTclunkで捨てる。

List of all Message Types

Tメッセージ Rメッセージ 説明
Tversion Rversion バージョンの交渉
Tauth Rauth 認証ファイルの取得
Rerror エラー(Rメッセージのみ)
Tflush Rflush 処理中のリクエストをキャンセル
Tattach Rattach ルートディレクトリの取得
Twalk Rwalk ファイルツリーを辿る
Topen Ropen ファイルを開く
Tcreate Rcreate ファイルの作成
Tread Rread ファイルを読む
Twrite Rwrite ファイルへ書き込む
Tclunk Rclunk fidの削除
Tremove Rremove ファイルを削除
Tstat Rstat ファイルの属性を取得
Twstat Rwstat ファイルの属性を変更

fidqid

fidはコネクションにおいてファイルを指定するために使われる整数で、Unixのファイルディスクリプタのようなものである。使用する整数はクライアントが指定できる。サーバーに接続する際にTattachにより、サーバーのルートディレクトリのfidが設定され、Twalkにより追加され、Tclunkにより削除される。

qidはサーバーがファイルを一意に識別するためのもので、Unixのinodeに相当するものである。ただしinodeはファイルが削除されると再利用される可能性があるのに対し、qidは再利用できない。qidはこのファイルがディレクトリかどうか等を示すtype、ファイルが変更されたときにインクリメントされるvers、そしてファイルを一意に表すpathの3つの情報で構成される。

認証

9Pにはクライアントの認証が組込まれていない。もともとはあったようだが、認証のアルゴリズムに欠陥が見付かったりすればいちいちコードを修正してコンパイルしなおさなければいけないうえ、9Pプロトコル自体も変更しないといけないので、外部に切りだすことになった。認証に必要なやりとりは認証サーバーとクライアントが行い、9Pサーバーは認証が成功したかどうかの情報を認証サーバーに確認することでクライアントの確認を行う。

9PサーバーはTattachメッセージを受けとると、認証サーバーとのコネクションを確立する。その後Rattachメッセージでafidという特殊なfidをクライアントに返す。クライアントからこのafidに対して読み書きする命令が届くと、9Pサーバーはこの読み書きを認証サーバーとのコネクションに横流しする。つまりクライアントはこのafidを通じて認証サーバーと直接やりとりができる。クライアントはユーザー名やパスワード等(パスワード認証の場合)の情報をafidに書き込んで認証する。このように認証をプロトコル自体から切り離すことで、認証に関するバグが見付かったとしても、認証サーバーを修正するだけでいい。また、より強力な認証システムを組み込むのも楽である。

コネクションの共有

クライアントはサーバーとversionメッセージを交すことでコネクションを確立する。コネクションの確立からの一連のやりとりをセッションという。ひとつのコネクションは複数のクライアントが共有できる。

クライアントはTauthまたはTattachメッセージによりサーバーにfidを要求できる。このうちTauthにより得られたfidは認証以外には使えない。Tattachで得られたfidは、Twalkメッセージによりファイルツリーを辿るための起点として利用でき、このとき辿った先のファイルを示すfidが生成される。これ以外の方法でfidが増えることはない。つまり、サーバーはひとつのコネクションのなかで、ルートディレクトリから派生したfidを追跡することで、クライアントを区別できる。

メッセージ詳細

それぞれ最初にメッセージの形式を書く。field[n]fieldという名前のnバイトのデータを表す(nは整数)。また、括弧の中が整数ではなくsになっている場合、そのフィールドは文字列のデータで、その前に文字列の長さを示す2バイトの整数が先行する。また、メッセージ名のフィールド(Tversion等)にはメッセージの種類を表す1バイトのenumが来る。

version

プロトコルのバージョンを交渉すし、コネクションを確立する。

size[4] Tversion tag[2] msize[4] version[s]
size[4] Rversion tag[2] msize[4] version[s]

クライアントは最初にサーバーにこのメッセージを送ってバージョンとメッセージの最大サイズ(msize)を交渉する。サーバーはクライアントから送られたバージョンとメッセージサイズに対応していれば、同じバージョンとサイズを送り返し、交渉成立である。

サーバーとクライアントはこのメッセージを以ってコネクションを確立する。

attach、auth

コネクションを確立する。

size[4] Tauth tag[2] afid[4] uname[s] aname[s]
size[4] Rauth tag[2] aqid[13]

size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s]
size[4] Rattach tag[2] qid[13]

attachメッセージはサーバーのルートディレクトリを要求し、fidを割り当てる。認証が必要なサーバーであれば、先にauthメッセージで認証を済ませておき、attachメッセージのafidに、先程使用したafidをセットする。サーバーはこのafidに紐付いている認証サーバーにクライアントが認証済みであるかを確認し、認証済みであればルートディレクトリのqidを返す。サーバーが複数のファイルツリーを公開している場合、anameで指定する。

authメッセージは認証サーバーとやりとりするためのfidであるafidを要求する。このafidを読み書きすることで認証サーバーとやりとりをし、自身の身分を証明する。認証が済んだらこのafidをセットしたattachメッセージを送る。

error

エラーを返す。

size[4] Rerror tag[2] ename[s]

クライアントからの要求に対して、なにかエラーがおきたときにそのエラーを返すために使われる。Terrorはない。

flush

リクエストをキャンセルする。

size[4] Tflush tag[2] oldtag[2]
size[4] Rflush tag[2]

実行中の要求をキャンセルする。キャンセルしたいリクエストのtagoldtagにセットする。

walk

ファイルツリーを移動する。

size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s])
size[4] Rwalk tag[2] nwqid[2] nwqid*(qid[13])

fidを起点に移動する。移動先のファイルをnewfidにセットする。移動するディレクトリの数をnwnameに、移動するディレクトリの名前を移動する順にwnameにそれぞれセットする。例えばカレントディレクトリから./dir1/dir2/に移動する場合、nwname2wname{"dir1", "dir2"}となる。wnameの最後の要素はディレクトリでなくファイルでもいい。

wnameの最初の要素への移動が失敗した場合はRerrorが返される。それ以外の場合はRwalkが返され、qidは移動が成功した順番にそのファイルのqidがセットされる。nwnamenwqidが一致した場合は最後まで移動できたことになる。

walkfidを増殖させる唯一の方法である。

open、create

fidを開いて(作成して)読み書きできる状態にする。

size[4] Topen tag[2] fid[4] mode[1]
size[4] Ropen tag[2] qid[13] iounit[4]

size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]
size[4] Rcreate tag[2] qid[13] iounit[4]

openfidと紐付いたファイルを開く。modeは読み込み専用のOREAD、書き込み専用のOWRITE、読み書きのORDWR等である。ファイルを開く手順は以下の通り:

  1. attachでルートディレクトリのfidを取得
  2. ルートディレクトリのfidからwalkで開きたいファイルのfidを取得
  3. そのfidopenで開く

createfidと紐付いたディレクトリに新しいファイルを作成する。creatではなくcreateである。作成が成功したらfidは新しく作成されたファイルに紐付く。

read、write

ファイルを読み書きする。

size[4] Tread tag[2] fid[4] offset[8] count[4]
size[4] Rread tag[2] count[4] data[count]

size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count]
size[4] Rwrite tag[2] count[4]

readfidoffsetバイト目からcountバイト読む。読んだデータと、読めたデータサイズがdatacountととして返される。writefidoffsetバイト目にcountバイトのデータdataを書く。書きこめたサイズがcountとして返される。ファイルを読み書きするためにはあらかじめfidopenする必要がある。

clunk

fidを忘れる。

size[4] Tclunk tag[2] fid[4]
size[4] Rclunk tag[2]

いらなくなったfidを忘れる。一度忘れたfidwalkで別のファイルを取得する際に再利用できる。

remove

ファイルを削除する。

size[4] Tremove tag[2] fid[4]
size[4] Rremove tag[2]

fidと紐付いたサーバー上のファイルを削除する。fidclunkしたのと同様に忘れられる。

stat、wstat

ファイルの属性を読み書きする。

size[4] Tstat tag[2] fid[4]
size[4] Rstat tag[2] stat[n]

size[4] Twstat tag[2] fid[4] stat[n]
size[4] Rwstat tag[2]

statはファイルの属性を読む。読めた情報はstatとして返される。wstatはファイルの属性をstatに変更する。statはファイルの名前やqid、サイズ、作成日、更新日等の情報がバイト列になったものである。