Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15432] ファイルの概要

このコミットは、Go言語の標準ライブラリであるnetパッケージにおいて、Plan 9オペレーティングシステム向けのネットワーク接続およびリスナーのファイル表現に関する機能を追加するものです。具体的には、Plan 9のファイルシステム上のネットワーク関連ファイルをGoのnet.Connnet.Listenerインターフェースに変換するためのFileConnFileListener、および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パッケージの実装では、FileConnFileListenerといった、既存の*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インターフェース: ネットワーク接続を表すインターフェースで、ReadWriteCloseなどのメソッドを持ちます。
  • net.Listenerインターフェース: ネットワークリスナーを表すインターフェースで、Acceptメソッドを通じて新しい接続を受け入れます。
  • *os.Fileとネットワーク: UNIX系システムでは、ソケットはファイルディスクリプタとして扱われるため、net.Connnet.Listenerから*os.Fileを取得したり、その逆を行ったりする機能(File()FileConn()FileListener())が提供されています。これは、GoのネットワークAPIと、低レベルのファイルディスクリプタ操作を必要とする他のライブラリやシステムコールとの連携を可能にします。

技術的詳細

このコミットの主要な技術的課題は、Plan 9の複数ファイルで構成されるネットワーク接続の概念を、Goの単一オブジェクト(net.Connnet.Listener)にマッピングすることです。

  1. netFDの拡張: netFDはGoのnetパッケージ内部でネットワークファイルディスクリプタを抽象化する構造体です。Plan 9では、このnetFDctl(制御ファイル)とdata(データファイル)という2つの*os.Fileを内部に持つことで、Plan 9のネットワークモデルを表現します。
  2. dup()メソッドの再定義:
    • netFD.dup(): 以前はPlan 9では未実装でしたが、このコミットで実装され、接続のdataファイルディスクリプタを複製して*os.Fileとして返します。
    • TCPListener.dup(): TCPListener専用のdupメソッドが追加され、リスナーのctlファイルディスクリプタを複製して返します。これにより、TCPListener.File()メソッドが正しく機能するようになります。
  3. newFileFD関数の導入:
    • この関数は、与えられた*os.FileがPlan 9のネットワークファイル(ctldatacloneなど)のいずれかであるかを解析し、対応するnetFD構造体を構築します。
    • syscall.Fd2pathを使用してファイルディスクリプタからパスを取得し、そのパスを解析して、ネットワークデバイスのタイプ(例: tcp)、インスタンス名、および関連する制御ファイルやデータファイルのパスを特定します。
    • 特に、cloneファイルやctlファイルが渡された場合は、そこからネットワークインスタンス名(例: 0, 1など)を読み取り、対応するディレクトリパスを構築します。
    • readPlan9Addr関数(既存)を使用して、localファイルからローカルアドレスを読み込み、netFDに設定します。
  4. newFileConnnewFileListener関数の導入:
    • これらの関数はnewFileFDを内部的に呼び出し、*os.FileからnetFDを構築します。
    • newFileConnは、構築されたnetFDdataファイルをオープンし、ローカルアドレスのタイプ(TCPまたはUDP)に基づいて適切なnet.Conn実装(newTCPConnまたはnewUDPConn)を返します。
    • newFileListenerは、構築されたnetFDstatusファイルを読み込み、その内容が"Listen"であることを確認することで、ファイルがリスナーを表していることを検証します。その後、TCPListenerを返します。
  5. FileConnFileListenerのPlan 9実装:
    • Goのnetパッケージで定義されているFileConnFileListener関数は、Plan 9環境ではこれまでsyscall.EPLAN9(Plan 9固有のエラー)を返していましたが、このコミットにより、それぞれnewFileConnnewFileListenerを呼び出すように変更され、実際の機能が提供されます。

これらの変更により、GoプログラムはPlan 9のファイルシステムを通じて得られたネットワーク関連の*os.Fileオブジェクトを、Goの抽象化されたネットワークインターフェース(net.Connnet.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を構築した後、そのnetFDdataファイルをオープンします。
    • 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環境でもこれらの関数が期待通りに機能するようになります。

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上でより自然な形でネットワークプログラミングを行えるようになりました。

関連リンク

参考にした情報源リンク

  • 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.Connnet.Listenerインターフェースに変換するためのFileConnFileListener、および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パッケージの実装では、FileConnFileListenerといった、既存の*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インターフェース: ネットワーク接続を表すインターフェースで、ReadWriteCloseなどのメソッドを持ちます。
  • net.Listenerインターフェース: ネットワークリスナーを表すインターフェースで、Acceptメソッドを通じて新しい接続を受け入れます。
  • *os.Fileとネットワーク: UNIX系システムでは、ソケットはファイルディスクリプタとして扱われるため、net.Connnet.Listenerから*os.Fileを取得したり、その逆を行ったりする機能(File()FileConn()FileListener())が提供されています。これは、GoのネットワークAPIと、低レベルのファイルディスクリプタ操作を必要とする他のライブラリやシステムコールとの連携を可能にします。

技術的詳細

このコミットの主要な技術的課題は、Plan 9の複数ファイルで構成されるネットワーク接続の概念を、Goの単一オブジェクト(net.Connnet.Listener)にマッピングすることです。

  1. netFDの拡張: netFDはGoのnetパッケージ内部でネットワークファイルディスクリプタを抽象化する構造体です。Plan 9では、このnetFDctl(制御ファイル)とdata(データファイル)という2つの*os.Fileを内部に持つことで、Plan 9のネットワークモデルを表現します。
  2. dup()メソッドの再定義:
    • netFD.dup(): 以前はPlan 9では未実装でしたが、このコミットで実装され、接続のdataファイルディスクリプタを複製して*os.Fileとして返します。
    • TCPListener.dup(): TCPListener専用のdupメソッドが追加され、リスナーのctlファイルディスクリプタを複製して返します。これにより、TCPListener.File()メソッドが正しく機能するようになります。
  3. newFileFD関数の導入:
    • この関数は、与えられた*os.FileがPlan 9のネットワークファイル(ctldatacloneなど)のいずれかであるかを解析し、対応するnetFD構造体を構築します。
    • syscall.Fd2pathを使用してファイルディスクリプタからパスを取得し、そのパスを解析して、ネットワークデバイスのタイプ(例: tcp)、インスタンス名、および関連する制御ファイルやデータファイルのパスを特定します。
    • 特に、cloneファイルやctlファイルが渡された場合は、そこからネットワークインスタンス名(例: 0, 1など)を読み取り、対応するディレクトリパスを構築します。
    • readPlan9Addr関数(既存)を使用して、localファイルからローカルアドレスを読み込み、netFDに設定します。
  4. newFileConnnewFileListener関数の導入:
    • これらの関数はnewFileFDを内部的に呼び出し、*os.FileからnetFDを構築します。
    • newFileConnは、構築されたnetFDdataファイルをオープンし、ローカルアドレスのタイプ(TCPまたはUDP)に基づいて適切なnet.Conn実装(newTCPConnまたはnewUDPConn)を返します。
    • newFileListenerは、構築されたnetFDstatusファイルを読み込み、その内容が"Listen"であることを確認することで、ファイルがリスナーを表していることを検証します。その後、TCPListenerを返します。
  5. FileConnFileListenerのPlan 9実装:
    • Goのnetパッケージで定義されているFileConnFileListener関数は、Plan 9環境ではこれまでsyscall.EPLAN9(Plan 9固有のエラー)を返していましたが、このコミットにより、それぞれnewFileConnnewFileListenerを呼び出すように変更され、実際の機能が提供されます。

これらの変更により、GoプログラムはPlan 9のファイルシステムを通じて得られたネットワーク関連の*os.Fileオブジェクトを、Goの抽象化されたネットワークインターフェース(net.Connnet.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を構築した後、そのnetFDdataファイルをオープンします。
    • 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環境でもこれらの関数が期待通りに機能するようになります。

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上でより自然な形でネットワークプログラミングを行えるようになりました。

関連リンク

参考にした情報源リンク

  • https://golang.org/cl/7235068 (Go Code Review - このコミットの元の変更リスト)
  • Go言語のソースコード(src/pkg/net/ディレクトリ内の関連ファイル)
  • Plan 9のドキュメント(特にネットワーク関連のmanページ)
  • Go言語のnetパッケージの設計に関する一般的な知識
  • syscallパッケージのドキュメント(Dup, Fd2pathなど)
  • osパッケージのドキュメント(OpenFile, NewFileなど)