www.mtkn.jp

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

commit 77357f43025a96259ed83e487c04493a46c920c0
parent 87310b0f73d58b567d19e4d62159a78176649871
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Sun, 17 Nov 2024 18:53:14 +0900

update

Diffstat:
Mman/draft/9p.html | 180++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpub/draft/9p.html | 130++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpub/rss.xml | 4++--
3 files changed, 302 insertions(+), 12 deletions(-)

diff --git a/man/draft/9p.html b/man/draft/9p.html @@ -539,8 +539,6 @@ type Msg interface { 名前はいまいち。\ </p> <pre><code>\ -// A bufMsg is Msg with just an array of bytes. -// TODO: rename. type bufMsg []byte func (msg bufMsg) Size() uint32 { return gbit32(msg[0:4]) } @@ -666,13 +664,183 @@ func SendMsg(msg Msg, w io.Writer) error { 返信を書き込むためのレスポンダーgoroutine、\ そして各メッセージを処理するためのgoroutineを立ち上げて、\ チャネルでそれぞれを繋ぐ。\ -リスナーがメッセージを読み込むと、メッセージのタイプに応じて\ -各goroutineにメッセージの構造体を渡し、\ +リスナーがメッセージを読み込むと、<code>request</code>構造体に格納し、\ +メッセージのタイプに応じて各goroutineに渡す。\ 渡されたgoroutineはメッセージに応じた処理をし、\ 返信のメッセージをリスポンダーgoroutineに渡す。\ リスポンダーgoroutineはメッセージをバイト列に変換して返信する。 </p> <p> -この設計がいいかどうかよく知らんけど、Go言語を触るなら\ -goroutineとチャネルによるパイプラインを作ってみたいやん。\ +この設計がいいかどうかよく知らんけど、\ +Go言語を触るからgoroutineとチャネルによるパイプラインを作ってみたかった。\ </p> + +<h3>各メッセージの処理</h3> +<p> +メッセージは<code>s<i>メッセージタイプ</i></code>goroutineで処理する。\ +関数は<code>for</code>ループのなかでチャンネル<code>rc</code>から\ +<code>request</code>が届くのを待つ。\ +届いたリクエストには<code>ifcall</code>フィールドにクライアントからの\ +<code>Msg</code>が含まれるので、\ +この関数が担当するstructにキャストする。\ +このキャストが失敗するのは明かなサーバーのバグなのでタイプアサーションはしない。\ +</p> +<pre><code>\ +func s<i>メッセージタイプ</i>(ctx context.Context, c *conn, rc &lt;-chan *request) { + for { + select { + case &lt;-ctx.Done(); + return + case r, ok := rc: + if !ok { + return + } + ifcall := r.ifcall.(*T<i>メッセージタイプ</i>) +</code></pre> +<p> +各種処理を完了したら、届いた<code>request</code>の<code>ofcall</code>に\ +返信の<code>Msg</code>を格納して返信を担当するgoroutineに送る。\ +<pre><code> select { + case c.respChan &lt;- r: + case &lt;-ctx.Done(): + return + } +</code></pre> +</p> +<h4>Version</h4> +<p> +クライアントから届いたバージョンの提案を見て、頭に"9P2000"があれば\ +返信のバージョンを"9P2000"にする。\ +現在定義されているバージョンは"9P2000"の他、\ +Unix用に拡張した"9P2000.u"、Linux用に拡張した"9P2000.l"がある。\ +基本的な昨日を実装することが目標なので、"9P2000"のみを受け付ける。\ +</p> +<p> +Versionメッセージではメッセージの最大サイズも決定する。\ +サーバーは予め8Kバイトを最大サイズにしている。\ +クライアントがこれ以上のサイズを提案した場合、8Kにしてもらい、\ +これ以下のサイズを提案した場合、そのサイズでメッセージをやりとりする。\ +8Kバイトという既定値はplan9の実装を参考にした。\ +このサイズである必然性はよく知らない。\ +</p> +<pre><code> version := ifcall.Version + if strings.HasPrefix(version, &quot;9P2000&quot;) { + version = &quot;9P2000&quot; + } else { + version = &quot;unknown&quot; + } + msize := ifcall.Msize + if msize &gt; c.mSize() { + msize = c.mSize() + } + r.ofcall = &amp;RVersion{ + Msize: msize, + Version: version, + } + c.setMSize(r.ofcall.(*RVersion).Msize) +</code></pre> + +<h4>Auth</h4> +<p> +サーバーの認証は<code>Server</code>の<code>Auth</code>フィールドに\ +認証のためのやりとりを待ち受ける関数を登録することで有効化できる。\ +このフィールドが<code>nil</code>の場合、認証が必要ない旨のエラーを返す。\ +</p> +<pre><code> if c.s.Auth == nil { + r.err = fmt.Errorf(&quot;authentication not required&quot;) + goto resp + } +</code></pre> +<p> +認証が必要な場合、クライアントから提案された<code>afid</code>をコネクションに\ +登録して、<code>Server.Auth</code>を呼び出す。 +</p> + +<h4>attach</h4> +<p> +サーバーはまず認証が必要な場合認証が済んでいるか確認する。\ +</p> +<pre><code> switch { + case c.s.Auth == nil &amp;&amp; ifcall.Afid == NOFID: + case c.s.Auth == nil &amp;&amp; ifcall.Afid != NOFID: + r.err = ErrBotch + goto resp + case c.s.Auth != nil &amp;&amp; ifcall.Afid == NOFID: + r.err = fmt.Errorf(&quot;authentication required&quot;) + goto resp + case c.s.Auth != nil &amp;&amp; ifcall.Afid != NOFID: + afid, ok := c.fPool.lookup(ifcall.Afid) + if !ok { + r.err = ErrUnknownFid + goto resp + } + af, ok := afid.file.(*AuthFile) + if !ok { + r.err = fmt.Errorf(&quot;not auth file&quot;) + goto resp + } + if af.Uname != ifcall.Uname || af.Aname != ifcall.Aname || !af.AuthOK { + r.err = fmt.Errorf(&quot;not authenticated&quot;) + goto resp + } + } +</code></pre> +<p> +次にクライアントが要求してきたファイルツリーがサーバーにあるかどうか確認し、\ +クライアントが設定した<code>fid</code>にルートディレクトリを紐付ける。\ +</p> +<pre><code> r.fid, err = c.fPool.add(ifcall.Fid) + if err != nil { + r.err = ErrDupFid + goto resp + } + r.fid.path = &quot;.&quot; + r.fid.uid = ifcall.Uname + fsys, ok = c.s.fsmap[ifcall.Aname] + if !ok { + r.err = fmt.Errorf(&quot;no such file system&quot;) + goto resp + } + r.fid.fs = fsys + fi, err = fs.Stat(ExportFS{c.s.fsmap[ifcall.Aname]}, &quot;.&quot;) + if err != nil { + r.err = fmt.Errorf(&quot;stat root: %v&quot;, err) + goto resp + } + r.fid.qidpath = fi.Sys().(*Stat).Qid.Path + r.ofcall = &amp;RAttach{ + Qid: fi.Sys().(*Stat).Qid, + } +</code></pre> + +<h4>flush</h4> +<p> +指定されたリクエストの<code>flush</code>メソッドを呼び出す。\ +<code>flush</code>メソッドはリクエストの<code>done</code>チャンネルを閉じ、\ +リクエストのタグを削除する。\ +処理に時間のかかるものは、リクエストの<code>done</code>チャネルが閉じられたら、\ +その処理を中止する。\ +</p> +<pre><code>\ +// flush cancels the request by calling r.cancel. +// It also delete the request from its pool. +func (r *request) flush() { + // TODO: need mutex? + close(r.done) + r.pool.delete(r.tag) +} +</code></pre> + +<h4>walk</h4> +<p> + +</p> + +<h4>open</h4> +<h4>create</h4> +<h4>read</h4> +<h4>write</h4> +<h4>clunk</h4> +<h4>remove</h4> +<h4>stat</h4> +<h4>wstat</h4> diff --git a/pub/draft/9p.html b/pub/draft/9p.html @@ -342,9 +342,7 @@ type Msg interface { <code>marshal()</code>はサーバーから返信を送る際にメッセージの構造体をバイト列にエンコードするための関数である。<code>String() string</code>はログ出力用。</p> <p> メッセージはバイト列として受け取った後、メッセージのタイプによって処理を分ける。その際、扱いやすいようにバイト列を各メッセージの構造体に変換する。変換前のバイト列から、各メッセージに共通のフィールドを取得できると便利なので、バイト列のまま<code>Msg</code>インターフェースを実装する<code>bufMsg</code>を定義した。名前はいまいち。</p> -<pre><code>// A bufMsg is Msg with just an array of bytes. -// TODO: rename. -type bufMsg []byte +<pre><code>type bufMsg []byte func (msg bufMsg) Size() uint32 { return gbit32(msg[0:4]) } func (msg bufMsg) Type() MsgType { return MsgType(msg[4]) } @@ -446,8 +444,132 @@ func SendMsg(msg Msg, w io.Writer) error { <h3>メインループ</h3> <p> -ライブラリはメッセージを読み込むためのリスナーgoroutinと返信を書き込むためのレスポンダーgoroutine、そして各メッセージを処理するためのgoroutineを立ち上げる。リスナーがメッセージを読み込むと、メッセージのタイプに応じて各goroutineにメッセージの構造体を渡し、渡されたgoroutineはメッセージに応じた処理をし、返信のメッセージをリスポンダーgoroutineに渡す。リスポンダーgoroutineはメッセージをバイト列に変換して返信する。 +ライブラリはメッセージを読み込むためのリスナーgoroutinと返信を書き込むためのレスポンダーgoroutine、そして各メッセージを処理するためのgoroutineを立ち上げて、チャネルでそれぞれを繋ぐ。リスナーがメッセージを読み込むと、<code>request</code>構造体に格納し、メッセージのタイプに応じて各goroutineに渡す。渡されたgoroutineはメッセージに応じた処理をし、返信のメッセージをリスポンダーgoroutineに渡す。リスポンダーgoroutineはメッセージをバイト列に変換して返信する。 </p> +<p> +この設計がいいかどうかよく知らんけど、Go言語を触るからgoroutineとチャネルによるパイプラインを作ってみたかった。</p> + +<h3>各メッセージの処理</h3> +<p> +メッセージは<code>s<i>メッセージタイプ</i></code>goroutineで処理する。関数は<code>for</code>ループのなかでチャンネル<code>rc</code>から<code>request</code>が届くのを待つ。届いたリクエストには<code>ifcall</code>フィールドにクライアントからの<code>Msg</code>が含まれるので、この関数が担当するstructにキャストする。このキャストが失敗するのは明かなサーバーのバグなのでタイプアサーションはしない。</p> +<pre><code>func s<i>メッセージタイプ</i>(ctx context.Context, c *conn, rc &lt;-chan *request) { + for { + select { + case &lt;-ctx.Done(); + return + case r, ok := rc: + if !ok { + return + } + ifcall := r.ifcall.(*T<i>メッセージタイプ</i>) +</code></pre> +<p> +各種処理を完了したら、届いた<code>request</code>の<code>ofcall</code>に返信の<code>Msg</code>を格納して返信を担当するgoroutineに送る。<pre><code> select { + case c.respChan &lt;- r: + case &lt;-ctx.Done(): + return + } +</code></pre> +</p> +<h4>Version</h4> +<p> +クライアントから届いたバージョンの提案を見て、頭に"9P2000"があれば返信のバージョンを"9P2000"にする。現在定義されているバージョンは"9P2000"の他、Unix用に拡張した"9P2000.u"、Linux用に拡張した"9P2000.l"がある。基本的な昨日を実装することが目標なので、"9P2000"のみを受け付ける。</p> +<p> +Versionメッセージではメッセージの最大サイズも決定する。サーバーは予め8Kバイトを最大サイズにしている。クライアントがこれ以上のサイズを提案した場合、8Kにしてもらい、これ以下のサイズを提案した場合、そのサイズでメッセージをやりとりする。8Kバイトという既定値はplan9の実装を参考にした。このサイズである必然性はよく知らない。</p> +<pre><code> version := ifcall.Version + if strings.HasPrefix(version, &quot;9P2000&quot;) { + version = &quot;9P2000&quot; + } else { + version = &quot;unknown&quot; + } + msize := ifcall.Msize + if msize &gt; c.mSize() { + msize = c.mSize() + } + r.ofcall = &amp;RVersion{ + Msize: msize, + Version: version, + } + c.setMSize(r.ofcall.(*RVersion).Msize) +</code></pre> + +<h4>Auth</h4> +<p> +サーバーの認証は<code>Server</code>の<code>Auth</code>フィールドに認証のためのやりとりを待ち受ける関数を登録することで有効化できる。このフィールドが<code>nil</code>の場合、認証が必要ない旨のエラーを返す。</p> +<pre><code> if c.s.Auth == nil { + r.err = fmt.Errorf(&quot;authentication not required&quot;) + goto resp + } +</code></pre> +<p> +認証が必要な場合、クライアントから提案された<code>afid</code>をコネクションに登録して、<code>Server.Auth</code>を呼び出す。 +</p> + +<h4>attach</h4> +<p> +サーバーはまず認証が必要な場合認証が済んでいるか確認する。</p> +<pre><code> switch { + case c.s.Auth == nil &amp;&amp; ifcall.Afid == NOFID: + case c.s.Auth == nil &amp;&amp; ifcall.Afid != NOFID: + r.err = ErrBotch + goto resp + case c.s.Auth != nil &amp;&amp; ifcall.Afid == NOFID: + r.err = fmt.Errorf(&quot;authentication required&quot;) + goto resp + case c.s.Auth != nil &amp;&amp; ifcall.Afid != NOFID: + afid, ok := c.fPool.lookup(ifcall.Afid) + if !ok { + r.err = ErrUnknownFid + goto resp + } + af, ok := afid.file.(*AuthFile) + if !ok { + r.err = fmt.Errorf(&quot;not auth file&quot;) + goto resp + } + if af.Uname != ifcall.Uname || af.Aname != ifcall.Aname || !af.AuthOK { + r.err = fmt.Errorf(&quot;not authenticated&quot;) + goto resp + } + } +</code></pre> +<p> +次にクライアントが要求してきたファイルツリーがサーバーにあるかどうか確認し、クライアントが設定した<code>fid</code>にルートディレクトリを紐付ける。</p> +<pre><code> r.fid, err = c.fPool.add(ifcall.Fid) + if err != nil { + r.err = ErrDupFid + goto resp + } + r.fid.path = &quot;.&quot; + r.fid.uid = ifcall.Uname + fsys, ok = c.s.fsmap[ifcall.Aname] + if !ok { + r.err = fmt.Errorf(&quot;no such file system&quot;) + goto resp + } + r.fid.fs = fsys + fi, err = fs.Stat(ExportFS{c.s.fsmap[ifcall.Aname]}, &quot;.&quot;) + if err != nil { + r.err = fmt.Errorf(&quot;stat root: %v&quot;, err) + goto resp + } + r.fid.qidpath = fi.Sys().(*Stat).Qid.Path + r.ofcall = &amp;RAttach{ + Qid: fi.Sys().(*Stat).Qid, + } +</code></pre> + +<h4>flush</h4> +<p> +指定されたリクエストの<code>flush</code>メソッドを呼び出す。<code>flush</code>メソッドはリクエストの<code>done</code>チャンネルを閉じ、リクエストのタグを削除する。</p> +<pre><code>// flush cancels the request by calling r.cancel. +// It also delete the request from its pool. +func (r *request) flush() { + // TODO: need mutex? + close(r.done) + r.pool.delete(r.tag) +} +</code></pre> </article> </main> diff --git a/pub/rss.xml b/pub/rss.xml @@ -5,8 +5,8 @@ <description>ウェブページの更新履歴</description> <language>ja-jp</language> <link>https://www.mtkn.jp</link> -<lastBuildDate>Thu, 14 Nov 2024 20:48:32 +0900</lastBuildDate> -<pubDate>Thu, 14 Nov 2024 20:48:32 +0900</pubDate> +<lastBuildDate>Sun, 17 Nov 2024 12:15:43 +0900</lastBuildDate> +<pubDate>Sun, 17 Nov 2024 12:15:43 +0900</pubDate> <docs>https://www.rssboard.org/rss-specification</docs> <item> <title>麻婆豆腐</title>