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

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

このコミットは、Go言語の標準ライブラリosパッケージにおけるGetwd(現在の作業ディレクトリを取得する関数)のPlan 9ビルドに関するバグ修正です。以前のコミットでmacOS (Darwin) 向けの変更が導入された際に、Plan 9環境でsyscall.ENOTSUPエラーが未定義であるためにビルドが壊れた問題を解決します。

コミット

commit ad119b9c4dcae8389a3700c245a923b0ebe449cd
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Aug 6 12:04:08 2013 -0700

    os: fix plan9 build
    
    I broke it with the darwin getwd attrlist stuff (0583e9d36dd).
    plan9 doesn't have syscall.ENOTSUP.
    
    It's in api/go1.txt as a symbol always available (not context-specific):
    
    pkg syscall, const ENOTSUP Errno
    
    ... but plan9 isn't considered by cmd/api, so it only looks
    universally available.  Alternatively, we could add a fake ENOTSUP
    to plan9, but they were making efforts earlier to clean their
    syscall package, so I'd prefer not to dump more in it.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/12509044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ad119b9c4dcae8389a3700c245a923b0ebe449cd

元コミット内容

このコミットは、osパッケージのGetwd関数がPlan 9環境でビルドできない問題を修正します。問題は、以前のコミット0583e9d36dd("darwin: use getattrlist for Getwd")でmacOS (Darwin) 向けのGetwd実装が変更された際に発生しました。この変更は、syscall.ENOTSUPというエラーコードを使用しましたが、Plan 9システムにはこのエラーコードが存在しないため、ビルドエラーが発生しました。

コミットメッセージでは、syscall.ENOTSUPapi/go1.txtで常に利用可能なシンボルとして定義されているにもかかわらず、cmd/apiツールがPlan 9を考慮していないため、普遍的に利用可能であると誤認されていたことが指摘されています。修正アプローチとして、Plan 9に偽のENOTSUPを追加するのではなく、プラットフォーム固有のロジックでsyscall.ENOTSUPのチェックを分離する方法が選択されました。これは、Plan 9のsyscallパッケージをクリーンに保つという以前の取り組みに沿ったものです。

変更の背景

この変更の背景には、Go言語のクロスプラットフォーム対応における課題があります。Goは様々なオペレーティングシステムをサポートしていますが、OS固有のシステムコールやエラーコードの差異を吸収する必要があります。

具体的には、以下の経緯があります。

  1. macOS (Darwin) のGetwd最適化: 以前のコミット0583e9d36ddでは、macOSのGetwd実装がgetattrlistシステムコールを使用するように変更されました。これは、シンボリックリンクを解決せずに現在の作業ディレクトリのパスを取得するための最適化でした。このgetattrlistENOTSUP(Operation not supported)を返す可能性があるため、そのエラーを適切に処理する必要がありました。
  2. syscall.ENOTSUPの利用: macOSの新しいGetwd実装では、syscall.ENOTSUPを使用して、特定の操作がサポートされていない場合にエラーを処理していました。
  3. Plan 9での問題: しかし、Plan 9オペレーティングシステムにはENOTSUPというエラーコードが存在しませんでした。Goのビルドシステムは、api/go1.txtに定義されているシンボルを普遍的に利用可能であると見なす傾向がありましたが、cmd/apiツールがPlan 9の特殊性を完全に考慮していなかったため、この差異が見過ごされました。
  4. ビルドの破損: 結果として、Plan 9環境でGoのosパッケージをビルドしようとすると、syscall.ENOTSUPが未定義であるためにコンパイルエラーが発生し、ビルドが壊れてしまいました。
  5. 修正の必要性: この問題を解決し、Goのクロスプラットフォーム互換性を維持するために、このコミットが導入されました。特に、Plan 9のsyscallパッケージに余計なものを追加したくないという開発者の意向も考慮されました。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  • Go言語のクロスコンパイルとビルドタグ: Goは異なるOSやアーキテクチャ向けにコードをコンパイルする機能(クロスコンパイル)を強力にサポートしています。これには、ファイル名に_os.go(例: getwd_darwin.go)のようなサフィックスを付けることで、特定のOS向けにのみコンパイルされるファイルを指定する仕組みがあります。
  • syscallパッケージ: Goのsyscallパッケージは、オペレーティングシステムの低レベルなプリミティブ(システムコール)へのインターフェースを提供します。OSによって利用可能なシステムコールやエラーコードは異なります。
  • Getwd関数: os.Getwd()は、現在の作業ディレクトリの絶対パスを返すGoの標準関数です。この関数の実装は、OSによって異なる場合があります。
  • syscall.ENOTSUP: これは、"Operation not supported"(操作がサポートされていません)を意味するエラーコードです。多くのPOSIX準拠システム(Linux, macOSなど)で定義されていますが、Plan 9のような非POSIXシステムでは存在しない場合があります。
  • getattrlist (macOS): macOSで利用可能なシステムコールで、ファイルシステムオブジェクトの属性を効率的に取得するために使用されます。os.Getwdの実装で、シンボリックリンクを解決せずに現在のディレクトリのパスを取得するために利用されることがあります。
  • Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Unixとは異なる設計思想を持ち、ファイルシステム中心の哲学が特徴です。Go言語はPlan 9の影響を強く受けており、Plan 9向けのビルドもサポートしています。
  • api/go1.txtcmd/api: api/go1.txtは、Go 1の互換性保証の一部として、Goの標準ライブラリが提供するAPIのリストを定義したファイルです。cmd/apiツールは、このファイルと実際のGoソースコードを比較し、APIの互換性が維持されているかを確認します。

技術的詳細

このコミットの技術的な解決策は、syscall.ENOTSUPの存在をプラットフォーム固有のロジックにカプセル化することです。

  1. useSyscallwd変数の導入: src/pkg/os/getwd.goに、useSyscallwdというfunc(error) bool型のグローバル変数(関数ポインタ)が導入されました。この変数は、syscall.Getwd()が返したエラーに基づいて、その戻り値を使用すべきかどうかを決定するロジックをカプセル化します。 初期値としては、エラーが何であれ常にtrueを返す匿名関数が設定されています。これは、デフォルトの挙動として、syscall.Getwdがエラーを返してもその結果を使用しようとすることを意味します。

    // useSyscallwd determines whether to use the return value of
    // syscall.Getwd based on its error.
    var useSyscallwd = func(error) bool { return true }
    
  2. Getwd関数の変更: src/pkg/os/getwd.goGetwd関数内で、syscall.Getwd()からのエラーチェックが変更されました。以前はe != syscall.ENOTSUPという直接的な比較が行われていましたが、これがuseSyscallwd(e)の呼び出しに置き換えられました。

    // 変更前
    // if e != syscall.ENOTSUP {
    
    // 変更後
    if useSyscallwd(e) {
    

    これにより、syscall.ENOTSUPの存在しないプラットフォーム(Plan 9など)では、この比較が直接行われることがなくなります。

  3. macOS (Darwin) 固有の実装: src/pkg/os/getwd_darwin.goという新しいファイルが追加されました。このファイルは、Goのビルドタグの仕組みにより、macOS (Darwin) 環境でのみコンパイルされます。 このファイルには、useSyscallwdDarwinという関数が定義されています。この関数は、引数として受け取ったエラーがsyscall.ENOTSUPと異なる場合にtrueを返します。

    func useSyscallwdDarwin(err error) bool {
        return err != syscall.ENOTSUP
    }
    

    そして、init関数内で、グローバル変数useSyscallwdにこのuseSyscallwdDarwin関数が代入されます。

    func init() {
        useSyscallwd = useSyscallwdDarwin
    }
    

    これにより、macOS環境ではuseSyscallwdsyscall.ENOTSUPを考慮したロジックを持つようになり、他のプラットフォームではデフォルトの(常にtrueを返す)ロジックが適用されることになります。

このアプローチにより、syscall.ENOTSUPが存在しないPlan 9のようなシステムでは、syscall.ENOTSUPへの参照がコンパイル時に含まれなくなり、ビルドエラーが解消されます。同時に、syscall.ENOTSUPが存在するmacOSのようなシステムでは、そのエラーを適切に処理する以前のロジックが維持されます。

コアとなるコードの変更箇所

src/pkg/os/getwd.go

--- a/src/pkg/os/getwd.go
+++ b/src/pkg/os/getwd.go
@@ -14,6 +14,10 @@ var getwdCache struct {
 	dir string
 }
 
+// useSyscallwd determines whether to use the return value of
+// syscall.Getwd based on its error.
+var useSyscallwd = func(error) bool { return true }
+
 // Getwd returns a rooted path name corresponding to the
 // current directory.  If the current directory can be
 // reached via multiple paths (due to symbolic links),
@@ -22,7 +26,7 @@ func Getwd() (pwd string, err error) {
 	// If the operating system provides a Getwd call, use it.
 	if syscall.ImplementsGetwd {
 		s, e := syscall.Getwd()
-		if e != syscall.ENOTSUP {
+		if useSyscallwd(e) {
 			return s, NewSyscallError("getwd", e)
 		}
 	}

src/pkg/os/getwd_darwin.go (新規ファイル)

--- /dev/null
+++ b/src/pkg/os/getwd_darwin.go
@@ -0,0 +1,15 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+func init() {
+	useSyscallwd = useSyscallwdDarwin
+}
+
+func useSyscallwdDarwin(err error) bool {
+	return err != syscall.ENOTSUP
+}

コアとなるコードの解説

src/pkg/os/getwd.goの変更点

  1. useSyscallwd変数の追加: var useSyscallwd = func(error) bool { return true } この行は、useSyscallwdという関数型の変数を宣言し、初期化しています。この変数は、syscall.Getwd()から返されたエラーeを受け取り、そのエラーがGetwdの戻り値を使用すべきではないことを示すかどうかを判断するロジックをカプセル化します。デフォルトでは、どのようなエラーが返されてもtrueを返す(つまり、syscall.Getwdの戻り値を使用する)ように設定されています。これは、syscall.ENOTSUPが存在しないシステム(Plan 9など)でのフォールバックの挙動となります。

  2. Getwd関数内の条件式の変更: if useSyscallwd(e) { 以前はif e != syscall.ENOTSUP {という直接的な比較が行われていました。この変更により、エラーの評価ロジックがuseSyscallwd関数に委譲されます。これにより、syscall.ENOTSUPが定義されていない環境でもコンパイルエラーが発生しなくなります。

src/pkg/os/getwd_darwin.goの追加と解説

  1. ファイル名とビルドタグ: ファイル名がgetwd_darwin.goであるため、Goのビルドシステムは、このファイルがmacOS (Darwin) 環境でのみコンパイルされることを認識します。これにより、macOS固有のコードが他のOSのビルドに影響を与えることを防ぎます。

  2. useSyscallwdDarwin関数の定義: func useSyscallwdDarwin(err error) bool { return err != syscall.ENOTSUP } この関数は、macOS環境におけるuseSyscallwdの具体的な実装を提供します。macOSではsyscall.ENOTSUPが定義されているため、この関数は、syscall.Getwd()が返したエラーがsyscall.ENOTSUPではない場合にtrueを返します。これは、getattrlistENOTSUPを返した場合に、そのエラーを特別扱いしてGetwdの戻り値を使用しないという、以前のmacOS固有のロジックを維持するためのものです。

  3. init関数でのuseSyscallwdのオーバーライド: func init() { useSyscallwd = useSyscallwdDarwin } Goのinit関数は、パッケージが初期化される際に自動的に実行されます。macOS環境でこのファイルがコンパイルされると、このinit関数が実行され、src/pkg/os/getwd.goで定義されたグローバル変数useSyscallwdが、useSyscallwdDarwin関数で上書きされます。これにより、macOSではsyscall.ENOTSUPを考慮したエラーハンドリングが有効になります。

この変更の全体的な効果は、syscall.ENOTSUPの存在に依存するロジックをプラットフォーム固有のファイルに分離し、syscall.ENOTSUPが存在しないプラットフォームではそのロジックがコンパイルされないようにすることです。これにより、Goのクロスプラットフォーム互換性が向上し、Plan 9でのビルドが再び可能になりました。

関連リンク

参考にした情報源リンク