[インデックス 15432] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnetパッケージにおいて、Plan 9オペレーティングシステム向けのネットワーク接続およびリスナーのファイル表現に関する機能を追加するものです。具体的には、Plan 9のファイルシステム上のネットワーク関連ファイルをGoのnet.Connやnet.Listenerインターフェースに変換するためのFileConn、FileListener、およびFileメソッドの実装が含まれています。
コミット
commit b461fe660d82b3f3f21cd4042e0f4d3f800aac6c
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Tue Feb 26 01:26:40 2013 +0100
net: Implement FileListener, FileConn, and File methods for Plan 9
Functions for representing network connections as files
and vice versa, on Plan 9.
Representing network connections as files is not so
straight-forward, because a network connection on Plan 9
is represented by a host of files rather than a single
file descriptor (as is the case on UNIX). We use the
type system to distinguish between listeners and
connections, returning the control file in the former
case and the data file in the latter case.
R=rsc, rminnich, ality, akumar, bradfitz
CC=golang-dev
https://golang.org/cl/7235068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b461fe660d82b3f3f21cd4042e0f4d3f800aac6c
元コミット内容
このコミットは、GoのnetパッケージがPlan 9環境でネットワーク接続をファイルとして表現し、またその逆の変換を可能にするための機能を追加します。Plan 9では、UNIX系システムのように単一のファイルディスクリプタでネットワーク接続を表現するのではなく、複数の関連ファイル(例: ctl, data, statusなど)の集合として表現します。このコミットは、リスナーと接続を区別するために型システムを利用し、それぞれの場合に応じて制御ファイル(ctl)またはデータファイル(data)を返すように実装しています。
変更の背景
Go言語はクロスプラットフォーム対応を目指しており、Plan 9もサポート対象の一つです。Plan 9のネットワークモデルはUNIX系システムとは大きく異なるため、Goの標準ネットワークインターフェース(net.Conn, net.Listener)をPlan 9のファイルベースのネットワーク表現に適合させる必要がありました。
これまでのGoのPlan 9向けnetパッケージの実装では、FileConnやFileListenerといった、既存の*os.Fileからネットワーク接続やリスナーを再構築する機能が未実装でした。これらの関数は、GoプログラムがPlan 9のファイルシステムを通じて確立されたネットワーク接続を、Goの慣用的なネットワークAPIで操作するために不可欠です。このコミットは、このギャップを埋め、Plan 9上でのGoのネットワークプログラミングの柔軟性と互換性を向上させることを目的としています。
前提知識の解説
Plan 9 from Bell Labs
Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。その最も特徴的な設計思想は「すべてがファイルである」というもので、プロセス、デバイス、ネットワーク接続など、システム内のあらゆるリソースがファイルシステムのエントリとして表現されます。これにより、標準的なファイル操作のインターフェースを通じて、多様なリソースにアクセスできる統一されたプログラミングモデルが提供されます。
Plan 9のネットワークモデル
Plan 9のネットワークモデルは、その「すべてがファイルである」という哲学を色濃く反映しています。
/netファイルシステム: ネットワーク関連のリソースは、特殊なファイルシステムである/net以下にマウントされます。- 接続の表現: UNIX系システムではソケットが単一のファイルディスクリプタで表現されるのに対し、Plan 9では個々のネットワーク接続やリスナーはディレクトリとして表現され、そのディレクトリ内に複数の特殊なファイルが含まれます。
ctl(control): 接続の制御を行うためのファイル。接続の確立、切断、オプション設定など。data(data): 実際のデータ送受信を行うためのファイル。読み書きすることでネットワークI/Oが行われます。status(status): 接続の状態情報を提供するファイル。local(local): ローカルアドレス情報。remote(remote): リモートアドレス情報。
cloneファイル: 新しい接続やリスナーを作成するための「クローン」ファイル。これをオープンすると、新しい接続ディレクトリが作成されます。
Go言語のnetパッケージ
Goのnetパッケージは、TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うためのプラットフォーム非依存のインターフェースを提供します。
net.Connインターフェース: ネットワーク接続を表すインターフェースで、Read、Write、Closeなどのメソッドを持ちます。net.Listenerインターフェース: ネットワークリスナーを表すインターフェースで、Acceptメソッドを通じて新しい接続を受け入れます。*os.Fileとネットワーク: UNIX系システムでは、ソケットはファイルディスクリプタとして扱われるため、net.Connやnet.Listenerから*os.Fileを取得したり、その逆を行ったりする機能(File()、FileConn()、FileListener())が提供されています。これは、GoのネットワークAPIと、低レベルのファイルディスクリプタ操作を必要とする他のライブラリやシステムコールとの連携を可能にします。
技術的詳細
このコミットの主要な技術的課題は、Plan 9の複数ファイルで構成されるネットワーク接続の概念を、Goの単一オブジェクト(net.Connやnet.Listener)にマッピングすることです。
netFDの拡張:netFDはGoのnetパッケージ内部でネットワークファイルディスクリプタを抽象化する構造体です。Plan 9では、このnetFDがctl(制御ファイル)とdata(データファイル)という2つの*os.Fileを内部に持つことで、Plan 9のネットワークモデルを表現します。dup()メソッドの再定義:netFD.dup(): 以前はPlan 9では未実装でしたが、このコミットで実装され、接続のdataファイルディスクリプタを複製して*os.Fileとして返します。TCPListener.dup():TCPListener専用のdupメソッドが追加され、リスナーのctlファイルディスクリプタを複製して返します。これにより、TCPListener.File()メソッドが正しく機能するようになります。
newFileFD関数の導入:- この関数は、与えられた
*os.FileがPlan 9のネットワークファイル(ctl、data、cloneなど)のいずれかであるかを解析し、対応するnetFD構造体を構築します。 syscall.Fd2pathを使用してファイルディスクリプタからパスを取得し、そのパスを解析して、ネットワークデバイスのタイプ(例:tcp)、インスタンス名、および関連する制御ファイルやデータファイルのパスを特定します。- 特に、
cloneファイルやctlファイルが渡された場合は、そこからネットワークインスタンス名(例:0,1など)を読み取り、対応するディレクトリパスを構築します。 readPlan9Addr関数(既存)を使用して、localファイルからローカルアドレスを読み込み、netFDに設定します。
- この関数は、与えられた
newFileConnとnewFileListener関数の導入:- これらの関数は
newFileFDを内部的に呼び出し、*os.FileからnetFDを構築します。 newFileConnは、構築されたnetFDのdataファイルをオープンし、ローカルアドレスのタイプ(TCPまたはUDP)に基づいて適切なnet.Conn実装(newTCPConnまたはnewUDPConn)を返します。newFileListenerは、構築されたnetFDのstatusファイルを読み込み、その内容が"Listen"であることを確認することで、ファイルがリスナーを表していることを検証します。その後、TCPListenerを返します。
- これらの関数は
FileConnとFileListenerのPlan 9実装:- Goの
netパッケージで定義されているFileConnとFileListener関数は、Plan 9環境ではこれまでsyscall.EPLAN9(Plan 9固有のエラー)を返していましたが、このコミットにより、それぞれnewFileConnとnewFileListenerを呼び出すように変更され、実際の機能が提供されます。
- Goの
これらの変更により、GoプログラムはPlan 9のファイルシステムを通じて得られたネットワーク関連の*os.Fileオブジェクトを、Goの抽象化されたネットワークインターフェース(net.Connやnet.Listener)としてシームレスに扱うことができるようになります。
コアとなるコードの変更箇所
src/pkg/net/fd_plan9.go
--- a/src/pkg/net/fd_plan9.go
+++ b/src/pkg/net/fd_plan9.go
@@ -83,8 +83,29 @@ func (fd *netFD) Close() error {
return err
}
+// This method is only called via Conn.
func (fd *netFD) dup() (*os.File, error) {
- return nil, syscall.EPLAN9
+ if !fd.ok() || fd.data == nil {
+ return nil, syscall.EINVAL
+ }
+ return fd.file(fd.data, fd.dir+"/data")
+}
+
+func (l *TCPListener) dup() (*os.File, error) {
+ if !l.fd.ok() {
+ return nil, syscall.EINVAL
+ }
+ return l.fd.file(l.fd.ctl, l.fd.dir+"/ctl")
+}
+
+func (fd *netFD) file(f *os.File, s string) (*os.File, error) {
+ syscall.ForkLock.RLock()
+ dfd, err := syscall.Dup(int(f.Fd()), -1)
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return nil, &OpError{"dup", s, fd.laddr, err}
+ }
+ return os.NewFile(uintptr(dfd), s), nil
}
func setDeadline(fd *netFD, t time.Time) error {
src/pkg/net/file_plan9.go
--- a/src/pkg/net/file_plan9.go
+++ b/src/pkg/net/file_plan9.go
@@ -5,16 +5,139 @@
package net
import (
+ "errors"
+ "io"
"os"
"syscall"
)
+func (fd *netFD) status(ln int) (string, error) {
+ if !fd.ok() {
+ return "", syscall.EINVAL
+ }
+
+ status, err := os.Open(fd.dir + "/status")
+ if err != nil {
+ return "", err
+ }
+ defer status.Close()
+ buf := make([]byte, ln)
+ n, err := io.ReadFull(status, buf[:])
+ if err != nil {
+ return "", err
+ }
+ return string(buf[:n]), nil
+}
+
+func newFileFD(f *os.File) (net *netFD, err error) {
+ var ctl *os.File
+ close := func(fd int) {
+ if err != nil {
+ syscall.Close(fd)
+ }
+ }
+
+ path, err := syscall.Fd2path(int(f.Fd()))
+ if err != nil {
+ return nil, os.NewSyscallError("fd2path", err)
+ }
+ comp := splitAtBytes(path, "/")
+ n := len(comp)
+ if n < 3 || comp[0] != "net" {
+ return nil, syscall.EPLAN9
+ }
+
+ name := comp[2]
+ switch file := comp[n-1]; file {
+ case "ctl", "clone":
+ syscall.ForkLock.RLock()
+ fd, err := syscall.Dup(int(f.Fd()), -1)
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return nil, os.NewSyscallError("dup", err)
+ }
+ defer close(fd)
+
+ dir := "/net/" + comp[n-2]
+ ctl = os.NewFile(uintptr(fd), dir+"/"+file)
+ ctl.Seek(0, 0)
+ var buf [16]byte
+ n, err := ctl.Read(buf[:])
+ if err != nil {
+ return nil, err
+ }
+ name = string(buf[:n])
+ default:
+ if len(comp) < 4 {
+ return nil, errors.New("could not find control file for connection")
+ }
+ dir := "/net/" + comp[1] + "/" + name
+ ctl, err = os.OpenFile(dir+"/ctl", os.O_RDWR, 0)
+ if err != nil {
+ return nil, err
+ }
+ defer close(int(ctl.Fd()))
+ }
+ dir := "/net/" + comp[1] + "/" + name
+ laddr, err := readPlan9Addr(comp[1], dir+"/local")
+ if err != nil {
+ return nil, err
+ }
+ return newFD(comp[1], name, ctl, nil, laddr, nil), nil
+}
+
+func newFileConn(f *os.File) (c Conn, err error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ if !fd.ok() {
+ return nil, syscall.EINVAL
+ }
+
+ fd.data, err = os.OpenFile(fd.dir+"/data", os.O_RDWR, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ switch fd.laddr.(type) {
+ case *TCPAddr:
+ return newTCPConn(fd), nil
+ case *UDPAddr:
+ return newUDPConn(fd), nil
+ }
+ return nil, syscall.EPLAN9
+}
+
+func newFileListener(f *os.File) (l Listener, err error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ switch fd.laddr.(type) {
+ case *TCPAddr:
+ default:
+ return nil, syscall.EPLAN9
+ }
+
+ // check that file corresponds to a listener
+ s, err := fd.status(len("Listen"))
+ if err != nil {
+ return nil, err
+ }
+ if s != "Listen" {
+ return nil, errors.New("file does not represent a listener")
+ }
+
+ return &TCPListener{fd}, nil
+}
+
// FileConn returns a copy of the network connection corresponding to
// the open file f. It is the caller's responsibility to close f when
// finished. Closing c does not affect f, and closing f does not
// affect c.
func FileConn(f *os.File) (c Conn, err error) {
- return nil, syscall.EPLAN9
+ return newFileConn(f)
}
// FileListener returns a copy of the network listener corresponding
@@ -22,7 +145,7 @@ func FileConn(f *os.File) (c Conn, err error) {
// when finished. Closing l does not affect f, and closing f does not
// affect l.
func FileListener(f *os.File) (l Listener, err error) {
- return nil, syscall.EPLAN9
+ return newFileListener(f)
}
// FilePacketConn returns a copy of the packet network connection
src/pkg/net/tcpsock_plan9.go
--- a/src/pkg/net/tcpsock_plan9.go
+++ b/src/pkg/net/tcpsock_plan9.go
@@ -161,7 +161,7 @@ func (l *TCPListener) SetDeadline(t time.Time) error {
// File returns a copy of the underlying os.File, set to blocking
// mode. It is the caller's responsibility to close f when finished.
// Closing l does not affect f, and closing f does not affect l.
-func (l *TCPListener) File() (f *os.File, err error) { return l.fd.dup() }
+func (l *TCPListener) File() (f *os.File, err error) { return l.dup() }
// ListenTCP announces on the TCP address laddr and returns a TCP
// listener. Net must be "tcp", "tcp4", or "tcp6". If laddr has a
コアとなるコードの解説
src/pkg/net/fd_plan9.goの変更点
func (fd *netFD) dup() (*os.File, error):- このメソッドは、
netFDが表すネットワーク接続のデータファイル(fd.data)のファイルディスクリプタを複製し、新しい*os.Fileオブジェクトとして返します。これは、Goのnet.Connインターフェースが提供するFile()メソッドの内部で利用され、接続の基盤となるファイルディスクリプタへのアクセスを可能にします。 fd.fileヘルパー関数を呼び出すことで、実際の複製処理と*os.Fileの生成を行っています。
- このメソッドは、
func (l *TCPListener) dup() (*os.File, error):TCPListener専用のdupメソッドです。リスナーの制御ファイル(l.fd.ctl)のファイルディスクリプタを複製し、新しい*os.Fileとして返します。これはTCPListener.File()メソッドによって利用されます。
func (fd *netFD) file(f *os.File, s string) (*os.File, error):- 汎用的なヘルパー関数で、与えられた
*os.Fileのファイルディスクリプタをsyscall.Dupで複製し、その複製されたディスクリプタから新しい*os.Fileオブジェクトを作成します。syscall.ForkLockを使用して、Dupシステムコール中の競合状態を防いでいます。
- 汎用的なヘルパー関数で、与えられた
src/pkg/net/file_plan9.goの変更点
このファイルは、Plan 9のファイルシステム上のネットワーク関連ファイルをGoのネットワークオブジェクトに変換するロジックの大部分を担っています。
func (fd *netFD) status(ln int) (string, error):netFDが表すネットワークデバイスのstatusファイルを読み込み、その内容を文字列として返します。statusファイルは、接続やリスナーの状態情報を提供します。newFileListener関数で、ファイルが実際にリスナーであるかを確認するために使用されます。
func newFileFD(f *os.File) (net *netFD, err error):- この関数は、このコミットの最も重要な部分です。
*os.Fileオブジェクトを受け取り、それがPlan 9のネットワークファイルシステム(/net)内のどのファイル(ctl,data,cloneなど)に対応するかを解析し、Goの内部ネットワークディスクリプタ構造体であるnetFDを構築します。 syscall.Fd2pathでファイルパスを取得し、そのパスを/で分割して解析します。- ファイルが
ctlまたはcloneの場合、そのファイルからネットワークインスタンス名(例:0,1など)を読み取り、対応するネットワークディレクトリ(例:/net/tcp/0)を特定します。 - ファイルが
dataなどの場合、パスから制御ファイル(ctl)のパスを推測し、それをオープンします。 - 最終的に、特定されたネットワークタイプ、インスタンス名、制御ファイル、および
localファイルから読み取ったローカルアドレス情報を用いて新しいnetFDを生成します。
- この関数は、このコミットの最も重要な部分です。
func newFileConn(f *os.File) (c Conn, err error):newFileFDを呼び出してnetFDを構築した後、そのnetFDのdataファイルをオープンします。netFDのローカルアドレスの型(*TCPAddrまたは*UDPAddr)に基づいて、newTCPConnまたはnewUDPConnを呼び出し、適切なnet.Connインターフェースを返します。これにより、Plan 9のデータファイルがGoの汎用的な接続インターフェースとして扱えるようになります。
func newFileListener(f *os.File) (l Listener, err error):- 同様に
newFileFDを呼び出してnetFDを構築します。 netFD.statusメソッドを呼び出し、statusファイルの内容が"Listen"であることを確認します。これにより、渡された*os.Fileが実際にネットワークリスナーを表していることを保証します。- 検証が成功した場合、
TCPListener構造体を返します。
- 同様に
func FileConn(f *os.File) (c Conn, err error)とfunc FileListener(f *os.File) (l Listener, err error):- これらのGo標準ライブラリ関数は、以前はPlan 9では未実装(
syscall.EPLAN9を返すだけ)でしたが、このコミットにより、それぞれ新しく実装されたnewFileConn(f)とnewFileListener(f)を呼び出すように変更されました。これにより、Plan 9環境でもこれらの関数が期待通りに機能するようになります。
- これらのGo標準ライブラリ関数は、以前はPlan 9では未実装(
src/pkg/net/tcpsock_plan9.goの変更点
func (l *TCPListener) File() (f *os.File, err error):- このメソッドは、
TCPListenerの基盤となる*os.Fileを返すためのものです。以前はl.fd.dup()を直接呼び出していましたが、このコミットで追加されたl.dup()メソッド(fd_plan9.goで定義)を呼び出すように変更されました。これは、TCPListenerが自身の制御ファイル(ctl)を複製するための適切なdupメソッドを使用するように、委譲先を修正したものです。
- このメソッドは、
これらの変更により、GoのnetパッケージはPlan 9のユニークなネットワークモデルに深く統合され、Go開発者がPlan 9上でより自然な形でネットワークプログラミングを行えるようになりました。
関連リンク
- Go
netpackage documentation: https://pkg.go.dev/net - Plan 9 from Bell Labs: https://9p.io/plan9/
- Plan 9 network protocols: https://9p.io/plan9/man/man7/ip.html
参考にした情報源リンク
- https://golang.org/cl/7235068 (Go Code Review - このコミットの元の変更リスト)
- Go言語のソースコード(
src/pkg/net/ディレクトリ内の関連ファイル) - Plan 9のドキュメント(特にネットワーク関連のmanページ)
- Go言語の
netパッケージの設計に関する一般的な知識 syscallパッケージのドキュメント(Dup,Fd2pathなど)osパッケージのドキュメント(OpenFile,NewFileなど)The user wants me to output the generated explanation. I have already generated the explanation in the previous turn. I will now output it.# [インデックス 15432] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnetパッケージにおいて、Plan 9オペレーティングシステム向けのネットワーク接続およびリスナーのファイル表現に関する機能を追加するものです。具体的には、Plan 9のファイルシステム上のネットワーク関連ファイルをGoのnet.Connやnet.Listenerインターフェースに変換するためのFileConn、FileListener、およびFileメソッドの実装が含まれています。
コミット
commit b461fe660d82b3f3f21cd4042e0f4d3f800aac6c
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Tue Feb 26 01:26:40 2013 +0100
net: Implement FileListener, FileConn, and File methods for Plan 9
Functions for representing network connections as files
and vice versa, on Plan 9.
Representing network connections as files is not so
straight-forward, because a network connection on Plan 9
is represented by a host of files rather than a single
file descriptor (as is the case on UNIX). We use the
type system to distinguish between listeners and
connections, returning the control file in the former
case and the data file in the latter case.
R=rsc, rminnich, ality, akumar, bradfitz
CC=golang-dev
https://golang.org/cl/7235068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b461fe660d82b3f3f21cd4042e0f4d3f800aac6c
元コミット内容
このコミットは、GoのnetパッケージがPlan 9環境でネットワーク接続をファイルとして表現し、またその逆の変換を可能にするための機能を追加します。Plan 9では、UNIX系システムのように単一のファイルディスクリプタでネットワーク接続を表現するのではなく、複数の関連ファイル(例: ctl, data, statusなど)の集合として表現します。このコミットは、リスナーと接続を区別するために型システムを利用し、それぞれの場合に応じて制御ファイル(ctl)またはデータファイル(data)を返すように実装しています。
変更の背景
Go言語はクロスプラットフォーム対応を目指しており、Plan 9もサポート対象の一つです。Plan 9のネットワークモデルはUNIX系システムとは大きく異なるため、Goの標準ネットワークインターフェース(net.Conn, net.Listener)をPlan 9のファイルベースのネットワーク表現に適合させる必要がありました。
これまでのGoのPlan 9向けnetパッケージの実装では、FileConnやFileListenerといった、既存の*os.Fileからネットワーク接続やリスナーを再構築する機能が未実装でした。これらの関数は、GoプログラムがPlan 9のファイルシステムを通じて確立されたネットワーク接続を、Goの慣用的なネットワークAPIで操作するために不可欠です。このコミットは、このギャップを埋め、Plan 9上でのGoのネットワークプログラミングの柔軟性と互換性を向上させることを目的としています。
前提知識の解説
Plan 9 from Bell Labs
Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。その最も特徴的な設計思想は「すべてがファイルである」というもので、プロセス、デバイス、ネットワーク接続など、システム内のあらゆるリソースがファイルシステムのエントリとして表現されます。これにより、標準的なファイル操作のインターフェースを通じて、多様なリソースにアクセスできる統一されたプログラミングモデルが提供されます。
Plan 9のネットワークモデル
Plan 9のネットワークモデルは、その「すべてがファイルである」という哲学を色濃く反映しています。
/netファイルシステム: ネットワーク関連のリソースは、特殊なファイルシステムである/net以下にマウントされます。- 接続の表現: UNIX系システムではソケットが単一のファイルディスクリプタで表現されるのに対し、Plan 9では個々のネットワーク接続やリスナーはディレクトリとして表現され、そのディレクトリ内に複数の特殊なファイルが含まれます。
ctl(control): 接続の制御を行うためのファイル。接続の確立、切断、オプション設定など。data(data): 実際のデータ送受信を行うためのファイル。読み書きすることでネットワークI/Oが行われます。status(status): 接続の状態情報を提供するファイル。local(local): ローカルアドレス情報。remote(remote): リモートアドレス情報。
cloneファイル: 新しい接続やリスナーを作成するための「クローン」ファイル。これをオープンすると、新しい接続ディレクトリが作成されます。
Go言語のnetパッケージ
Goのnetパッケージは、TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うためのプラットフォーム非依存のインターフェースを提供します。
net.Connインターフェース: ネットワーク接続を表すインターフェースで、Read、Write、Closeなどのメソッドを持ちます。net.Listenerインターフェース: ネットワークリスナーを表すインターフェースで、Acceptメソッドを通じて新しい接続を受け入れます。*os.Fileとネットワーク: UNIX系システムでは、ソケットはファイルディスクリプタとして扱われるため、net.Connやnet.Listenerから*os.Fileを取得したり、その逆を行ったりする機能(File()、FileConn()、FileListener())が提供されています。これは、GoのネットワークAPIと、低レベルのファイルディスクリプタ操作を必要とする他のライブラリやシステムコールとの連携を可能にします。
技術的詳細
このコミットの主要な技術的課題は、Plan 9の複数ファイルで構成されるネットワーク接続の概念を、Goの単一オブジェクト(net.Connやnet.Listener)にマッピングすることです。
netFDの拡張:netFDはGoのnetパッケージ内部でネットワークファイルディスクリプタを抽象化する構造体です。Plan 9では、このnetFDがctl(制御ファイル)とdata(データファイル)という2つの*os.Fileを内部に持つことで、Plan 9のネットワークモデルを表現します。dup()メソッドの再定義:netFD.dup(): 以前はPlan 9では未実装でしたが、このコミットで実装され、接続のdataファイルディスクリプタを複製して*os.Fileとして返します。TCPListener.dup():TCPListener専用のdupメソッドが追加され、リスナーのctlファイルディスクリプタを複製して返します。これにより、TCPListener.File()メソッドが正しく機能するようになります。
newFileFD関数の導入:- この関数は、与えられた
*os.FileがPlan 9のネットワークファイル(ctl、data、cloneなど)のいずれかであるかを解析し、対応するnetFD構造体を構築します。 syscall.Fd2pathを使用してファイルディスクリプタからパスを取得し、そのパスを解析して、ネットワークデバイスのタイプ(例:tcp)、インスタンス名、および関連する制御ファイルやデータファイルのパスを特定します。- 特に、
cloneファイルやctlファイルが渡された場合は、そこからネットワークインスタンス名(例:0,1など)を読み取り、対応するディレクトリパスを構築します。 readPlan9Addr関数(既存)を使用して、localファイルからローカルアドレスを読み込み、netFDに設定します。
- この関数は、与えられた
newFileConnとnewFileListener関数の導入:- これらの関数は
newFileFDを内部的に呼び出し、*os.FileからnetFDを構築します。 newFileConnは、構築されたnetFDのdataファイルをオープンし、ローカルアドレスのタイプ(TCPまたはUDP)に基づいて適切なnet.Conn実装(newTCPConnまたはnewUDPConn)を返します。newFileListenerは、構築されたnetFDのstatusファイルを読み込み、その内容が"Listen"であることを確認することで、ファイルがリスナーを表していることを検証します。その後、TCPListenerを返します。
- これらの関数は
FileConnとFileListenerのPlan 9実装:- Goの
netパッケージで定義されているFileConnとFileListener関数は、Plan 9環境ではこれまでsyscall.EPLAN9(Plan 9固有のエラー)を返していましたが、このコミットにより、それぞれnewFileConnとnewFileListenerを呼び出すように変更され、実際の機能が提供されます。
- Goの
これらの変更により、GoプログラムはPlan 9のファイルシステムを通じて得られたネットワーク関連の*os.Fileオブジェクトを、Goの抽象化されたネットワークインターフェース(net.Connやnet.Listener)としてシームレスに扱うことができるようになります。
コアとなるコードの変更箇所
src/pkg/net/fd_plan9.go
--- a/src/pkg/net/fd_plan9.go
+++ b/src/pkg/net/fd_plan9.go
@@ -83,8 +83,29 @@ func (fd *netFD) Close() error {
return err
}
+// This method is only called via Conn.
func (fd *netFD) dup() (*os.File, error) {
- return nil, syscall.EPLAN9
+ if !fd.ok() || fd.data == nil {
+ return nil, syscall.EINVAL
+ }
+ return fd.file(fd.data, fd.dir+"/data")
+}
+
+func (l *TCPListener) dup() (*os.File, error) {
+ if !l.fd.ok() {
+ return nil, syscall.EINVAL
+ }
+ return l.fd.file(l.fd.ctl, l.fd.dir+"/ctl")
+}
+
+func (fd *netFD) file(f *os.File, s string) (*os.File, error) {
+ syscall.ForkLock.RLock()
+ dfd, err := syscall.Dup(int(f.Fd()), -1)
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return nil, &OpError{"dup", s, fd.laddr, err}
+ }
+ return os.NewFile(uintptr(dfd), s), nil
}
func setDeadline(fd *netFD, t time.Time) error {
src/pkg/net/file_plan9.go
--- a/src/pkg/net/file_plan9.go
+++ b/src/pkg/net/file_plan9.go
@@ -5,16 +5,139 @@
package net
import (
+ "errors"
+ "io"
"os"
"syscall"
)
+func (fd *netFD) status(ln int) (string, error) {
+ if !fd.ok() {
+ return "", syscall.EINVAL
+ }
+
+ status, err := os.Open(fd.dir + "/status")
+ if err != nil {
+ return "", err
+ }
+ defer status.Close()
+ buf := make([]byte, ln)
+ n, err := io.ReadFull(status, buf[:])
+ if err != nil {
+ return "", err
+ }
+ return string(buf[:n]), nil
+}
+
+func newFileFD(f *os.File) (net *netFD, err error) {
+ var ctl *os.File
+ close := func(fd int) {
+ if err != nil {
+ syscall.Close(fd)
+ }
+ }
+
+ path, err := syscall.Fd2path(int(f.Fd()))
+ if err != nil {
+ return nil, os.NewSyscallError("fd2path", err)
+ }
+ comp := splitAtBytes(path, "/")
+ n := len(comp)
+ if n < 3 || comp[0] != "net" {
+ return nil, syscall.EPLAN9
+ }
+
+ name := comp[2]
+ switch file := comp[n-1]; file {
+ case "ctl", "clone":
+ syscall.ForkLock.RLock()
+ fd, err := syscall.Dup(int(f.Fd()), -1)
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return nil, os.NewSyscallError("dup", err)
+ }
+ defer close(fd)
+
+ dir := "/net/" + comp[n-2]
+ ctl = os.NewFile(uintptr(fd), dir+"/"+file)
+ ctl.Seek(0, 0)
+ var buf [16]byte
+ n, err := ctl.Read(buf[:])
+ if err != nil {
+ return nil, err
+ }
+ name = string(buf[:n])
+ default:
+ if len(comp) < 4 {
+ return nil, errors.New("could not find control file for connection")
+ }
+ dir := "/net/" + comp[1] + "/" + name
+ ctl, err = os.OpenFile(dir+"/ctl", os.O_RDWR, 0)
+ if err != nil {
+ return nil, err
+ }
+ defer close(int(ctl.Fd()))
+ }
+ dir := "/net/" + comp[1] + "/" + name
+ laddr, err := readPlan9Addr(comp[1], dir+"/local")
+ if err != nil {
+ return nil, err
+ }
+ return newFD(comp[1], name, ctl, nil, laddr, nil), nil
+}
+
+func newFileConn(f *os.File) (c Conn, err error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ if !fd.ok() {
+ return nil, syscall.EINVAL
+ }
+
+ fd.data, err = os.OpenFile(fd.dir+"/data", os.O_RDWR, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ switch fd.laddr.(type) {
+ case *TCPAddr:
+ return newTCPConn(fd), nil
+ case *UDPAddr:
+ return newUDPConn(fd), nil
+ }
+ return nil, syscall.EPLAN9
+}
+
+func newFileListener(f *os.File) (l Listener, err error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ switch fd.laddr.(type) {
+ case *TCPAddr:
+ default:
+ return nil, syscall.EPLAN9
+ }
+
+ // check that file corresponds to a listener
+ s, err := fd.status(len("Listen"))
+ if err != nil {
+ return nil, err
+ }
+ if s != "Listen" {
+ return nil, errors.New("file does not represent a listener")
+ }
+
+ return &TCPListener{fd}, nil
+}
+
// FileConn returns a copy of the network connection corresponding to
// the open file f. It is the caller's responsibility to close f when
// finished. Closing c does not affect f, and closing f does not
// affect c.
func FileConn(f *os.File) (c Conn, err error) {
- return nil, syscall.EPLAN9
+ return newFileConn(f)
}
// FileListener returns a copy of the network listener corresponding
@@ -22,7 +145,7 @@ func FileConn(f *os.File) (c Conn, err error) {
// when finished. Closing l does not affect f, and closing f does not
// affect l.
func FileListener(f *os.File) (l Listener, err error) {
- return nil, syscall.EPLAN9
+ return newFileListener(f)
}
// FilePacketConn returns a copy of the packet network connection
src/pkg/net/tcpsock_plan9.go
--- a/src/pkg/net/tcpsock_plan9.go
+++ b/src/pkg/net/tcpsock_plan9.go
@@ -161,7 +161,7 @@ func (l *TCPListener) SetDeadline(t time.Time) error {
// File returns a copy of the underlying os.File, set to blocking
// mode. It is the caller's responsibility to close f when finished.
// Closing l does not affect f, and closing f does not affect l.
-func (l *TCPListener) File() (f *os.File, err error) { return l.fd.dup() }
+func (l *TCPListener) File() (f *os.File, err error) { return l.dup() }
// ListenTCP announces on the TCP address laddr and returns a TCP
// listener. Net must be "tcp", "tcp4", or "tcp6". If laddr has a
コアとなるコードの解説
src/pkg/net/fd_plan9.goの変更点
func (fd *netFD) dup() (*os.File, error):- このメソッドは、
netFDが表すネットワーク接続のデータファイル(fd.data)のファイルディスクリプタを複製し、新しい*os.Fileオブジェクトとして返します。これは、Goのnet.Connインターフェースが提供するFile()メソッドの内部で利用され、接続の基盤となるファイルディスクリプタへのアクセスを可能にします。 fd.fileヘルパー関数を呼び出すことで、実際の複製処理と*os.Fileの生成を行っています。
- このメソッドは、
func (l *TCPListener) dup() (*os.File, error):TCPListener専用のdupメソッドです。リスナーの制御ファイル(l.fd.ctl)のファイルディスクリプタを複製し、新しい*os.Fileとして返します。これはTCPListener.File()メソッドによって利用されます。
func (fd *netFD) file(f *os.File, s string) (*os.File, error):- 汎用的なヘルパー関数で、与えられた
*os.Fileのファイルディスクリプタをsyscall.Dupで複製し、その複製されたディスクリプタから新しい*os.Fileオブジェクトを作成します。syscall.ForkLockを使用して、Dupシステムコール中の競合状態を防いでいます。
- 汎用的なヘルパー関数で、与えられた
src/pkg/net/file_plan9.goの変更点
このファイルは、Plan 9のファイルシステム上のネットワーク関連ファイルをGoのネットワークオブジェクトに変換するロジックの大部分を担っています。
func (fd *netFD) status(ln int) (string, error):netFDが表すネットワークデバイスのstatusファイルを読み込み、その内容を文字列として返します。statusファイルは、接続やリスナーの状態情報を提供します。newFileListener関数で、ファイルが実際にリスナーであるかを確認するために使用されます。
func newFileFD(f *os.File) (net *netFD, err error):- この関数は、このコミットの最も重要な部分です。
*os.Fileオブジェクトを受け取り、それがPlan 9のネットワークファイルシステム(/net)内のどのファイル(ctl,data,cloneなど)に対応するかを解析し、Goの内部ネットワークディスクリプタ構造体であるnetFDを構築します。 syscall.Fd2pathでファイルパスを取得し、そのパスを/で分割して解析します。- ファイルが
ctlまたはcloneの場合、そのファイルからネットワークインスタンス名(例:0,1など)を読み取り、対応するネットワークディレクトリ(例:/net/tcp/0)を特定します。 - ファイルが
dataなどの場合、パスから制御ファイル(ctl)のパスを推測し、それをオープンします。 - 最終的に、特定されたネットワークタイプ、インスタンス名、制御ファイル、および
localファイルから読み取ったローカルアドレス情報を用いて新しいnetFDを生成します。
- この関数は、このコミットの最も重要な部分です。
func newFileConn(f *os.File) (c Conn, err error):newFileFDを呼び出してnetFDを構築した後、そのnetFDのdataファイルをオープンします。netFDのローカルアドレスの型(*TCPAddrまたは*UDPAddr)に基づいて、newTCPConnまたはnewUDPConnを呼び出し、適切なnet.Connインターフェースを返します。これにより、Plan 9のデータファイルがGoの汎用的な接続インターフェースとして扱えるようになります。
func newFileListener(f *os.File) (l Listener, err error):- 同様に
newFileFDを呼び出してnetFDを構築します。 netFD.statusメソッドを呼び出し、statusファイルの内容が"Listen"であることを確認します。これにより、渡された*os.Fileが実際にネットワークリスナーを表していることを保証します。- 検証が成功した場合、
TCPListener構造体を返します。
- 同様に
func FileConn(f *os.File) (c Conn, err error)とfunc FileListener(f *os.File) (l Listener, err error):- これらのGo標準ライブラリ関数は、以前はPlan 9では未実装(
syscall.EPLAN9を返すだけ)でしたが、このコミットにより、それぞれ新しく実装されたnewFileConn(f)とnewFileListener(f)を呼び出すように変更されました。これにより、Plan 9環境でもこれらの関数が期待通りに機能するようになります。
- これらのGo標準ライブラリ関数は、以前はPlan 9では未実装(
src/pkg/net/tcpsock_plan9.goの変更点
func (l *TCPListener) File() (f *os.File, err error):- このメソッドは、
TCPListenerの基盤となる*os.Fileを返すためのものです。以前はl.fd.dup()を直接呼び出していましたが、このコミットで追加されたl.dup()メソッド(fd_plan9.goで定義)を呼び出すように変更されました。これは、TCPListenerが自身の制御ファイル(ctl)を複製するための適切なdupメソッドを使用するように、委譲先を修正したものです。
- このメソッドは、
これらの変更により、GoのnetパッケージはPlan 9のユニークなネットワークモデルに深く統合され、Go開発者がPlan 9上でより自然な形でネットワークプログラミングを行えるようになりました。
関連リンク
- Go
netpackage documentation: https://pkg.go.dev/net - Plan 9 from Bell Labs: https://9p.io/plan9/
- Plan 9 network protocols: https://9p.io/plan9/man/man7/ip.html
参考にした情報源リンク
- https://golang.org/cl/7235068 (Go Code Review - このコミットの元の変更リスト)
- Go言語のソースコード(
src/pkg/net/ディレクトリ内の関連ファイル) - Plan 9のドキュメント(特にネットワーク関連のmanページ)
- Go言語の
netパッケージの設計に関する一般的な知識 syscallパッケージのドキュメント(Dup,Fd2pathなど)osパッケージのドキュメント(OpenFile,NewFileなど)