www.mtkn.jp

Manuscripts for my personal webpage.
git clone https://git.mtkn.jp/www.mtkn.jp
Log | Files | Refs | LICENSE

9p.html (32300B)


      1 <h1>9P</h1>
      2 <time>2024-10-27</time>
      3 <h2>はじめに</h2>
      4 <p>
      5 9Pはコンピュータ上の色々なものをファイルとして扱うためのプロトコルである。\
      6 このプロトコルを使えば、ディスクに記録されたデータだけでなく、\
      7 マウスやキーボードといった入力機器や、ネットワーク、プロセスの情報等も\
      8 ファイルとして扱える。\
      9 Unixの後継OSとして米AT&Tのベル研究所で開発されたPlan9というOS\
     10 のためのプロトコルである。
     11 </p>
     12 
     13 <p>
     14 Plan9というOSはネットワークを介して複数のコンピュータを繋いで使うように設計\
     15 されているので、\
     16 この9Pもローカルだけでなくネットワーク越しに使うことを前提にしている。\
     17 </p>
     18 
     19 <p>
     20 便利そうなのでGo言語で実装してみた。
     21 </p>
     22 
     23 <h2>9Pプロトコル</h2>
     24 <h3>概要</h3>
     25 <p>
     26 9Pの通信ではクライアントがサーバーに対してリクエストを送り、サーバーがそれに\
     27 対してリプライを返すことを繰り返す。\
     28 クライアントからのリクエストをTメッセージ、サーバーからのリプライをRメッセージと\
     29 言う。\
     30 Tメッセージにはファイルにアクセスするための色々なものが用意されている。\
     31 例えばファイルを開くための<code>Topen</code>や、\
     32 開いたファイルに書き込むための<code>Twrite</code>等である。\
     33 それぞれのTメッセージには対応するRメッセージがある。\
     34 <code>Topen</code>に対して<code>Ropen</code>、\
     35 <code>Twrite</code>に対して<code>Rwrite</code>等である。\
     36 </p>
     37 <p>
     38 通信は必要な情報をプロトコルの定める方法でバイト列に変換して送受信する。\
     39 2バイト以上のデータはリトルエンディアンになるように配置する。\
     40 テキストデータはUTF-8にエンコードする。\
     41 テキストデータは長さの情報も一緒に送るので最後のヌル文字は含めない。\
     42 </p>
     43 <p>
     44 各メッセージは4バイトのサイズから始まる。\
     45 これは、この4バイトを含むメッセージの長さをバイト単位で示したものである。\
     46 次にメッセージの種類を記述する1バイトのデータが来る。\
     47 次はメッセージのタグである。クライアントはサーバーからの返事を\
     48 待たずに別のメッセージを送れるので、サーバーからのRメッセージがどのTメッセージ\
     49 に対する返事なのかを識別する必要がある。そのために使うのがタグである。\
     50 クライアントがTメッセージを送る際にタグを付け、サーバーはそれに対するRメッセージに\
     51 同じタグを付ける。\
     52 その後ろには、メッセージの種類によって固有の情報が付けられる。\
     53 </p>
     54 <p>
     55 以下は実際の9P通信のログである。\
     56 実際はバイナリの通信だが、ログとして見易いように変換してある。\
     57 また、先頭にくるメッセージサイズは省略してある。\
     58 この例では、サーバーのルートディレクトリにある<code>hello</code>という\
     59 ファイルの内容を読みこんでいる。:\
     60 </p>
     61 <pre><code>\
     62 &lt;-- Tversion Tag 65535 msize 8192 version '9P2000'
     63 --&gt; Rversion Tag 65535 msize 8192 version '9P2000'
     64 &lt;-- Tattach Tag 0 fid 0 afid -1 uname kenji aname
     65 --&gt; Rattach Tag 0 qid (0000000000000000 0 d)
     66 &lt;-- Twalk Tag 0 fid 0 newfid 1 nwname 1 0:hello
     67 --&gt; Rwalk Tag 0 nwqid 1 0:(0000000000000001 0 )
     68 &lt;-- Tstat Tag 0 fid 1
     69 --&gt; Rstat Tag 0 stat 'hello' 'kenji' 'kenji' '' q (0000000000000001 0 ) m 0644 at 1730004808 mt 1730004808 l 7 t 0 d 0
     70 &lt;-- Twalk Tag 0 fid 1 newfid 2 nwname 0
     71 --&gt; Rwalk Tag 0 nwqid 0
     72 &lt;-- Topen Tag 0 fid 2 mode 0x0
     73 --&gt; Ropen Tag 0 qid (0000000000000001 0 ) iounit 8169
     74 &lt;-- Tread Tag 0 fid 2 offset 0 count 4096
     75 --&gt; Rread Tag 0 count 7 ' 776f726c 64210a'
     76 &lt;-- Tread Tag 0 fid 2 offset 7 count 4096
     77 --&gt; Rread Tag 0 count 0 ''
     78 &lt;-- Tclunk Tag 0 fid 2
     79 --&gt; Rclunk Tag 0
     80 </code></pre>
     81 <p>
     82 まず最初にクライアントがサーバーに対して<code>Tversion</code>を送り、\
     83 プロトコルのバージョンと、メッセージの最大サイズを交渉する。\
     84 <code>Tversion</code>のタグは<code>65535</code>と決められている。\
     85 <code>msize</code>の8192はメッセージの最大サイズ(バイト)で、\
     86 <code>version</code>の9P2000がプロトコルのバージョンである。\
     87 サーバーから<code>Rversion</code>で同じ\
     88 <code>msize</code>と<code>version</code>が返ってきたので\
     89 このサイズとバージョンで以降の通信を行う。\
     90 </p>
     91 <p>
     92 次の<code>Tattach</code>で、クライアントがサーバーにルートディレクトリの\
     93 <code>fid</code>を要求する。\
     94 <code>afid</code>以降は認証用の情報である(後述)。\
     95 <code>fid</code>はコネクションにおいてファイルと紐付けられる整数で、\
     96 Unixにおけるファイルディスクリプタのようなものである。\
     97 ファイルの読み書きはこの<code>fid</code>を使って行われる。\
     98 サーバーからの<code>Rattach</code>にはルートディレクトリの\
     99 <code>qid</code>が含まれる。\
    100 これはサーバー上でファイルを一意に識別するもので、\
    101 Unixにおけるinodeに相当するものである。\
    102 以上でサーバーに繋いでセッションを確立できた。\
    103 </p>
    104 <p>
    105 次に<code>Twalk</code>で目的のファイル(<code>hello</code>)まで\
    106 ファイルツリーを辿る。\
    107 ここでは起点として先程得られたルートディレクトリ(<code>fid = 1</code>)\
    108 を起点として、このディレクトリ内の<code>hello</code>ファイルを要求して\
    109 <code>fid = 2</code>を割り当てようとしている。\
    110 サーバーはルートディレクトリ内に要求されたファイルが見付かったので、\
    111 そのファイルの<code>qid</code>を返す。\
    112 </p>
    113 <p>
    114 目的のファイルに<code>fid</code>が割り当てられたので、\
    115 <code>Tstat</code>でこのファイルの属性を要求している。\
    116 おそらくファイルのパーミッションを調べるためである。\
    117 </p>
    118 <p>
    119 次に<code>Topen</code>を使ってこのファイルを開き、\
    120 <code>Tread</code>を使って内容を読む。\
    121 二回目の<code>Tread</code>に対して0バイトのデータが返ってきたので、\
    122 ファイルはこれで終りである。\
    123 </p>
    124 <p>
    125 最後に、必要なくなった<code>fid</code>は<code>Tclunk</code>で捨てる。\
    126 </p>
    127 
    128 <h3>List of all Message Types</h3>
    129 
    130 <table>
    131 <tr>
    132 <th>Tメッセージ</th>
    133 <th>Rメッセージ</th>
    134 <th>説明</th>
    135 </tr>
    136 <tr>
    137 <td>Tversion</td>
    138 <td>Rversion</td>
    139 <td>バージョンの交渉</td>
    140 </tr>
    141 <tr>
    142 <td>Tauth</td>
    143 <td>Rauth</td>
    144 <td>認証ファイルの取得</td>
    145 </tr>
    146 <tr>
    147 <td></td>
    148 <td>Rerror</td>
    149 <td>エラー(Rメッセージのみ)</td>
    150 </tr>
    151 <tr>
    152 <td>Tflush</td>
    153 <td>Rflush</td>
    154 <td>処理中のリクエストをキャンセル</td>
    155 </tr>
    156 <tr>
    157 <td>Tattach</td>
    158 <td>Rattach</td>
    159 <td>ルートディレクトリの取得</td>
    160 </tr>
    161 <tr>
    162 <td>Twalk</td>
    163 <td>Rwalk</td>
    164 <td>ファイルツリーを辿る</td>
    165 </tr>
    166 <tr>
    167 <td>Topen</td>
    168 <td>Ropen</td>
    169 <td>ファイルを開く</td>
    170 </tr>
    171 <tr>
    172 <td>Tcreate</td>
    173 <td>Rcreate</td>
    174 <td>ファイルの作成</td>
    175 </tr>
    176 <tr>
    177 <td>Tread</td>
    178 <td>Rread</td>
    179 <td>ファイルを読む</td>
    180 </tr>
    181 <tr>
    182 <td>Twrite</td>
    183 <td>Rwrite</td>
    184 <td>ファイルへ書き込む</td>
    185 </tr>
    186 <tr>
    187 <td>Tclunk</td>
    188 <td>Rclunk</td>
    189 <td>fidの削除</td>
    190 </tr>
    191 <tr>
    192 <td>Tremove</td>
    193 <td>Rremove</td>
    194 <td>ファイルを削除</td>
    195 </tr>
    196 <tr>
    197 <td>Tstat</td>
    198 <td>Rstat</td>
    199 <td>ファイルの属性を取得</td>
    200 </tr>
    201 <tr>
    202 <td>Twstat</td>
    203 <td>Rwstat</td>
    204 <td>ファイルの属性を変更</td>
    205 </tr>
    206 </table>
    207 
    208 <h2><code>fid</code>と<code>qid</code></h2>
    209 <p>
    210 <code>fid</code>はコネクションにおいてファイルを指定するために\
    211 使われる整数で、Unixのファイルディスクリプタのようなものである。\
    212 使用する整数はクライアントが指定できる。\
    213 サーバーに接続する際に<code>Tattach</code>により、サーバーの\
    214 ルートディレクトリの<code>fid</code>が設定され、\
    215 <code>Twalk</code>により追加され、<code>Tclunk</code>により削除される。\
    216 </p>
    217 <p>
    218 <code>qid</code>はサーバーがファイルを一意に識別するためのもので、\
    219 Unixのinodeに相当するものである。\
    220 ただしinodeはファイルが削除されると再利用される可能性があるのに対し、\
    221 <code>qid</code>は再利用できない。\
    222 <code>qid</code>はこのファイルがディレクトリかどうか等を示す<code>type</code>、\
    223 ファイルが変更されたときにインクリメントされる<code>vers</code>、\
    224 そしてファイルを一意に表す<code>path</code>の3つの情報で構成される。\
    225 </p>
    226 <h2>認証</h2>
    227 <p>
    228 9Pにはクライアントの認証が組込まれていない。\
    229 もともとはあったようだが、認証のアルゴリズムに欠陥が見付かったりすれば\
    230 いちいちコードを修正してコンパイルしなおさなければいけないうえ、\
    231 9Pプロトコル自体も変更しないといけないので、外部に切りだすことになった。\
    232 認証に必要なやりとりは認証サーバーとクライアントが行い、\
    233 9Pサーバーは認証が成功したかどうかの情報を認証サーバーに確認することで\
    234 クライアントの確認を行う。
    235 </p>
    236 <p>
    237 9Pサーバーは<code>Tattach</code>メッセージを受けとると、\
    238 認証サーバーとのコネクションを確立する。\
    239 その後<code>Rattach</code>メッセージで<code>afid</code>という\
    240 特殊なFidをクライアントに返す。\
    241 クライアントからこの<code>afid</code>に対して読み書きする命令が届くと、\
    242 9Pサーバーはこの読み書きを認証サーバーとのコネクションに横流しする。\
    243 つまりクライアントはこの<code>afid</code>を通じて認証サーバーと\
    244 直接やりとりができる。\
    245 クライアントはユーザー名やパスワード等(パスワード認証の場合)の情報を\
    246 <code>afid</code>に書き込んで認証する。\
    247 このように認証をプロトコル自体から切り離すことで、\
    248 認証に関するバグが見付かったとしても、認証サーバーを修正するだけでいい。\
    249 また、より強力な認証システムを組み込むのも楽である。
    250 </p>
    251 
    252 <h2>コネクションの共有</h2>
    253 <p>
    254 クライアントはサーバーと<code>version</code>メッセージを交すことで\
    255 コネクションを確立する。\
    256 コネクションの確立からの一連のやりとりをセッションという。\
    257 ひとつのコネクションは複数のクライアントが共有できる。\
    258 </p>
    259 <p>
    260 クライアントは<code>Tauth</code>または<code>Tattach</code>メッセージにより\
    261 サーバーに<code>fid</code>を要求できる。\
    262 このうち<code>Tauth</code>により得られた<code>fid</code>は認証以外には使えない。\
    263 <code>Tattach</code>で得られた<code>fid</code>は、\
    264 <code>Twalk</code>メッセージによりファイルツリーを辿るための起点として利用でき、\
    265 このとき辿った先のファイルを示す<code>fid</code>が生成される。\
    266 これ以外の方法で<code>fid</code>が増えることはない。\
    267 つまり、サーバーはひとつのコネクションのなかで、<code>Tattach</code>から\
    268 派生した<code>fid</code>を追跡することで、クライアントを区別できる。\
    269 </p>
    270 
    271 <h2>メッセージ詳細</h2>
    272 <p>
    273 各メッセージの詳細を書く。\
    274 それぞれ最初にメッセージの形式を書く。\
    275 <code><i>field</i>[<i>n</i>]</code>は<code><i>field</i></code>\
    276 という名前の<i>n</i>バイトのデータを表す(<i>n</i>は整数)。\
    277 また、括弧の中が整数ではなくsになっている場合、そのフィールドは\
    278 文字列のデータで、その前に文字列の長さを示す2バイトのデータが先行する。\
    279 </p>
    280 <h3>version</h3>
    281 <p>
    282 プロトコルのバージョンを交渉すし、コネクションを確立する。
    283 </p>
    284 <pre><code>\
    285 size[4] Tversion tag[2] msize[4] version[s]
    286 size[4] Rversion tag[2] msize[4] version[s]
    287 </code></pre>
    288 <p>
    289 クライアントは最初にサーバーにこのメッセージを送ってバージョンと\
    290 メッセージの最大サイズ(<code>msize</code>)を交渉する。\
    291 サーバーはクライアントから送られたバージョンとメッセージサイズに\
    292 対応していれば、同じバージョンとサイズを送り返し、交渉成立である。\
    293 </p>
    294 <p>
    295 サーバーとクライアントはこのメッセージを以ってコネクションを確立する。\
    296 </p>
    297 
    298 <h3>attach、auth</h3>
    299 <p>
    300 コネクションを確立する。
    301 </p>
    302 <pre><code>\
    303 size[4] Tauth tag[2] afid[4] uname[s] aname[s]
    304 size[4] Rauth tag[2] aqid[13]
    305 
    306 size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s]
    307 size[4] Rattach tag[2] qid[13]
    308 </code></pre>
    309 <p>
    310 <code>attach</code>メッセージはサーバーのルートディレクトリを要求し、\
    311 <code>fid</code>を割り当てる。\
    312 認証が必要なサーバーであれば、\
    313 先に<code>auth</code>メッセージで認証を済ませておき、\
    314 <code>attach</code>メッセージの<code>afid</code>に、\
    315 先程使用した<code>afid</code>をセットする。\
    316 サーバーはこの<cdoe>afid</code>に紐付いている認証サーバーに\
    317 クライアントが認証済みであるかを確認し、\
    318 認証済みであればルートディレクトリの<code>qid</code>を返す。\
    319 サーバーが複数のファイルツリーを公開している場合、\
    320 <code>aname</code>で指定する。\
    321 </p>
    322 <p>
    323 <code>auth</code>メッセージは認証サーバーとやりとりするための\
    324 <code>fid</code>である<code>afid</code>を要求する。\
    325 この<code>afid</code>を読み書きすることで\
    326 認証サーバーとやりとりをし、自身を身分を証明する。\
    327 認証が済んだらこの<code>afid</code>をセットした\
    328 <code>attach</code>メッセージを送る。\
    329 </p>
    330 
    331 <h3>error</h3>
    332 <p>
    333 エラーを返す。
    334 </p>
    335 <pre><code>\
    336 size[4] Rerror tag[2] ename[s]
    337 </code></pre>
    338 <p>
    339 クライアントからの要求に対して、なにかエラーがおきたときに\
    340 そのエラーを返すために使われる。\
    341 そのためサーバーからクライアントへの<code>Rerror</code>はあるが\
    342 逆向きのTメッセージはない。\
    343 </p>
    344 
    345 <h3>flush</h3>
    346 <p>
    347 リクエストをキャンセルする。
    348 </p>
    349 <pre><code>\
    350 size[4] Tflush tag[2] oldtag[2]
    351 size[4] Rflush tag[2]
    352 </code></pre>
    353 <p>
    354 実行中の要求をキャンセルする。\
    355 キャンセルしたいリクエストの<code>tag</code>を<code>oldtag</code>にセットする。
    356 </p>
    357 
    358 <h3>walk</h3>
    359 <p>
    360 ファイルツリーを移動する。
    361 </p>
    362 <pre><code>\
    363 size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s])
    364 size[4] Rwalk tag[2] nwqid[2] nwqid*(qid[13])
    365 </code></pre>
    366 <p>
    367 <code>fid</code>を起点に移動する。\
    368 移動先のファイルを<code>newfid</code>にセットする。\
    369 移動するディレクトリの数を<code>nwname</code>に、\
    370 移動するディレクトリの名前を移動する順に<code>wname</code>に\
    371 それぞれセットする。\
    372 例えばカレントディレクトリから<code>./dir1/dir2/</code>に\
    373 移動する場合、<code>nwname</code>は<code>2</code>、<code>wname</code>は\
    374 <code>{"dir1", "dir2"}</code>となる。\
    375 <code>wname</code>の最後の要素はディレクトリでなくファイルでもいい。\
    376 </p>
    377 <p>
    378 <code>wname</code>の最初の要素への移動が失敗した場合は\
    379 <code>Rerror</code>が返される。\
    380 それ以外の場合は<code>Rwalk</code>が返され、\
    381 <code>qid</code>は移動が成功した順番にそのファイルの<code>qid</code>\
    382 がセットされる。\
    383 <code>nwname</code>と<code>nwqid</code>が一致した場合は\
    384 最後まで移動できたことになる。\
    385 </p>
    386 <p>
    387 <code>walk</code>は<code>fid</code>を増殖させる唯一の方法である。\
    388 </p>
    389 
    390 <h3>open、create</h3>
    391 <p>
    392 <code>fid</code>を開いて(作成して)読み書きできる状態にする。
    393 </p>
    394 <pre><code>\
    395 size[4] Topen tag[2] fid[4] mode[1]
    396 size[4] Ropen tag[2] qid[13] iounit[4]
    397 
    398 size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]
    399 size[4] Rcreate tag[2] qid[13] iounit[4]
    400 </code></pre>
    401 <p>
    402 <code>open</code>は<code>fid</code>と紐付いたファイルを開く。\
    403 <code>mode</code>は読み込み専用の<code>OREAD</code>、\
    404 書き込み専用の<code>OWRITE</code>、\
    405 読み書きの<code>ORDWR</code>等である。\
    406 ファイルを開く手順は以下の通り:
    407 <ol>
    408 <li><code>attach</code>でルートディレクトリの<code>fid</code>を取得</li>
    409 <li>ルートディレクトリの<code>fid</code>から\
    410 <code>walk</code>で開きたいファイルの<code>fid</code>を取得</li>
    411 <li>その<code>fid</code>を<code>open</code>で開く</li>
    412 </ol>
    413 </p>
    414 <p>
    415 <code>create</code>は<code>fid</code>と紐付いたディレクトリに\
    416 新しいファイルを作成する。\
    417 <code>creat</code>ではなく<code>create</code>である。\
    418 作成が成功したら<code>fid</code>は新しく作成されたファイルに紐付く。\
    419 </p>
    420 
    421 <h3>read、write</h3>
    422 <p>
    423 ファイルを読み書きする。
    424 </p>
    425 <pre><code>\
    426 size[4] Tread tag[2] fid[4] offset[8] count[4]
    427 size[4] Rread tag[2] count[4] data[count]
    428 
    429 size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count]
    430 size[4] Rwrite tag[2] count[4]
    431 </code></pre>
    432 <p>
    433 <code>read</code>は<code>fid</code>の<code>offset</code>バイト目\
    434 から<code>count</code>バイト読む。\
    435 読んだデータと、読めたデータサイズが\
    436 <code>data</code>、<code>count</code>ととして返される。\
    437 <code>write</code>は<code>fid</code>の<code>offset</code>バイト目\
    438 に<code>count</code>バイトのデータ<code>data</code>を書く。\
    439 書きこめたサイズが<code>count</code>として返される。\
    440 ファイルを読み書きするためには\
    441 あらかじめ<code>fid</code>を<code>open</code>する必要がある。\
    442 </p>
    443 
    444 <h3>clunk</h3>
    445 <p>
    446 <code>fid</code>を忘れる。
    447 </p>
    448 <pre><code>\
    449 size[4] Tclunk tag[2] fid[4]
    450 size[4] Rclunk tag[2]
    451 </code></pre>
    452 <p>
    453 いらなくなった<code>fid</code>を忘れる。\
    454 一度忘れた<code>fid</code>は<code>walk</code>で別のファイルを取得する際に\
    455 再利用できる。\
    456 </p>
    457 
    458 <h3>remove</h3>
    459 <p>
    460 ファイルを削除する。
    461 </p>
    462 <pre><code>\
    463 size[4] Tremove tag[2] fid[4]
    464 size[4] Rremove tag[2]
    465 </code></pre>
    466 <p>
    467 <code>fid</code>と紐付いたサーバー上のファイルを削除する。\
    468 <code>fid</code>は<code>clunk</code>したのと同様に忘れられる。\
    469 </p>
    470 
    471 <h3>stat、wstat</h3>
    472 <p>
    473 ファイルの属性を読み書きする。
    474 </p>
    475 <pre><code>\
    476 size[4] Tstat tag[2] fid[4]
    477 size[4] Rstat tag[2] stat[n]
    478 
    479 size[4] Twstat tag[2] fid[4] stat[n]
    480 size[4] Rwstat tag[2]
    481 </code></pre>
    482 <p>
    483 <code>stat</code>はファイルの属性を読む。\
    484 読めた情報は<code>stat</code>として返される。\
    485 <code>wstat</code>はファイルの属性を<code>stat</code>に変更する。\
    486 <code>stat</code>はファイルの名前や<code>qid</code>、サイズ、\
    487 作成日、更新日等の情報がバイト列になったものである。\
    488 </p>
    489 
    490 <h2>Goによる実装</h2>
    491 <h3>メッセージの構造体の定義</h3>
    492 <p>
    493 メッセージには<code>size</code>、<code>tag</code>等共通の要素がある。\
    494 クライアントからメッセージを受けとる際、\
    495 <code>size</code>を参照して何バイト読むか判断し、\
    496 メッセージの<code>type</code>によりその後の処理を場合分けする。\
    497 そのためにメッセージを<code>interface</code>として定義した:
    498 </p>
    499 <pre><code>\
    500 // A Msg represents any kind of message of 9P.
    501 // It defines methods for common fields.
    502 // For each message type &lt;T&gt;, new&lt;T&gt;([]byte) function parses the byte array
    503 // of 9P message into the corresponding message struct.
    504 // For detailed information on each message, consult the documentation
    505 // of 9P protocol.
    506 type Msg interface {
    507 	// Size returns the size field of message.
    508 	// Size field holds the size of the message in bytes
    509 	// including the 4-byte size field itself.
    510 	Size() uint32
    511 	// Type returns the type field of message.
    512 	Type() MsgType
    513 	// GetTag returns the Tag of message.
    514 	// Tag is the identifier of each message.
    515 	// The Get prefix is to avoid name confliction with the each
    516 	// message's Tag field.
    517 	GetTag() uint16
    518 	// SetTag sets the Tag field of the message.
    519 	SetTag(uint16)
    520 	// Marshal convert Msg to byte array to be transmitted.
    521 	marshal() []byte
    522 	String() string
    523 }
    524 </code></pre>
    525 <p>
    526 <code>marshal()</code>はサーバーから返信を送る際に\
    527 メッセージの構造体をバイト列にエンコードするための関数である。\
    528 <code>String() string</code>はログ出力用。\
    529 </p>
    530 <p>
    531 メッセージはバイト列として受け取った後、\
    532 メッセージのタイプによって処理を分ける。\
    533 その際、扱いやすいようにバイト列を各メッセージの構造体に\
    534 変換する。\
    535 変換前のバイト列から、各メッセージに共通のフィールドを\
    536 取得できると便利なので、\
    537 バイト列のまま<code>Msg</code>インターフェースを実装する\
    538 <code>bufMsg</code>を定義した。\
    539 名前はいまいち。\
    540 </p>
    541 <pre><code>\
    542 type bufMsg []byte
    543 
    544 func (msg bufMsg) Size() uint32    { return gbit32(msg[0:4]) }
    545 func (msg bufMsg) Type() MsgType   { return MsgType(msg[4]) }
    546 func (msg bufMsg) GetTag() uint16  { return gbit16(msg[5:7]) }
    547 func (msg bufMsg) SetTag(t uint16) { pbit16(msg[5:7], t) }
    548 func (msg bufMsg) marshal() []byte { return []byte(msg)[:msg.Size()] }
    549 func (msg bufMsg) String() string {
    550 	switch msg.Type() {
    551 	case Tversion:
    552 		return newTVersion(msg).String()
    553 		/* 省略  */
    554 	}
    555 }
    556 </code></pre>
    557 <p>
    558 <code>gbit32</code>は4バイトのリトルエンディアンの配列を\
    559 整数に変換するもので、\
    560 <code>pbit32</code>は逆に整数をリトルエンディアンのスライスに\
    561 格納するものである。\
    562 </p>
    563 <p>
    564 各メッセージはそれぞれのフィールドを構造体のフィールドとして\
    565 定義したものである。\
    566 例えば<code>tversion</code>に対応する構造体は以下の通り。\
    567 </p>
    568 <pre><code>\
    569 type TVersion struct {
    570     Tag     uint16
    571     Msize   uint32
    572     Version string
    573 }
    574 </code></pre>
    575 <p>
    576 バイト列から各メッセージの構造体に変換するために\
    577 <code>new<i>メッセージタイプ</i></code>\
    578 を定義した。
    579 </p>
    580 <pre><code>\
    581 func newTVersion(buf []byte) *TVersion {
    582     msg := new(TVersion)
    583     msg.Tag = gbit16(buf[5:7])
    584     msg.Msize = gbit32(buf[7:11])
    585     vs := gbit16(buf[11:13])
    586     msg.Version = string(buf[13 : 13+vs])
    587     return msg
    588 }
    589 </code></pre>
    590 
    591 <h3>メッセージのやりとり</h3>
    592 <p>
    593 まずは9Pメッセージを読む関数を実装する。\
    594 バイト列が読めればいいので引数は<code>io.Reader</code>にした:
    595 </p>
    596 <pre><code>\
    597 func readMsg(r io.Reader) ([]byte, error) {
    598 </code></pre>
    599 <p>
    600 最初にメッセージのサイズを読む:
    601 </p>
    602 <pre><code>	buf := make([]byte, 4)
    603 	read, err := r.Read(buf)
    604 	if err != nil {
    605 		if err == io.EOF {
    606 			return nil, err
    607 		}
    608 		return nil, fmt.Errorf(&quot;read size: %v&quot;, err)
    609 	}
    610 	if read != len(buf) {
    611 		return buf, fmt.Errorf(&quot;read size: invalid message.&quot;)
    612 	}
    613 	size := bufMsg(buf).Size()
    614 </code></pre>
    615 <p>
    616 続いて読みこんだサイズに基づいてメッセージの残りの部分を読む。
    617 一回の<code>Read</code>で最後まで読めないことがあったので、
    618 全部読めるまで<code>Read</code>を繰り返す:
    619 </p>
    620 <pre><code>	mbuf := make([]byte, size-4)
    621 	for read = 0; read &lt; int(size)-4; {
    622 		n, err := r.Read(mbuf[read:])
    623 		if err != nil {
    624 			return buf, fmt.Errorf(&quot;read body: %v&quot;, err)
    625 		}
    626 		read += n
    627 	}
    628 	buf = append(buf, mbuf...)
    629 	return buf, nil
    630 }
    631 </code></pre>
    632 <p>
    633 読み込んだバイト列は<code>ReadMsg</code>関数で\
    634 メッセージの構造体に変換する。\
    635 </p>
    636 <pre><code>\
    637 RecvMsg(r io.Reader) (Msg, error) {
    638 	b, err := readMsg(r)
    639 	if err == io.EOF {
    640 		return nil, err
    641 	} else if err != nil {
    642 		return nil, fmt.Errorf(&quot;readMsg: %v&quot;, err)
    643 	}
    644 	return unmarshal(b)
    645 }
    646 </code></pre>
    647 <p>
    648 返信のメッセージはメッセージの構造体を<code>marshal</code>関数\
    649 でバイト列に変換して<code>io.Writer</code>に書き込む。\
    650 </p>
    651 <pre><code>\
    652 // SendMsg send a 9P message to w
    653 func SendMsg(msg Msg, w io.Writer) error {
    654 	if _, err := w.Write(msg.marshal()); err != nil {
    655 		return fmt.Errorf(&quot;write: %v&quot;, err)
    656 	}
    657 	return nil
    658 }
    659 </code></pre>
    660 
    661 <h3>メインループ</h3>
    662 <p>
    663 ライブラリはメッセージを読み込むためのリスナーgoroutinと\
    664 返信を書き込むためのレスポンダーgoroutine、\
    665 そして各メッセージを処理するためのgoroutineを立ち上げて、\
    666 チャネルでそれぞれを繋ぐ。\
    667 リスナーがメッセージを読み込むと、<code>request</code>構造体に格納し、\
    668 メッセージのタイプに応じて各goroutineに渡す。\
    669 渡されたgoroutineはメッセージに応じた処理をし、\
    670 返信のメッセージをリスポンダーgoroutineに渡す。\
    671 リスポンダーgoroutineはメッセージをバイト列に変換して返信する。
    672 </p>
    673 <p>
    674 この設計がいいかどうかよく知らんけど、\
    675 Go言語を触るからgoroutineとチャネルによるパイプラインを作ってみたかった。\
    676 </p>
    677 
    678 <h3>各メッセージの処理</h3>
    679 <p>
    680 メッセージは<code>s<i>メッセージタイプ</i></code>goroutineで処理する。\
    681 関数は<code>for</code>ループのなかでチャンネル<code>rc</code>から\
    682 <code>request</code>が届くのを待つ。\
    683 届いたリクエストには<code>ifcall</code>フィールドにクライアントからの\
    684 <code>Msg</code>が含まれるので、\
    685 この関数が担当するstructにキャストする。\
    686 このキャストが失敗するのは明かなサーバーのバグなのでタイプアサーションはしない。\
    687 </p>
    688 <pre><code>\
    689 func s<i>メッセージタイプ</i>(ctx context.Context, c *conn, rc &lt;-chan *request) {
    690 	for {
    691 		select {
    692 		case &lt;-ctx.Done();
    693 			return
    694 		case r, ok := rc:
    695 			if !ok {
    696 				return
    697 			}
    698 			ifcall := r.ifcall.(*T<i>メッセージタイプ</i>)
    699 </code></pre>
    700 <p>
    701 各種処理を完了したら、届いた<code>request</code>の<code>ofcall</code>に\
    702 返信の<code>Msg</code>を格納して返信を担当するgoroutineに送る。\
    703 <pre><code>			select {
    704 			case c.respChan &lt;- r:
    705 			case &lt;-ctx.Done():
    706 				return
    707 			}
    708 </code></pre>
    709 </p>
    710 <h4>Version</h4>
    711 <p>
    712 クライアントから届いたバージョンの提案を見て、頭に"9P2000"があれば\
    713 返信のバージョンを"9P2000"にする。\
    714 現在定義されているバージョンは"9P2000"の他、\
    715 Unix用に拡張した"9P2000.u"、Linux用に拡張した"9P2000.l"がある。\
    716 基本的な昨日を実装することが目標なので、"9P2000"のみを受け付ける。\
    717 </p>
    718 <p>
    719 Versionメッセージではメッセージの最大サイズも決定する。\
    720 サーバーは予め8Kバイトを最大サイズにしている。\
    721 クライアントがこれ以上のサイズを提案した場合、8Kにしてもらい、\
    722 これ以下のサイズを提案した場合、そのサイズでメッセージをやりとりする。\
    723 8Kバイトという既定値はplan9の実装を参考にした。\
    724 このサイズである必然性はよく知らない。\
    725 </p>
    726 <pre><code>			version := ifcall.Version
    727 			if strings.HasPrefix(version, &quot;9P2000&quot;) {
    728 				version = &quot;9P2000&quot;
    729 			} else {
    730 				version = &quot;unknown&quot;
    731 			}
    732 			msize := ifcall.Msize
    733 			if msize &gt; c.mSize() {
    734 				msize = c.mSize()
    735 			}
    736 			r.ofcall = &amp;RVersion{
    737 				Msize:   msize,
    738 				Version: version,
    739 			}
    740 			c.setMSize(r.ofcall.(*RVersion).Msize)
    741 </code></pre>
    742 
    743 <h4>Auth</h4>
    744 <p>
    745 サーバーの認証は<code>Server</code>の<code>Auth</code>フィールドに\
    746 認証のためのやりとりを待ち受ける関数を登録することで有効化できる。\
    747 このフィールドが<code>nil</code>の場合、認証が必要ない旨のエラーを返す。\
    748 </p>
    749 <pre><code>			if c.s.Auth == nil {
    750 				r.err = fmt.Errorf(&quot;authentication not required&quot;)
    751 				goto resp
    752 			}
    753 </code></pre>
    754 <p>
    755 認証が必要な場合、クライアントから提案された<code>afid</code>をコネクションに\
    756 登録して、<code>Server.Auth</code>を呼び出す。
    757 </p>
    758 
    759 <h4>attach</h4>
    760 <p>
    761 サーバーはまず認証が必要な場合認証が済んでいるか確認する。\
    762 </p>
    763 <pre><code>			switch {
    764 			case c.s.Auth == nil &amp;&amp; ifcall.Afid == NOFID:
    765 			case c.s.Auth == nil &amp;&amp; ifcall.Afid != NOFID:
    766 				r.err = ErrBotch
    767 				goto resp
    768 			case c.s.Auth != nil &amp;&amp; ifcall.Afid == NOFID:
    769 				r.err = fmt.Errorf(&quot;authentication required&quot;)
    770 				goto resp
    771 			case c.s.Auth != nil &amp;&amp; ifcall.Afid != NOFID:
    772 				afid, ok := c.fPool.lookup(ifcall.Afid)
    773 				if !ok {
    774 					r.err = ErrUnknownFid
    775 					goto resp
    776 				}
    777 				af, ok := afid.file.(*AuthFile)
    778 				if !ok {
    779 					r.err = fmt.Errorf(&quot;not auth file&quot;)
    780 					goto resp
    781 				}
    782 				if af.Uname != ifcall.Uname || af.Aname != ifcall.Aname || !af.AuthOK {
    783 					r.err = fmt.Errorf(&quot;not authenticated&quot;)
    784 					goto resp
    785 				}
    786 			}
    787 </code></pre>
    788 <p>
    789 次にクライアントが要求してきたファイルツリーがサーバーにあるかどうか確認し、\
    790 クライアントが設定した<code>fid</code>にルートディレクトリを紐付ける。\
    791 </p>
    792 <pre><code>			r.fid, err = c.fPool.add(ifcall.Fid)
    793 			if err != nil {
    794 				r.err = ErrDupFid
    795 				goto resp
    796 			}
    797 			r.fid.path = &quot;.&quot;
    798 			r.fid.uid = ifcall.Uname
    799 			fsys, ok = c.s.fsmap[ifcall.Aname]
    800 			if !ok {
    801 				r.err = fmt.Errorf(&quot;no such file system&quot;)
    802 				goto resp
    803 			}
    804 			r.fid.fs = fsys
    805 			fi, err = fs.Stat(ExportFS{c.s.fsmap[ifcall.Aname]}, &quot;.&quot;)
    806 			if err != nil {
    807 				r.err = fmt.Errorf(&quot;stat root: %v&quot;, err)
    808 				goto resp
    809 			}
    810 			r.fid.qidpath = fi.Sys().(*Stat).Qid.Path
    811 			r.ofcall = &amp;RAttach{
    812 				Qid: fi.Sys().(*Stat).Qid,
    813 			}
    814 </code></pre>
    815 
    816 <h4>flush</h4>
    817 <p>
    818 指定されたリクエストの<code>flush</code>メソッドを呼び出す。\
    819 <code>flush</code>メソッドはリクエストの<code>done</code>チャンネルを閉じ、\
    820 リクエストのタグを削除する。\
    821 処理に時間のかかるものは、リクエストの<code>done</code>チャネルが閉じられたら、\
    822 その処理を中止する。\
    823 </p>
    824 <pre><code>\
    825 // flush cancels the request by calling r.cancel.
    826 // It also delete the request from its pool.
    827 func (r *request) flush() {
    828 	close(r.done)
    829 	r.pool.delete(r.tag)
    830 }
    831 </code></pre>
    832 
    833 <h4>walk</h4>
    834 <p>
    835 仕様に書かれている通りの条件を確認した後、\
    836 ファイルツリーを順番に辿り、得られた<code>Qid</code>を\
    837 記録してクライアントに返す。\
    838 </p>
    839 <pre><code>			wqids = make([]Qid, 0, len(ifcall.Wnames))
    840 			cwdp = oldFid.path
    841 			for _, name := range ifcall.Wnames {
    842 				cwdp = path.Clean(path.Join(cwdp, name))
    843 				if cwdp == &quot;..&quot; {
    844 					cwdp = &quot;.&quot; // parent of the root is itself.
    845 				}
    846 				stat, err := fs.Stat(ExportFS{oldFid.fs}, cwdp)
    847 				if err != nil {
    848 					break
    849 				}
    850 				wqids = append(wqids, stat.Sys().(*Stat).Qid)
    851 			}
    852 			if len(wqids) == 0 {
    853 				newFid.qidpath = oldFid.qidpath
    854 			} else {
    855 				newFid.qidpath = wqids[len(wqids)-1].Path
    856 			}
    857 			newFid.path = cwdp
    858 			newFid.uid = oldFid.uid
    859 			newFid.fs = oldFid.fs
    860 			r.ofcall = &amp;RWalk{
    861 				Qids: wqids,
    862 			}
    863 </code></pre>
    864 
    865 <h4>open</h4>
    866 <h4>create</h4>
    867 <h4>read</h4>
    868 <h4>write</h4>
    869 <h4>clunk</h4>
    870 <h4>remove</h4>
    871 <h4>stat</h4>
    872 <h4>wstat</h4>