[インデックス 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
net
package 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
net
package 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
など)