[インデックス 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.go
とsys_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以降はio
とos
パッケージに統合)には、ファイルを簡単に読み込むためのReadFile
関数があります。しかし、このコミットの時点では、time
パッケージがio/ioutil
やos
パッケージに直接依存するのを避けるため、readFile
関数が手動で実装されていました。これは、標準ライブラリのコア部分における依存関係を最小限に抑えるための設計判断です。- Goの
Makefile
: Goプロジェクトでは、ビルドプロセスを自動化するためにMakefile
が使用されることがあります。GOFILES
変数は、コンパイル対象となるGoソースファイルを指定するために使われます。 - Goの
runtime
パッケージ: Goプログラムの実行環境(ガベージコレクション、スケジューラ、システムコールインターフェースなど)を提供するGo言語のコア部分です。
技術的詳細
このコミットの技術的な詳細は以下の通りです。
-
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
内のreadFile
はsyscall.Open(name, syscall.O_RDONLY)
のように2引数でsyscall.Open
を呼び出します。sys_unix.go
内のreadFile
はsyscall.Open(name, syscall.O_RDONLY, 0)
のように3引数でsyscall.Open
を呼び出します。- これらのファイルはそれぞれ
// +build plan9
および// +build unix
ビルドタグによって、対応するOSでのみコンパイルされるようになっています。 readFile
はioutil.ReadFile
の簡易的な再実装であり、time
パッケージがio/ioutil
やos
に依存しないようにするためのものです。
- 既存の
-
time
パッケージのMakefile
の更新:src/pkg/time/Makefile
から、削除されたsys.go
がGOFILES
リストから除外されました。
-
Sleep
関数の宣言の移動:Sleep
関数の宣言がsys.go
からsrc/pkg/time/sleep.go
に移動されました。これにより、Sleep
の宣言がプラットフォーム固有のファイルではなく、汎用的なファイルに存在することになります。
-
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
の分離された実装と連携するようになりました。
-
Plan 9ランタイムにおける
time·now
の追加 (runtime/plan9/thread.c
):src/pkg/runtime/plan9/thread.c
にtime·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の公式ドキュメントや関連する学術論文、コミュニティの議論など。