9p.html (18899B)
1 +++ 2 date = '2024-12-19T00:00:00+09:00' 3 draft = false 4 title = '9P' 5 +++ 6 <time>2024-12-19</time> 7 <h2>はじめに</h2> 8 <p> 9 9Pはコンピュータ上の色々なものをファイルとして扱うためのプロトコルである。このプロトコルを使えば、ディスクに記録されたデータだけでなく、マウスやキーボードといった入力機器や、ネットワーク、プロセスの情報等もファイルとして扱える。Unixの後継OSとして米AT&Tのベル研究所で開発されたPlan9というOSのために設計された。 10 </p> 11 12 <p> 13 Plan9というOSはネットワークを介して複数のコンピュータを繋いで使うように設計されているので、この9Pもローカルだけでなくネットワーク越しに使うことを前提にしている。</p> 14 15 <h2>プロトコルの概要</h2> 16 <p> 17 9Pの通信ではクライアントがサーバーに対してリクエストを送り、サーバーがそれに対してリプライを返すことを繰り返す。クライアントからのリクエストをTメッセージ、サーバーからのリプライをRメッセージと言う。Tメッセージにはファイルにアクセスするための色々なものが用意されている。例えばファイルを開くためのTopenや、開いたファイルに書き込むためのTwrite等である。それぞれのTメッセージには対応するRメッセージがある。Topenに対してRopen、Twriteに対してRwrite等である。</p> 18 <p> 19 通信は必要な情報をプロトコルの定める方法でバイト列に変換して行う。2バイト以上のデータはリトルエンディアンになるように配置する。テキストデータはUTF-8にエンコードする。テキストデータは長さの情報も一緒に送るので最後のヌル文字は含めない。</p> 20 <p> 21 各メッセージは4バイトの整数から始まる。これは、この4バイトを含むメッセージの長さをバイト単位で示したものである。次にメッセージの種類を記述する1バイトのデータが来る。次はメッセージのタグである。クライアントはサーバーからの返事を待たずに別のメッセージを送れるので、サーバーからのRメッセージがどのTメッセージに対する返事なのかを識別する必要がある。そのために使うのがタグである。クライアントがTメッセージを送る際にタグを付け、サーバーはそれに対するRメッセージに同じタグを付ける。その後ろには、メッセージの種類によって固有の情報が付けられる。</p> 22 <p> 23 以下は実際の9P通信のログである。実際はバイナリの通信だが、ログとして見易いように変換してある。また、先頭にくるメッセージサイズは省略してある。この例では、サーバーのルートディレクトリにある<code>hello</code>というファイルの内容を読みこんでいる:</p> 24 <pre><code><-- Tversion Tag 65535 msize 8192 version '9P2000' 25 --> Rversion Tag 65535 msize 8192 version '9P2000' 26 <-- Tattach Tag 0 fid 0 afid -1 uname kenji aname 27 --> Rattach Tag 0 qid (0000000000000000 0 d) 28 <-- Twalk Tag 0 fid 0 newfid 1 nwname 1 0:hello 29 --> Rwalk Tag 0 nwqid 1 0:(0000000000000001 0 ) 30 <-- Tstat Tag 0 fid 1 31 --> Rstat Tag 0 stat 'hello' 'kenji' 'kenji' '' q (0000000000000001 0 ) m 0644 at 1730004808 mt 1730004808 l 7 t 0 d 0 32 <-- Twalk Tag 0 fid 1 newfid 2 nwname 0 33 --> Rwalk Tag 0 nwqid 0 34 <-- Topen Tag 0 fid 2 mode 0x0 35 --> Ropen Tag 0 qid (0000000000000001 0 ) iounit 8169 36 <-- Tread Tag 0 fid 2 offset 0 count 4096 37 --> Rread Tag 0 count 7 ' 776f726c 64210a' 38 <-- Tread Tag 0 fid 2 offset 7 count 4096 39 --> Rread Tag 0 count 0 '' 40 <-- Tclunk Tag 0 fid 2 41 --> Rclunk Tag 0 42 </code></pre> 43 <p> 44 まず最初にクライアントがサーバーに対してTversionを送り、プロトコルのバージョンと、メッセージの最大サイズを交渉する。Tversionのタグは<code>65535</code>と決められている。<code>msize</code>の8192はメッセージの最大サイズ(バイト)で、<code>version</code>の9P2000がプロトコルのバージョンである。サーバーからRversionで同じ<code>msize</code>と<code>version</code>が返ってきたのでこのサイズとバージョンで以降の通信を行う。</p> 45 <p> 46 次のTattachで、クライアントがサーバーにルートディレクトリの<code>fid</code>を要求する。<code>afid</code>以降は認証用の情報である(後述)。<code>fid</code>はコネクションにおいてファイルと紐付けられる整数で、Unixにおけるファイルディスクリプタのようなものである。ファイルの読み書きはこの<code>fid</code>を使って行われる。サーバーからのRattachにはルートディレクトリの<code>qid</code>が含まれる。これはサーバー上でファイルを一意に識別するもので、Unixにおけるinodeに相当するものである。以上でサーバーに繋いでセッションを確立できた。</p> 47 <p> 48 次にTwalkで目的のファイル(<code>hello</code>)までファイルツリーを辿る。ここでは起点として先程得られたルートディレクトリ(<code>fid = 1</code>)を起点として、このディレクトリ内の<code>hello</code>ファイルを要求して<code>fid = 2</code>を割り当てようとしている。サーバーはルートディレクトリ内に要求されたファイルが見付かったので、そのファイルの<code>qid</code>を返す。</p> 49 <p> 50 目的のファイルに<code>fid</code>が割り当てられたので、Tstatでこのファイルの属性を要求している。おそらくファイルのパーミッションを調べるためである。</p> 51 <p> 52 次にTopenを使ってこのファイルを開き、Treadを使って内容を読む。二回目のTreadに対して0バイトのデータが返ってきたので、ファイルはこれで終りである。</p> 53 <p> 54 最後に、必要なくなった<code>fid</code>はTclunkで捨てる。</p> 55 56 <h2>List of all Message Types</h2> 57 58 <table> 59 <tr> 60 <th>Tメッセージ</th> 61 <th>Rメッセージ</th> 62 <th>説明</th> 63 </tr> 64 <tr> 65 <td>Tversion</td> 66 <td>Rversion</td> 67 <td>バージョンの交渉</td> 68 </tr> 69 <tr> 70 <td>Tauth</td> 71 <td>Rauth</td> 72 <td>認証ファイルの取得</td> 73 </tr> 74 <tr> 75 <td></td> 76 <td>Rerror</td> 77 <td>エラー(Rメッセージのみ)</td> 78 </tr> 79 <tr> 80 <td>Tflush</td> 81 <td>Rflush</td> 82 <td>処理中のリクエストをキャンセル</td> 83 </tr> 84 <tr> 85 <td>Tattach</td> 86 <td>Rattach</td> 87 <td>ルートディレクトリの取得</td> 88 </tr> 89 <tr> 90 <td>Twalk</td> 91 <td>Rwalk</td> 92 <td>ファイルツリーを辿る</td> 93 </tr> 94 <tr> 95 <td>Topen</td> 96 <td>Ropen</td> 97 <td>ファイルを開く</td> 98 </tr> 99 <tr> 100 <td>Tcreate</td> 101 <td>Rcreate</td> 102 <td>ファイルの作成</td> 103 </tr> 104 <tr> 105 <td>Tread</td> 106 <td>Rread</td> 107 <td>ファイルを読む</td> 108 </tr> 109 <tr> 110 <td>Twrite</td> 111 <td>Rwrite</td> 112 <td>ファイルへ書き込む</td> 113 </tr> 114 <tr> 115 <td>Tclunk</td> 116 <td>Rclunk</td> 117 <td>fidの削除</td> 118 </tr> 119 <tr> 120 <td>Tremove</td> 121 <td>Rremove</td> 122 <td>ファイルを削除</td> 123 </tr> 124 <tr> 125 <td>Tstat</td> 126 <td>Rstat</td> 127 <td>ファイルの属性を取得</td> 128 </tr> 129 <tr> 130 <td>Twstat</td> 131 <td>Rwstat</td> 132 <td>ファイルの属性を変更</td> 133 </tr> 134 </table> 135 136 <h2><code>fid</code>と<code>qid</code></h2> 137 <p> 138 <code>fid</code>はコネクションにおいてファイルを指定するために使われる整数で、Unixのファイルディスクリプタのようなものである。使用する整数はクライアントが指定できる。サーバーに接続する際にTattachにより、サーバーのルートディレクトリの<code>fid</code>が設定され、Twalkにより追加され、Tclunkにより削除される。</p> 139 <p> 140 <code>qid</code>はサーバーがファイルを一意に識別するためのもので、Unixのinodeに相当するものである。ただしinodeはファイルが削除されると再利用される可能性があるのに対し、<code>qid</code>は再利用できない。<code>qid</code>はこのファイルがディレクトリかどうか等を示す<code>type</code>、ファイルが変更されたときにインクリメントされる<code>vers</code>、そしてファイルを一意に表す<code>path</code>の3つの情報で構成される。</p> 141 <h2>認証</h2> 142 <p> 143 9Pにはクライアントの認証が組込まれていない。もともとはあったようだが、認証のアルゴリズムに欠陥が見付かったりすればいちいちコードを修正してコンパイルしなおさなければいけないうえ、9Pプロトコル自体も変更しないといけないので、外部に切りだすことになった。認証に必要なやりとりは認証サーバーとクライアントが行い、9Pサーバーは認証が成功したかどうかの情報を認証サーバーに確認することでクライアントの確認を行う。 144 </p> 145 <p> 146 9PサーバーはTattachメッセージを受けとると、認証サーバーとのコネクションを確立する。その後Rattachメッセージで<code>afid</code>という特殊な<code>fid</code>をクライアントに返す。クライアントからこの<code>afid</code>に対して読み書きする命令が届くと、9Pサーバーはこの読み書きを認証サーバーとのコネクションに横流しする。つまりクライアントはこの<code>afid</code>を通じて認証サーバーと直接やりとりができる。クライアントはユーザー名やパスワード等(パスワード認証の場合)の情報を<code>afid</code>に書き込んで認証する。このように認証をプロトコル自体から切り離すことで、認証に関するバグが見付かったとしても、認証サーバーを修正するだけでいい。また、より強力な認証システムを組み込むのも楽である。 147 </p> 148 149 <h2>コネクションの共有</h2> 150 <p> 151 クライアントはサーバーと<code>version</code>メッセージを交すことでコネクションを確立する。コネクションの確立からの一連のやりとりをセッションという。ひとつのコネクションは複数のクライアントが共有できる。</p> 152 <p> 153 クライアントはTauthまたはTattachメッセージによりサーバーに<code>fid</code>を要求できる。このうちTauthにより得られた<code>fid</code>は認証以外には使えない。Tattachで得られた<code>fid</code>は、Twalkメッセージによりファイルツリーを辿るための起点として利用でき、このとき辿った先のファイルを示す<code>fid</code>が生成される。これ以外の方法で<code>fid</code>が増えることはない。つまり、サーバーはひとつのコネクションのなかで、ルートディレクトリから派生した<code>fid</code>を追跡することで、クライアントを区別できる。</p> 154 155 <h2>メッセージ詳細</h2> 156 <p> 157 それぞれ最初にメッセージの形式を書く。<code><i>field</i>[<i>n</i>]</code>は<code><i>field</i></code>という名前の<i>n</i>バイトのデータを表す(<i>n</i>は整数)。また、括弧の中が整数ではなく<code>s</code>になっている場合、そのフィールドは文字列のデータで、その前に文字列の長さを示す2バイトの整数が先行する。また、メッセージ名のフィールド(<code>Tversion</code>等)にはメッセージの種類を表す1バイトのenumが来る。 158 </p> 159 <h3>version</h3> 160 <p> 161 プロトコルのバージョンを交渉すし、コネクションを確立する。 162 </p> 163 <pre><code>size[4] Tversion tag[2] msize[4] version[s] 164 size[4] Rversion tag[2] msize[4] version[s] 165 </code></pre> 166 <p> 167 クライアントは最初にサーバーにこのメッセージを送ってバージョンとメッセージの最大サイズ(<code>msize</code>)を交渉する。サーバーはクライアントから送られたバージョンとメッセージサイズに対応していれば、同じバージョンとサイズを送り返し、交渉成立である。</p> 168 <p> 169 サーバーとクライアントはこのメッセージを以ってコネクションを確立する。</p> 170 171 <h3>attach、auth</h3> 172 <p> 173 コネクションを確立する。 174 </p> 175 <pre><code>size[4] Tauth tag[2] afid[4] uname[s] aname[s] 176 size[4] Rauth tag[2] aqid[13] 177 178 size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s] 179 size[4] Rattach tag[2] qid[13] 180 </code></pre> 181 <p> 182 <code>attach</code>メッセージはサーバーのルートディレクトリを要求し、<code>fid</code>を割り当てる。認証が必要なサーバーであれば、先に<code>auth</code>メッセージで認証を済ませておき、<code>attach</code>メッセージの<code>afid</code>に、先程使用した<code>afid</code>をセットする。サーバーはこの<code>afid</code>に紐付いている認証サーバーにクライアントが認証済みであるかを確認し、認証済みであればルートディレクトリの<code>qid</code>を返す。サーバーが複数のファイルツリーを公開している場合、<code>aname</code>で指定する。</p> 183 <p> 184 <code>auth</code>メッセージは認証サーバーとやりとりするための<code>fid</code>である<code>afid</code>を要求する。この<code>afid</code>を読み書きすることで認証サーバーとやりとりをし、自身の身分を証明する。認証が済んだらこの<code>afid</code>をセットした<code>attach</code>メッセージを送る。</p> 185 186 <h3>error</h3> 187 <p> 188 エラーを返す。 189 </p> 190 <pre><code>size[4] Rerror tag[2] ename[s] 191 </code></pre> 192 <p> 193 クライアントからの要求に対して、なにかエラーがおきたときにそのエラーを返すために使われる。<code>Terror</code>はない。</p> 194 195 <h3>flush</h3> 196 <p> 197 リクエストをキャンセルする。 198 </p> 199 <pre><code>size[4] Tflush tag[2] oldtag[2] 200 size[4] Rflush tag[2] 201 </code></pre> 202 <p> 203 実行中の要求をキャンセルする。キャンセルしたいリクエストの<code>tag</code>を<code>oldtag</code>にセットする。 204 </p> 205 206 <h3>walk</h3> 207 <p> 208 ファイルツリーを移動する。 209 </p> 210 <pre><code>size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s]) 211 size[4] Rwalk tag[2] nwqid[2] nwqid*(qid[13]) 212 </code></pre> 213 <p> 214 <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> 215 <p> 216 <code>wname</code>の最初の要素への移動が失敗した場合は<code>Rerror</code>が返される。それ以外の場合は<code>Rwalk</code>が返され、<code>qid</code>は移動が成功した順番にそのファイルの<code>qid</code>がセットされる。<code>nwname</code>と<code>nwqid</code>が一致した場合は最後まで移動できたことになる。</p> 217 <p> 218 <code>walk</code>は<code>fid</code>を増殖させる唯一の方法である。</p> 219 220 <h3>open、create</h3> 221 <p> 222 <code>fid</code>を開いて(作成して)読み書きできる状態にする。 223 </p> 224 <pre><code>size[4] Topen tag[2] fid[4] mode[1] 225 size[4] Ropen tag[2] qid[13] iounit[4] 226 227 size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1] 228 size[4] Rcreate tag[2] qid[13] iounit[4] 229 </code></pre> 230 <p> 231 <code>open</code>は<code>fid</code>と紐付いたファイルを開く。<code>mode</code>は読み込み専用の<code>OREAD</code>、書き込み専用の<code>OWRITE</code>、読み書きの<code>ORDWR</code>等である。ファイルを開く手順は以下の通り: 232 <ol> 233 <li><code>attach</code>でルートディレクトリの<code>fid</code>を取得</li> 234 <li>ルートディレクトリの<code>fid</code>から<code>walk</code>で開きたいファイルの<code>fid</code>を取得</li> 235 <li>その<code>fid</code>を<code>open</code>で開く</li> 236 </ol> 237 </p> 238 <p> 239 <code>create</code>は<code>fid</code>と紐付いたディレクトリに新しいファイルを作成する。<code>creat</code>ではなく<code>create</code>である。作成が成功したら<code>fid</code>は新しく作成されたファイルに紐付く。</p> 240 241 <h3>read、write</h3> 242 <p> 243 ファイルを読み書きする。 244 </p> 245 <pre><code>size[4] Tread tag[2] fid[4] offset[8] count[4] 246 size[4] Rread tag[2] count[4] data[count] 247 248 size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count] 249 size[4] Rwrite tag[2] count[4] 250 </code></pre> 251 <p> 252 <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> 253 254 <h3>clunk</h3> 255 <p> 256 <code>fid</code>を忘れる。 257 </p> 258 <pre><code>size[4] Tclunk tag[2] fid[4] 259 size[4] Rclunk tag[2] 260 </code></pre> 261 <p> 262 いらなくなった<code>fid</code>を忘れる。一度忘れた<code>fid</code>は<code>walk</code>で別のファイルを取得する際に再利用できる。</p> 263 264 <h3>remove</h3> 265 <p> 266 ファイルを削除する。 267 </p> 268 <pre><code>size[4] Tremove tag[2] fid[4] 269 size[4] Rremove tag[2] 270 </code></pre> 271 <p> 272 <code>fid</code>と紐付いたサーバー上のファイルを削除する。<code>fid</code>は<code>clunk</code>したのと同様に忘れられる。</p> 273 274 <h3>stat、wstat</h3> 275 <p> 276 ファイルの属性を読み書きする。 277 </p> 278 <pre><code>size[4] Tstat tag[2] fid[4] 279 size[4] Rstat tag[2] stat[n] 280 281 size[4] Twstat tag[2] fid[4] stat[n] 282 size[4] Rwstat tag[2] 283 </code></pre> 284 <p> 285 <code>stat</code>はファイルの属性を読む。読めた情報は<code>stat</code>として返される。<code>wstat</code>はファイルの属性を<code>stat</code>に変更する。<code>stat</code>はファイルの名前や<code>qid</code>、サイズ、作成日、更新日等の情報がバイト列になったものである。</p> 286 287