www.mtkn.jp

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

9p.html (32610B)


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