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 <-- Tversion Tag 65535 msize 8192 version '9P2000' 63 --> Rversion Tag 65535 msize 8192 version '9P2000' 64 <-- Tattach Tag 0 fid 0 afid -1 uname kenji aname 65 --> Rattach Tag 0 qid (0000000000000000 0 d) 66 <-- Twalk Tag 0 fid 0 newfid 1 nwname 1 0:hello 67 --> Rwalk Tag 0 nwqid 1 0:(0000000000000001 0 ) 68 <-- Tstat Tag 0 fid 1 69 --> Rstat Tag 0 stat 'hello' 'kenji' 'kenji' '' q (0000000000000001 0 ) m 0644 at 1730004808 mt 1730004808 l 7 t 0 d 0 70 <-- Twalk Tag 0 fid 1 newfid 2 nwname 0 71 --> Rwalk Tag 0 nwqid 0 72 <-- Topen Tag 0 fid 2 mode 0x0 73 --> Ropen Tag 0 qid (0000000000000001 0 ) iounit 8169 74 <-- Tread Tag 0 fid 2 offset 0 count 4096 75 --> Rread Tag 0 count 7 ' 776f726c 64210a' 76 <-- Tread Tag 0 fid 2 offset 7 count 4096 77 --> Rread Tag 0 count 0 '' 78 <-- Tclunk Tag 0 fid 2 79 --> 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 <T>, new<T>([]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("read size: %v", err) 609 } 610 if read != len(buf) { 611 return buf, fmt.Errorf("read size: invalid message.") 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 < int(size)-4; { 622 n, err := r.Read(mbuf[read:]) 623 if err != nil { 624 return buf, fmt.Errorf("read body: %v", 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("readMsg: %v", 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("write: %v", 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 <-chan *request) { 690 for { 691 select { 692 case <-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 <- r: 705 case <-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, "9P2000") { 728 version = "9P2000" 729 } else { 730 version = "unknown" 731 } 732 msize := ifcall.Msize 733 if msize > c.mSize() { 734 msize = c.mSize() 735 } 736 r.ofcall = &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("authentication not required") 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 && ifcall.Afid == NOFID: 765 case c.s.Auth == nil && ifcall.Afid != NOFID: 766 r.err = ErrBotch 767 goto resp 768 case c.s.Auth != nil && ifcall.Afid == NOFID: 769 r.err = fmt.Errorf("authentication required") 770 goto resp 771 case c.s.Auth != nil && 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("not auth file") 780 goto resp 781 } 782 if af.Uname != ifcall.Uname || af.Aname != ifcall.Aname || !af.AuthOK { 783 r.err = fmt.Errorf("not authenticated") 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 = "." 798 r.fid.uid = ifcall.Uname 799 fsys, ok = c.s.fsmap[ifcall.Aname] 800 if !ok { 801 r.err = fmt.Errorf("no such file system") 802 goto resp 803 } 804 r.fid.fs = fsys 805 fi, err = fs.Stat(ExportFS{c.s.fsmap[ifcall.Aname]}, ".") 806 if err != nil { 807 r.err = fmt.Errorf("stat root: %v", err) 808 goto resp 809 } 810 r.fid.qidpath = fi.Sys().(*Stat).Qid.Path 811 r.ofcall = &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 == ".." { 844 cwdp = "." // 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 = &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>