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

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

このコミットは、Go言語の標準ライブラリであるtimeパッケージにおけるPlan 9オペレーティングシステム向けのビルド問題を修正するものです。具体的には、Plan 9のsyscall.Open関数の引数仕様が他のOSと異なるために発生していたreadFile関数の問題を解決し、それに伴う時間帯情報(zoneinfo)の読み込みロジックの改善が含まれています。

コミット

commit 1cb254a085678170895634b33c486b3cef223286
Author: Anthony Martin <ality@pbrane.org>
Date:   Mon Dec 12 16:12:22 2011 -0500

    time: fix Plan 9 build for new API
    
    I had to move readFile into sys_$GOOS.go
    since syscall.Open takes only two arguments
    on Plan 9.
    
    R=lucio.dere, rsc, alex.brainman
    CC=golang-dev
    https://golang.org/cl/5447061

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

https://github.com/golang/go/commit/1cb254a085678170895634b33c486b3cef223286

元コミット内容

このコミットは、Go言語のtimeパッケージがPlan 9環境で正しくビルドされ、動作するようにするための修正です。主な変更点は、ファイル読み込み関数readFileのプラットフォーム固有の実装への分離と、それに伴うPlan 9向けの時間帯情報処理ロジックの更新です。

変更の背景

Go言語はクロスプラットフォーム対応を重視しており、様々なオペレーティングシステムで動作するように設計されています。このコミットの背景には、GoのsyscallパッケージにおけるOpen関数のAPI変更、またはPlan 9固有のsyscall.Openの引数仕様が他のUnix系システム(Linux, macOSなど)と異なっていたという問題があります。

具体的には、一般的なUnix系システムではsyscall.Openはファイルパス、フラグ、パーミッションの3つの引数を取りますが、Plan 9ではファイルパスとフラグの2つの引数しか取らないという違いがありました。src/pkg/time/sys.goに存在していた汎用的なreadFile関数は、この3引数形式のsyscall.Openを使用していたため、Plan 9環境でビルドエラーを引き起こしていました。

この問題を解決するため、readFile関数を各OSのsyscall.Openの引数仕様に合わせて、プラットフォーム固有のファイル(sys_plan9.gosys_unix.go)に分離する必要が生じました。また、時間帯情報の読み込みロジックも、この変更に合わせて調整する必要がありました。

前提知識の解説

  • Go言語のsyscallパッケージ: Go言語の標準ライブラリの一つで、オペレーティングシステムが提供する低レベルなシステムコール(ファイル操作、プロセス管理、ネットワーク通信など)へのインターフェースを提供します。OSに依存する処理を直接呼び出すために使用されます。
  • Go言語のtimeパッケージ: 日付と時刻の操作、時間帯の管理、タイマー、スリープなどの機能を提供する標準ライブラリです。内部的にはOSのシステムコールを利用して現在時刻の取得や時間帯情報の読み込みを行います。
  • Plan 9 from Bell Labs: ベル研究所で開発された分散オペレーティングシステムです。Unixの概念をさらに推し進め、全てのリソースをファイルとして扱うという特徴的な設計思想を持っています。そのシステムコールAPIはUnix系OSと類似点もありますが、細部で異なる点が多く、特にsyscall.Openの引数仕様はその一例です。
  • Goのビルドタグ (+build): Go言語では、ソースコードの先頭に// +build tagのようなコメントを記述することで、特定のビルド環境(OS、アーキテクチャなど)でのみそのファイルをコンパイル対象とするように指定できます。これにより、プラットフォーム固有のコードを容易に管理できます。例えば、// +build plan9はPlan 9環境でのみコンパイルされ、// +build unixはUnix系環境(Linux, macOSなど)でコンパイルされます。
  • ioutil.ReadFileと手動ファイル読み込み: Goのio/ioutilパッケージ(Go 1.16以降はioosパッケージに統合)には、ファイルを簡単に読み込むためのReadFile関数があります。しかし、このコミットの時点では、timeパッケージがio/ioutilosパッケージに直接依存するのを避けるため、readFile関数が手動で実装されていました。これは、標準ライブラリのコア部分における依存関係を最小限に抑えるための設計判断です。
  • GoのMakefile: Goプロジェクトでは、ビルドプロセスを自動化するためにMakefileが使用されることがあります。GOFILES変数は、コンパイル対象となるGoソースファイルを指定するために使われます。
  • Goのruntimeパッケージ: Goプログラムの実行環境(ガベージコレクション、スケジューラ、システムコールインターフェースなど)を提供するGo言語のコア部分です。

技術的詳細

このコミットの技術的な詳細は以下の通りです。

  1. readFile関数のプラットフォーム固有化:

    • 既存のsrc/pkg/time/sys.goファイルが削除されました。このファイルには、汎用的なreadFile関数が含まれていましたが、syscall.Openを3引数で呼び出していました。
    • readFile関数の実装が、Plan 9固有のsrc/pkg/time/sys_plan9.goと、Unix系OS固有のsrc/pkg/time/sys_unix.goにそれぞれ移動されました。
    • sys_plan9.go内のreadFilesyscall.Open(name, syscall.O_RDONLY)のように2引数でsyscall.Openを呼び出します。
    • sys_unix.go内のreadFilesyscall.Open(name, syscall.O_RDONLY, 0)のように3引数でsyscall.Openを呼び出します。
    • これらのファイルはそれぞれ// +build plan9および// +build unixビルドタグによって、対応するOSでのみコンパイルされるようになっています。
    • readFileioutil.ReadFileの簡易的な再実装であり、timeパッケージがio/ioutilosに依存しないようにするためのものです。
  2. timeパッケージのMakefileの更新:

    • src/pkg/time/Makefileから、削除されたsys.goGOFILESリストから除外されました。
  3. Sleep関数の宣言の移動:

    • Sleep関数の宣言がsys.goからsrc/pkg/time/sleep.goに移動されました。これにより、Sleepの宣言がプラットフォーム固有のファイルではなく、汎用的なファイルに存在することになります。
  4. Plan 9向け時間帯情報処理の改善 (zoneinfo_plan9.go):

    • src/pkg/time/zoneinfo_plan9.goが大幅に修正されました。
    • strings.Fields関数がtimeパッケージ内にコピーされ、fieldsという名前で再実装されました。これは、timeパッケージがstringsパッケージに依存するのを避けるためと考えられます。標準ライブラリのコアパッケージは、依存関係を最小限に抑える傾向があります。
    • parseZones関数がloadZoneDataにリファクタリングされ、エラーハンドリングが強化されました。
    • loadZoneFile関数が追加され、readFileを使用して時間帯情報ファイルを読み込むロジックがカプセル化されました。
    • initLocal関数が更新され、環境変数timezoneの読み込みや、/adm/timezone/localファイルの読み込みにloadZoneDataおよびloadZoneFileを使用するようになりました。
    • initTestingZone関数も同様にloadZoneFileを使用するように変更されました。
    • loadLocation関数が追加され、指定された名前の時間帯情報を/adm/timezone/ディレクトリから読み込む機能が提供されました。
    • これらの変更により、Plan 9環境での時間帯情報の読み込みと解析がより堅牢になり、新しいreadFileの分離された実装と連携するようになりました。
  5. Plan 9ランタイムにおけるtime·nowの追加 (runtime/plan9/thread.c):

    • src/pkg/runtime/plan9/thread.ctime·nowというC言語関数が追加されました。これは、Goのtimeパッケージが現在時刻を取得するために、Plan 9のランタイムからナノ秒単位の時刻を取得するためのインターフェースを提供します。runtime·nanotime()を呼び出してナノ秒を取得し、それを秒とナノ秒に分解してGo側に渡す役割を担います。

これらの変更は、Go言語がPlan 9という特定のOS環境で、そのOSのAPI仕様に厳密に準拠しつつ、timeパッケージの機能を提供するための重要なステップです。

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

src/pkg/time/sys.go (削除)

--- a/src/pkg/time/sys.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 time
-
-import "syscall"
-
-// Sleep pauses the current goroutine for the duration d.
-func Sleep(d Duration)
-
-// readFile reads and returns the content of the named file.
-// It is a trivial implementation of ioutil.ReadFile, reimplemented
-// here to avoid depending on io/ioutil or os.
-func readFile(name string) ([]byte, error) {
-	f, err := syscall.Open(name, syscall.O_RDONLY, 0)
-	if err != nil {
-		return nil, err
-	}
-	defer syscall.Close(f)
-	var (
-		buf [4096]byte
-		ret []byte
-		n   int
-	)
-	for {
-		n, err = syscall.Read(f, buf[:])
-		if n > 0 {
-			ret = append(ret, buf[:n]...)
-		}
-		if n == 0 || err != nil {
-			break
-		}
-	}
-	return ret, err
-}

src/pkg/time/sys_plan9.go (新規/変更)

--- a/src/pkg/time/sys_plan9.go
+++ b/src/pkg/time/sys_plan9.go
@@ -2,9 +2,39 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// +build plan9
+
 package time
 
+import "syscall"
+
 // for testing: whatever interrupts a sleep
 func interrupt() {
 	// cannot predict pid, don't want to kill group
 }
+
+// readFile reads and returns the content of the named file.
+// It is a trivial implementation of ioutil.ReadFile, reimplemented
+// here to avoid depending on io/ioutil or os.
+func readFile(name string) ([]byte, error) {
+	f, err := syscall.Open(name, syscall.O_RDONLY) // Plan 9 specific: 2 arguments
+	if err != nil {
+		return nil, err
+	}
+	defer syscall.Close(f)
+	var (
+		buf [4096]byte
+		ret []byte
+		n   int
+	)
+	for {
+		n, err = syscall.Read(f, buf[:])
+		if n > 0 {
+			ret = append(ret, buf[:n]...)
+		}
+		if n == 0 || err != nil {
+			break
+		}
+	}
+	return ret, err
+}

src/pkg/time/sys_unix.go (変更)

--- a/src/pkg/time/sys_unix.go
+++ b/src/pkg/time/sys_unix.go
@@ -12,3 +12,29 @@ import "syscall"
 func interrupt() {
 	syscall.Kill(syscall.Getpid(), syscall.SIGCHLD)
 }
+
+// readFile reads and returns the content of the named file.
+// It is a trivial implementation of ioutil.ReadFile, reimplemented
+// here to avoid depending on io/ioutil or os.
+func readFile(name string) ([]byte, error) {
+	f, err := syscall.Open(name, syscall.O_RDONLY, 0) // Unix specific: 3 arguments
+	if err != nil {
+		return nil, err
+	}
+	defer syscall.Close(f)
+	var (
+		buf [4096]byte
+		ret []byte
+		n   int
+	)
+	for {
+		n, err = syscall.Read(f, buf[:])
+		if n > 0 {
+			ret = append(ret, buf[:n]...)
+		}
+		if n == 0 || err != nil {
+			break
+		}
+	}
+	return ret, err
+}

src/pkg/time/zoneinfo_plan9.go (大幅変更)

--- a/src/pkg/time/zoneinfo_plan9.go
+++ b/src/pkg/time/zoneinfo_plan9.go
@@ -6,61 +6,152 @@
 
 package time
 
-//import (\n-//\t\"strconv\"\n-//\t\"strings\"\n-//)\n+import (\n+\t\"errors\"\n+\t\"syscall\"\n+)\n 
-func parseZones(s string) (zt []zonetime) {\n-\tf := strings.Fields(s)\n+var badData = errors.New("malformed time zone information")\n+
+func isSpace(r rune) bool {\n+\treturn r == ' ' || r == '\t' || r == '\n'\n+}\n+
+// Copied from strings to avoid a dependency.
+func fields(s string) []string {\n+\t// First count the fields.\n+\tn := 0\n+\tinField := false\n+\tfor _, rune := range s {\n+\t\twasInField := inField\n+\t\tinField = !isSpace(rune)\n+\t\tif inField && !wasInField {\n+\t\t\tn++\n+\t\t}\n+\t}\n+\n+\t// Now create them.\n+\ta := make([]string, n)\n+\tna := 0\n+\tfieldStart := -1 // Set to -1 when looking for start of field.\n+\tfor i, rune := range s {\n+\t\tif isSpace(rune) {\n+\t\t\tif fieldStart >= 0 {\n+\t\t\t\ta[na] = s[fieldStart:i]\n+\t\t\t\tna++\n+\t\t\t\tfieldStart = -1\n+\t\t\t}\n+\t\t} else if fieldStart == -1 {\n+\t\t\tfieldStart = i\n+\t\t}\n+\t}\n+\tif fieldStart >= 0 { // Last field might end at EOF.\n+\t\ta[na] = s[fieldStart:]\n+\t}\n+\treturn a\n+}\n+
+func loadZoneData(s string) (l *Location, err error) {\n+\tf := fields(s)\n \tif len(f) < 4 {\n-\t\treturn\n+\t\tif len(f) == 2 && f[0] == "GMT" {\n+\t\t\treturn UTC, nil\n+\t\t}\n+\t\treturn nil, badData\n \t}\n \n+\tvar zones [2]zone\n+\n \t// standard timezone offset\n-\to, err := strconv.Atoi(f[1])\n+\to, err := atoi(f[1])\n \tif err != nil {\n-\t\treturn\n+\t\treturn nil, badData\n \t}\n-\tstd := &zone{name: f[0], utcoff: o, isdst: false}\n+\tzones[0] = zone{name: f[0], offset: o, isDST: false}\n \n \t// alternate timezone offset\n-\to, err = strconv.Atoi(f[3])\n+\to, err = atoi(f[3])\n \tif err != nil {\n-\t\treturn\n+\t\treturn nil, badData\n \t}\n-\tdst := &zone{name: f[2], utcoff: o, isdst: true}\n+\tzones[1] = zone{name: f[2], offset: o, isDST: true}\n \n \t// transition time pairs\n+\tvar tx []zoneTrans\n \tf = f[4:]\n \tfor i := 0; i < len(f); i++ {\n-\t\tz := std\n+\t\tzi := 0\n \t\tif i%2 == 0 {\n-\t\t\tz = dst\n+\t\t\tzi = 1\n \t\t}\n-\t\tt, err := strconv.Atoi(f[i])\n+\t\tt, err := atoi(f[i])\n \t\tif err != nil {\n-\t\t\treturn nil\n+\t\t\treturn nil, badData\n \t\t}\n-\t\tt -= std.utcoff\n-\t\tzt = append(zt, zonetime{time: int32(t), zone: z})\n+\t\tt -= zones[0].offset\n+\t\ttx = append(tx, zoneTrans{when: int64(t), index: uint8(zi)})\n \t}\n-\treturn\n+\n+\t// Committed to succeed.\n+\tl = &Location{zone: zones[:], tx: tx}\n+\n+\t// Fill in the cache with information about right now,\n+\t// since that will be the most common lookup.\n+\tsec, _ := now()\n+\tfor i := range tx {\n+\t\tif tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {\n+\t\t\tl.cacheStart = tx[i].when\n+\t\t\tl.cacheEnd = 1<<63 - 1\n+\t\t\tif i+1 < len(tx) {\n+\t\t\t\tl.cacheEnd = tx[i+1].when\n+\t\t\t}\n+\t\t\tl.cacheZone = &l.zone[tx[i].index]\n+\t\t}\n+\t}\n+\n+\treturn l, nil\n }\n \n-func initLocal() {\n-\tt, err := os.Getenverror("timezone")\n+func loadZoneFile(name string) (*Location, error) {\n+\tb, err := readFile(name)\n \tif err != nil {\n-\t\t// do nothing: use UTC\n-\t\treturn\n+\t\treturn nil, err\n \t}\n-\tzones = parseZones(t)\n+\treturn loadZoneData(string(b))\n }\n \n func initTestingZone() {\n-\tbuf, err := readFile("/adm/timezone/US_Pacific")\n-\tif err != nil {\n+\tif z, err := loadZoneFile("/adm/timezone/US_Pacific"); err == nil {\n+\t\tlocalLoc = *z\n \t\treturn\n \t}\n-\tzones = parseZones(string(buf))\n+\n+\t// Fall back to UTC.\n+\tlocalLoc.name = "UTC"\n+}\n+\n+func initLocal() {\n+\tt, ok := syscall.Getenv("timezone")\n+\tif ok {\n+\t\tif z, err := loadZoneData(t); err == nil {\n+\t\t\tlocalLoc = *z\n+\t\t\treturn\n+\t\t}\n+\t} else {\n+\t\tif z, err := loadZoneFile("/adm/timezone/local"); err == nil {\n+\t\t\tlocalLoc = *z\n+\t\t\tlocalLoc.name = "Local"\n+\t\t\treturn\n+\t\t}\n+\t}\n+\n+\t// Fall back to UTC.\n+\tlocalLoc.name = "UTC"\n+}\n+\n+func loadLocation(name string) (*Location, error) {\n+\tif z, err := loadZoneFile("/adm/timezone/" + name); err == nil {\n+\t\treturn z, nil\n+\t}\n+\treturn nil, errors.New("unknown time zone " + name)\n }\n```

## コアとなるコードの解説

このコミットの核となる変更は、`readFile`関数のプラットフォーム固有の実装への分離と、それに伴う`time`パッケージのPlan 9向け時間帯情報処理のロジックの刷新です。

1.  **`readFile`の分離**:
    *   元の`src/pkg/time/sys.go`にあった`readFile`は、`syscall.Open`を3つの引数(ファイル名、フラグ、パーミッション)で呼び出していました。これはUnix系OSでは一般的ですが、Plan 9の`syscall.Open`は2つの引数しか取りません。
    *   この問題を解決するため、`readFile`は`src/pkg/time/sys_plan9.go`と`src/pkg/time/sys_unix.go`に分割されました。
    *   `sys_plan9.go`の`readFile`は、`syscall.Open(name, syscall.O_RDONLY)`のように2引数で呼び出し、Plan 9のAPIに適合させます。
    *   `sys_unix.go`の`readFile`は、`syscall.Open(name, syscall.O_RDONLY, 0)`のように3引数で呼び出し、Unix系OSのAPIに適合させます。
    *   これらのファイルはGoのビルドタグ(`+build plan9`と`+build unix`)によって、それぞれのOSでのみコンパイルされるため、クロスプラットフォームでの互換性が保たれます。

2.  **`zoneinfo_plan9.go`のロジック変更**:
    *   このファイルは、Plan 9環境での時間帯情報の読み込みと解析を担当します。
    *   最も注目すべきは、`strings.Fields`関数のロジックが`fields`という名前でこのファイル内にコピーされたことです。これは、`time`パッケージが`strings`パッケージに依存するのを避けるための典型的なGo標準ライブラリの設計パターンです。これにより、`time`パッケージの独立性が高まります。
    *   時間帯データを解析する`parseZones`関数は`loadZoneData`に名前が変更され、より堅牢なエラーハンドリングと、`Location`構造体への直接的なデータ格納ロジックが導入されました。
    *   `loadZoneFile`関数が追加され、ファイルから時間帯データを読み込む共通のロジックを提供します。これは、`readFile`の新しいプラットフォーム固有の実装を利用します。
    *   `initLocal`関数は、環境変数`timezone`または`/adm/timezone/local`ファイルからローカルの時間帯情報を読み込む際に、これらの新しい`loadZoneData`や`loadZoneFile`関数を使用するように更新されました。これにより、Plan 9環境での時間帯設定の取得がより正確かつ堅牢になります。
    *   `loadLocation`関数は、指定された名前の時間帯情報を`/adm/timezone/`ディレクトリから動的に読み込む機能を提供し、時間帯データベースの利用を可能にします。

これらの変更により、Goの`time`パッケージはPlan 9環境において、そのOSのシステムコールAPIの特性に適切に対応し、時間帯情報の処理を正確に行えるようになりました。

## 関連リンク

*   Go言語の公式ドキュメント: [https://golang.org/](https://golang.org/)
*   Go言語の`syscall`パッケージ: [https://pkg.go.dev/syscall](https://pkg.go.dev/syscall)
*   Go言語の`time`パッケージ: [https://pkg.go.dev/time](https://pkg.go.dev/time)
*   Plan 9 from Bell Labs: [https://9p.io/plan9/](https://9p.io/plan9/)

## 参考にした情報源リンク

*   Go言語のソースコード(GitHubリポジトリ): [https://github.com/golang/go](https://github.com/golang/go)
*   Go言語のコードレビューシステム (Gerrit): [https://go-review.googlesource.com/](https://go-review.googlesource.com/) (コミットメッセージに記載されている`https://golang.org/cl/5447061`は、このGerritの変更リストへのリンクです。)
*   Goのビルドタグに関するドキュメント: [https://pkg.go.dev/cmd/go#hdr-Build_constraints](https://pkg.go.dev/cmd/go#hdr-Build_constraints)
*   Plan 9のシステムコールに関する情報 (一般的な情報源): Plan 9の公式ドキュメントや関連する学術論文、コミュニティの議論など。