9p.html (19244B)
1 <h1>9P</h1> 2 <time>2024-12-19</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 <h2>プロトコルの概要</h2> 20 <p> 21 9Pの通信ではクライアントがサーバーに対してリクエストを送り、サーバーがそれに\ 22 対してリプライを返すことを繰り返す。\ 23 クライアントからのリクエストをTメッセージ、サーバーからのリプライをRメッセージと\ 24 言う。\ 25 Tメッセージにはファイルにアクセスするための色々なものが用意されている。\ 26 例えばファイルを開くためのTopenや、\ 27 開いたファイルに書き込むためのTwrite等である。\ 28 それぞれのTメッセージには対応するRメッセージがある。\ 29 Topenに対してRopen、\ 30 Twriteに対してRwrite等である。\ 31 </p> 32 <p> 33 通信は必要な情報をプロトコルの定める方法でバイト列に変換して行う。\ 34 2バイト以上のデータはリトルエンディアンになるように配置する。\ 35 テキストデータはUTF-8にエンコードする。\ 36 テキストデータは長さの情報も一緒に送るので最後のヌル文字は含めない。\ 37 </p> 38 <p> 39 各メッセージは4バイトの整数から始まる。\ 40 これは、この4バイトを含むメッセージの長さをバイト単位で示したものである。\ 41 次にメッセージの種類を記述する1バイトのデータが来る。\ 42 次はメッセージのタグである。クライアントはサーバーからの返事を\ 43 待たずに別のメッセージを送れるので、サーバーからのRメッセージがどのTメッセージ\ 44 に対する返事なのかを識別する必要がある。そのために使うのがタグである。\ 45 クライアントがTメッセージを送る際にタグを付け、サーバーはそれに対するRメッセージに\ 46 同じタグを付ける。\ 47 その後ろには、メッセージの種類によって固有の情報が付けられる。\ 48 </p> 49 <p> 50 以下は実際の9P通信のログである。\ 51 実際はバイナリの通信だが、ログとして見易いように変換してある。\ 52 また、先頭にくるメッセージサイズは省略してある。\ 53 この例では、サーバーのルートディレクトリにある<code>hello</code>という\ 54 ファイルの内容を読みこんでいる:\ 55 </p> 56 <pre><code>\ 57 <-- Tversion Tag 65535 msize 8192 version '9P2000' 58 --> Rversion Tag 65535 msize 8192 version '9P2000' 59 <-- Tattach Tag 0 fid 0 afid -1 uname kenji aname 60 --> Rattach Tag 0 qid (0000000000000000 0 d) 61 <-- Twalk Tag 0 fid 0 newfid 1 nwname 1 0:hello 62 --> Rwalk Tag 0 nwqid 1 0:(0000000000000001 0 ) 63 <-- Tstat Tag 0 fid 1 64 --> Rstat Tag 0 stat 'hello' 'kenji' 'kenji' '' q (0000000000000001 0 ) m 0644 at 1730004808 mt 1730004808 l 7 t 0 d 0 65 <-- Twalk Tag 0 fid 1 newfid 2 nwname 0 66 --> Rwalk Tag 0 nwqid 0 67 <-- Topen Tag 0 fid 2 mode 0x0 68 --> Ropen Tag 0 qid (0000000000000001 0 ) iounit 8169 69 <-- Tread Tag 0 fid 2 offset 0 count 4096 70 --> Rread Tag 0 count 7 ' 776f726c 64210a' 71 <-- Tread Tag 0 fid 2 offset 7 count 4096 72 --> Rread Tag 0 count 0 '' 73 <-- Tclunk Tag 0 fid 2 74 --> Rclunk Tag 0 75 </code></pre> 76 <p> 77 まず最初にクライアントがサーバーに対してTversionを送り、\ 78 プロトコルのバージョンと、メッセージの最大サイズを交渉する。\ 79 Tversionのタグは<code>65535</code>と決められている。\ 80 <code>msize</code>の8192はメッセージの最大サイズ(バイト)で、\ 81 <code>version</code>の9P2000がプロトコルのバージョンである。\ 82 サーバーからRversionで同じ\ 83 <code>msize</code>と<code>version</code>が返ってきたので\ 84 このサイズとバージョンで以降の通信を行う。\ 85 </p> 86 <p> 87 次のTattachで、クライアントがサーバーにルートディレクトリの\ 88 <code>fid</code>を要求する。\ 89 <code>afid</code>以降は認証用の情報である(後述)。\ 90 <code>fid</code>はコネクションにおいてファイルと紐付けられる整数で、\ 91 Unixにおけるファイルディスクリプタのようなものである。\ 92 ファイルの読み書きはこの<code>fid</code>を使って行われる。\ 93 サーバーからのRattachにはルートディレクトリの\ 94 <code>qid</code>が含まれる。\ 95 これはサーバー上でファイルを一意に識別するもので、\ 96 Unixにおけるinodeに相当するものである。\ 97 以上でサーバーに繋いでセッションを確立できた。\ 98 </p> 99 <p> 100 次にTwalkで目的のファイル(<code>hello</code>)まで\ 101 ファイルツリーを辿る。\ 102 ここでは起点として先程得られたルートディレクトリ(<code>fid = 1</code>)\ 103 を起点として、このディレクトリ内の<code>hello</code>ファイルを要求して\ 104 <code>fid = 2</code>を割り当てようとしている。\ 105 サーバーはルートディレクトリ内に要求されたファイルが見付かったので、\ 106 そのファイルの<code>qid</code>を返す。\ 107 </p> 108 <p> 109 目的のファイルに<code>fid</code>が割り当てられたので、\ 110 Tstatでこのファイルの属性を要求している。\ 111 おそらくファイルのパーミッションを調べるためである。\ 112 </p> 113 <p> 114 次にTopenを使ってこのファイルを開き、\ 115 Treadを使って内容を読む。\ 116 二回目のTreadに対して0バイトのデータが返ってきたので、\ 117 ファイルはこれで終りである。\ 118 </p> 119 <p> 120 最後に、必要なくなった<code>fid</code>はTclunkで捨てる。\ 121 </p> 122 123 <h2>List of all Message Types</h2> 124 125 <table> 126 <tr> 127 <th>Tメッセージ</th> 128 <th>Rメッセージ</th> 129 <th>説明</th> 130 </tr> 131 <tr> 132 <td>Tversion</td> 133 <td>Rversion</td> 134 <td>バージョンの交渉</td> 135 </tr> 136 <tr> 137 <td>Tauth</td> 138 <td>Rauth</td> 139 <td>認証ファイルの取得</td> 140 </tr> 141 <tr> 142 <td></td> 143 <td>Rerror</td> 144 <td>エラー(Rメッセージのみ)</td> 145 </tr> 146 <tr> 147 <td>Tflush</td> 148 <td>Rflush</td> 149 <td>処理中のリクエストをキャンセル</td> 150 </tr> 151 <tr> 152 <td>Tattach</td> 153 <td>Rattach</td> 154 <td>ルートディレクトリの取得</td> 155 </tr> 156 <tr> 157 <td>Twalk</td> 158 <td>Rwalk</td> 159 <td>ファイルツリーを辿る</td> 160 </tr> 161 <tr> 162 <td>Topen</td> 163 <td>Ropen</td> 164 <td>ファイルを開く</td> 165 </tr> 166 <tr> 167 <td>Tcreate</td> 168 <td>Rcreate</td> 169 <td>ファイルの作成</td> 170 </tr> 171 <tr> 172 <td>Tread</td> 173 <td>Rread</td> 174 <td>ファイルを読む</td> 175 </tr> 176 <tr> 177 <td>Twrite</td> 178 <td>Rwrite</td> 179 <td>ファイルへ書き込む</td> 180 </tr> 181 <tr> 182 <td>Tclunk</td> 183 <td>Rclunk</td> 184 <td>fidの削除</td> 185 </tr> 186 <tr> 187 <td>Tremove</td> 188 <td>Rremove</td> 189 <td>ファイルを削除</td> 190 </tr> 191 <tr> 192 <td>Tstat</td> 193 <td>Rstat</td> 194 <td>ファイルの属性を取得</td> 195 </tr> 196 <tr> 197 <td>Twstat</td> 198 <td>Rwstat</td> 199 <td>ファイルの属性を変更</td> 200 </tr> 201 </table> 202 203 <h2><code>fid</code>と<code>qid</code></h2> 204 <p> 205 <code>fid</code>はコネクションにおいてファイルを指定するために\ 206 使われる整数で、Unixのファイルディスクリプタのようなものである。\ 207 使用する整数はクライアントが指定できる。\ 208 サーバーに接続する際にTattachにより、サーバーの\ 209 ルートディレクトリの<code>fid</code>が設定され、\ 210 Twalkにより追加され、Tclunkにより削除される。\ 211 </p> 212 <p> 213 <code>qid</code>はサーバーがファイルを一意に識別するためのもので、\ 214 Unixのinodeに相当するものである。\ 215 ただしinodeはファイルが削除されると再利用される可能性があるのに対し、\ 216 <code>qid</code>は再利用できない。\ 217 <code>qid</code>はこのファイルがディレクトリかどうか等を示す<code>type</code>、\ 218 ファイルが変更されたときにインクリメントされる<code>vers</code>、\ 219 そしてファイルを一意に表す<code>path</code>の3つの情報で構成される。\ 220 </p> 221 <h2>認証</h2> 222 <p> 223 9Pにはクライアントの認証が組込まれていない。\ 224 もともとはあったようだが、認証のアルゴリズムに欠陥が見付かったりすれば\ 225 いちいちコードを修正してコンパイルしなおさなければいけないうえ、\ 226 9Pプロトコル自体も変更しないといけないので、外部に切りだすことになった。\ 227 認証に必要なやりとりは認証サーバーとクライアントが行い、\ 228 9Pサーバーは認証が成功したかどうかの情報を認証サーバーに確認することで\ 229 クライアントの確認を行う。 230 </p> 231 <p> 232 9PサーバーはTattachメッセージを受けとると、\ 233 認証サーバーとのコネクションを確立する。\ 234 その後Rattachメッセージで<code>afid</code>という\ 235 特殊な<code>fid</code>をクライアントに返す。\ 236 クライアントからこの<code>afid</code>に対して読み書きする命令が届くと、\ 237 9Pサーバーはこの読み書きを認証サーバーとのコネクションに横流しする。\ 238 つまりクライアントはこの<code>afid</code>を通じて認証サーバーと\ 239 直接やりとりができる。\ 240 クライアントはユーザー名やパスワード等(パスワード認証の場合)の情報を\ 241 <code>afid</code>に書き込んで認証する。\ 242 このように認証をプロトコル自体から切り離すことで、\ 243 認証に関するバグが見付かったとしても、認証サーバーを修正するだけでいい。\ 244 また、より強力な認証システムを組み込むのも楽である。 245 </p> 246 247 <h2>コネクションの共有</h2> 248 <p> 249 クライアントはサーバーと<code>version</code>メッセージを交すことで\ 250 コネクションを確立する。\ 251 コネクションの確立からの一連のやりとりをセッションという。\ 252 ひとつのコネクションは複数のクライアントが共有できる。\ 253 </p> 254 <p> 255 クライアントはTauthまたはTattachメッセージにより\ 256 サーバーに<code>fid</code>を要求できる。\ 257 このうちTauthにより得られた<code>fid</code>は認証以外には使えない。\ 258 Tattachで得られた<code>fid</code>は、\ 259 Twalkメッセージによりファイルツリーを辿るための起点として利用でき、\ 260 このとき辿った先のファイルを示す<code>fid</code>が生成される。\ 261 これ以外の方法で<code>fid</code>が増えることはない。\ 262 つまり、サーバーはひとつのコネクションのなかで、ルートディレクトリから\ 263 派生した<code>fid</code>を追跡することで、クライアントを区別できる。\ 264 </p> 265 266 <h2>メッセージ詳細</h2> 267 <p> 268 それぞれ最初にメッセージの形式を書く。\ 269 <code><i>field</i>[<i>n</i>]</code>は<code><i>field</i></code>\ 270 という名前の<i>n</i>バイトのデータを表す(<i>n</i>は整数)。\ 271 また、括弧の中が整数ではなく<code>s</code>になっている場合、そのフィールドは\ 272 文字列のデータで、その前に文字列の長さを示す2バイトの整数が先行する。\ 273 また、メッセージ名のフィールド(<code>Tversion</code>等)にはメッセージの種類を\ 274 表す1バイトのenumが来る。 275 </p> 276 <h3>version</h3> 277 <p> 278 プロトコルのバージョンを交渉すし、コネクションを確立する。 279 </p> 280 <pre><code>\ 281 size[4] Tversion tag[2] msize[4] version[s] 282 size[4] Rversion tag[2] msize[4] version[s] 283 </code></pre> 284 <p> 285 クライアントは最初にサーバーにこのメッセージを送ってバージョンと\ 286 メッセージの最大サイズ(<code>msize</code>)を交渉する。\ 287 サーバーはクライアントから送られたバージョンとメッセージサイズに\ 288 対応していれば、同じバージョンとサイズを送り返し、交渉成立である。\ 289 </p> 290 <p> 291 サーバーとクライアントはこのメッセージを以ってコネクションを確立する。\ 292 </p> 293 294 <h3>attach、auth</h3> 295 <p> 296 コネクションを確立する。 297 </p> 298 <pre><code>\ 299 size[4] Tauth tag[2] afid[4] uname[s] aname[s] 300 size[4] Rauth tag[2] aqid[13] 301 302 size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s] 303 size[4] Rattach tag[2] qid[13] 304 </code></pre> 305 <p> 306 <code>attach</code>メッセージはサーバーのルートディレクトリを要求し、\ 307 <code>fid</code>を割り当てる。\ 308 認証が必要なサーバーであれば、\ 309 先に<code>auth</code>メッセージで認証を済ませておき、\ 310 <code>attach</code>メッセージの<code>afid</code>に、\ 311 先程使用した<code>afid</code>をセットする。\ 312 サーバーはこの<code>afid</code>に紐付いている認証サーバーに\ 313 クライアントが認証済みであるかを確認し、\ 314 認証済みであればルートディレクトリの<code>qid</code>を返す。\ 315 サーバーが複数のファイルツリーを公開している場合、\ 316 <code>aname</code>で指定する。\ 317 </p> 318 <p> 319 <code>auth</code>メッセージは認証サーバーとやりとりするための\ 320 <code>fid</code>である<code>afid</code>を要求する。\ 321 この<code>afid</code>を読み書きすることで\ 322 認証サーバーとやりとりをし、自身の身分を証明する。\ 323 認証が済んだらこの<code>afid</code>をセットした\ 324 <code>attach</code>メッセージを送る。\ 325 </p> 326 327 <h3>error</h3> 328 <p> 329 エラーを返す。 330 </p> 331 <pre><code>\ 332 size[4] Rerror tag[2] ename[s] 333 </code></pre> 334 <p> 335 クライアントからの要求に対して、なにかエラーがおきたときに\ 336 そのエラーを返すために使われる。\ 337 <code>Terror</code>はない。\ 338 </p> 339 340 <h3>flush</h3> 341 <p> 342 リクエストをキャンセルする。 343 </p> 344 <pre><code>\ 345 size[4] Tflush tag[2] oldtag[2] 346 size[4] Rflush tag[2] 347 </code></pre> 348 <p> 349 実行中の要求をキャンセルする。\ 350 キャンセルしたいリクエストの<code>tag</code>を<code>oldtag</code>にセットする。 351 </p> 352 353 <h3>walk</h3> 354 <p> 355 ファイルツリーを移動する。 356 </p> 357 <pre><code>\ 358 size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s]) 359 size[4] Rwalk tag[2] nwqid[2] nwqid*(qid[13]) 360 </code></pre> 361 <p> 362 <code>fid</code>を起点に移動する。\ 363 移動先のファイルを<code>newfid</code>にセットする。\ 364 移動するディレクトリの数を<code>nwname</code>に、\ 365 移動するディレクトリの名前を移動する順に<code>wname</code>に\ 366 それぞれセットする。\ 367 例えばカレントディレクトリから<code>./dir1/dir2/</code>に\ 368 移動する場合、<code>nwname</code>は<code>2</code>、<code>wname</code>は\ 369 <code>{"dir1", "dir2"}</code>となる。\ 370 <code>wname</code>の最後の要素はディレクトリでなくファイルでもいい。\ 371 </p> 372 <p> 373 <code>wname</code>の最初の要素への移動が失敗した場合は\ 374 <code>Rerror</code>が返される。\ 375 それ以外の場合は<code>Rwalk</code>が返され、\ 376 <code>qid</code>は移動が成功した順番にそのファイルの<code>qid</code>\ 377 がセットされる。\ 378 <code>nwname</code>と<code>nwqid</code>が一致した場合は\ 379 最後まで移動できたことになる。\ 380 </p> 381 <p> 382 <code>walk</code>は<code>fid</code>を増殖させる唯一の方法である。\ 383 </p> 384 385 <h3>open、create</h3> 386 <p> 387 <code>fid</code>を開いて(作成して)読み書きできる状態にする。 388 </p> 389 <pre><code>\ 390 size[4] Topen tag[2] fid[4] mode[1] 391 size[4] Ropen tag[2] qid[13] iounit[4] 392 393 size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1] 394 size[4] Rcreate tag[2] qid[13] iounit[4] 395 </code></pre> 396 <p> 397 <code>open</code>は<code>fid</code>と紐付いたファイルを開く。\ 398 <code>mode</code>は読み込み専用の<code>OREAD</code>、\ 399 書き込み専用の<code>OWRITE</code>、\ 400 読み書きの<code>ORDWR</code>等である。\ 401 ファイルを開く手順は以下の通り: 402 <ol> 403 <li><code>attach</code>でルートディレクトリの<code>fid</code>を取得</li> 404 <li>ルートディレクトリの<code>fid</code>から\ 405 <code>walk</code>で開きたいファイルの<code>fid</code>を取得</li> 406 <li>その<code>fid</code>を<code>open</code>で開く</li> 407 </ol> 408 </p> 409 <p> 410 <code>create</code>は<code>fid</code>と紐付いたディレクトリに\ 411 新しいファイルを作成する。\ 412 <code>creat</code>ではなく<code>create</code>である。\ 413 作成が成功したら<code>fid</code>は新しく作成されたファイルに紐付く。\ 414 </p> 415 416 <h3>read、write</h3> 417 <p> 418 ファイルを読み書きする。 419 </p> 420 <pre><code>\ 421 size[4] Tread tag[2] fid[4] offset[8] count[4] 422 size[4] Rread tag[2] count[4] data[count] 423 424 size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count] 425 size[4] Rwrite tag[2] count[4] 426 </code></pre> 427 <p> 428 <code>read</code>は<code>fid</code>の<code>offset</code>バイト目\ 429 から<code>count</code>バイト読む。\ 430 読んだデータと、読めたデータサイズが\ 431 <code>data</code>、<code>count</code>ととして返される。\ 432 <code>write</code>は<code>fid</code>の<code>offset</code>バイト目\ 433 に<code>count</code>バイトのデータ<code>data</code>を書く。\ 434 書きこめたサイズが<code>count</code>として返される。\ 435 ファイルを読み書きするためには\ 436 あらかじめ<code>fid</code>を<code>open</code>する必要がある。\ 437 </p> 438 439 <h3>clunk</h3> 440 <p> 441 <code>fid</code>を忘れる。 442 </p> 443 <pre><code>\ 444 size[4] Tclunk tag[2] fid[4] 445 size[4] Rclunk tag[2] 446 </code></pre> 447 <p> 448 いらなくなった<code>fid</code>を忘れる。\ 449 一度忘れた<code>fid</code>は<code>walk</code>で別のファイルを取得する際に\ 450 再利用できる。\ 451 </p> 452 453 <h3>remove</h3> 454 <p> 455 ファイルを削除する。 456 </p> 457 <pre><code>\ 458 size[4] Tremove tag[2] fid[4] 459 size[4] Rremove tag[2] 460 </code></pre> 461 <p> 462 <code>fid</code>と紐付いたサーバー上のファイルを削除する。\ 463 <code>fid</code>は<code>clunk</code>したのと同様に忘れられる。\ 464 </p> 465 466 <h3>stat、wstat</h3> 467 <p> 468 ファイルの属性を読み書きする。 469 </p> 470 <pre><code>\ 471 size[4] Tstat tag[2] fid[4] 472 size[4] Rstat tag[2] stat[n] 473 474 size[4] Twstat tag[2] fid[4] stat[n] 475 size[4] Rwstat tag[2] 476 </code></pre> 477 <p> 478 <code>stat</code>はファイルの属性を読む。\ 479 読めた情報は<code>stat</code>として返される。\ 480 <code>wstat</code>はファイルの属性を<code>stat</code>に変更する。\ 481 <code>stat</code>はファイルの名前や<code>qid</code>、サイズ、\ 482 作成日、更新日等の情報がバイト列になったものである。\ 483 </p> 484