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

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

このコミットは、Go言語の標準ライブラリにおけるファイルシステム操作とシステムコールインターフェースの重要な改善を導入しています。主な目的は、statシステムコール(ファイルやディレクトリのメタデータ取得)のクロスプラットフォーム対応を強化し、ファイルディスクリプタ(os.FD)に名前フィールドを追加することで、より堅牢で使いやすいAPIを提供することです。また、システムコールにおける文字列の扱い方を根本的に変更し、より効率的で安全な方法に移行しています。

コミット

commit 704bc9d5c95858fd43151f19e7e4de6c99142f1c
Author: Rob Pike <r@golang.org>
Date:   Fri Feb 6 17:54:26 2009 -0800

    portable stat for os
    add name to os.FD
    clean up some interfaces
    
    R=rsc
    DELTA=318  (231 added, 44 deleted, 43 changed)
    OCL=24624
    CL=24627
---
 src/lib/io/io.go                | 30 +++++++---------\
 src/lib/net/fd.go               | 10 +++---\
 src/lib/os/Makefile             | 22 ++++++++----\
 src/lib/os/os_file.go           | 66 +++++++++++++++++++++++++---------\
 src/lib/os/os_test.go           | 79 +++++++++++++++++++++++++++++++++++++++++
 src/lib/os/os_types.go          | 26 ++++++++++++++\
 src/lib/os/stat_amd64_darwin.go | 34 ++++++++++++++++++\
 src/lib/os/stat_amd64_linux.go  | 34 ++++++++++++++++++\
 src/lib/syscall/file_linux.go   | 40 +++++++--------------
 src/lib/syscall/syscall.go      | 11 +++---\
 src/lib/utf8_test.go            | 11 +++---\
 11 files changed, 277 insertions(+), 86 deletions(-)

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

https://github.com/golang/go/commit/704bc9d5c95858fd43151f19e7e4de6c99142f1c

元コミット内容

portable stat for os
add name to os.FD
clean up some interfaces

変更の背景

このコミットは、Go言語の非常に初期の段階(2009年2月)に行われたもので、Goがまだオープンソース化されて間もない頃の基盤的な改善を反映しています。当時のGoは、異なるオペレーティングシステム(特にLinuxとDarwin/macOS)上での動作を安定させ、標準ライブラリのAPIを洗練させる途上にありました。

主な変更の背景には以下の点が挙げられます。

  1. クロスプラットフォーム対応の強化: statシステムコールは、ファイルやディレクトリに関する詳細な情報(サイズ、パーミッション、タイムスタンプなど)を取得するために不可欠です。しかし、その構造体(stat_t)やフィールドはOSによって異なります。Goが真にポータブルな言語となるためには、これらのOS固有の差異を抽象化し、統一されたインターフェースを提供する必要がありました。このコミットでは、os.DirというOS非依存の構造体を導入し、各OS(amd64/darwin, amd64/linux)向けのstat実装でこれを埋めることで、この課題に対処しています。

  2. ファイルディスクリプタの利便性向上: os.FDはファイルディスクリプタをラップするGoの型です。これまでの実装では、ファイルディスクリプタの数値(Fd)しか持っていませんでした。しかし、デバッグやロギング、あるいは特定の操作において、そのファイルディスクリプタがどのファイル名に対応しているかを知ることは非常に有用です。nameフィールドの追加は、この利便性を向上させ、APIの使いやすさを改善することを目的としています。

  3. システムコールにおける文字列処理の改善: 従来のGoのシステムコールでは、Goの文字列をC言語スタイルのNULL終端バイト配列に変換するためにsyscall.StringToBytesのような関数を使用していました。この方法は、固定サイズのバッファを必要とし、バッファオーバーフローのリスクや、文字列長がバッファサイズを超える場合の扱いに問題がありました。より効率的で安全なsyscall.StringBytePtrへの移行は、システムコール層の堅牢性を高めるための重要なリファクタリングです。これは、Goの文字列が内部的にUTF-8でエンコードされており、C言語のAPIと連携する際に適切な変換が必要となるため、特に重要でした。

これらの変更は、Go言語が多様な環境で安定して動作し、開発者が低レベルのOSインターフェースを意識することなく、高レベルなファイルシステム操作を行えるようにするための基盤を築くものでした。

前提知識の解説

このコミットを理解するためには、以下の概念についての基本的な知識が必要です。

  1. ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数です。プログラムはファイル名ではなく、このFDを使ってI/O操作を行います。標準入力(stdin)は0、標準出力(stdout)は1、標準エラー出力(stderr)は2というFDが予約されています。

  2. stat システムコール: Unix系OSで提供されるシステムコールの一つで、指定されたファイルやディレクトリのメタデータ(ファイルの種類、サイズ、パーミッション、所有者、最終アクセス/変更時刻など)を取得するために使用されます。statはパス名を引数にとり、fstatはファイルディスクリプタを引数にとり、lstatはシンボリックリンク自体を対象とします(statはリンク先のファイルを対象とする)。これらのシステムコールは、通常、stat_tという構造体に結果を格納します。

  3. システムコール (Syscall): オペレーティングシステムが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイルI/O、メモリ管理、プロセス制御など、OSのカーネルが管理するリソースへのアクセスは、システムコールを介して行われます。Go言語では、syscallパッケージがこれらの低レベルなインターフェースを提供します。

  4. Go言語のosパッケージ: Goの標準ライブラリの一部で、OSに依存しないインターフェースを提供し、ファイルシステム操作、プロセス管理、環境変数へのアクセスなどを可能にします。このパッケージは、内部的にOS固有のシステムコールを呼び出すことで、クロスプラットフォームな抽象化を実現しています。

  5. Go言語のioパッケージ: Goの標準ライブラリの一部で、I/Oプリミティブ(Reader, Writerインターフェースなど)を提供します。ファイルやネットワーク接続など、様々なI/Oソースとシンクに対して統一的なインターフェースを提供します。

  6. Go言語の文字列とC言語の文字列: Go言語の文字列は、不変のバイトスライスであり、UTF-8でエンコードされています。文字列の長さはバイト数で管理され、NULL終端ではありません。一方、C言語の文字列は、文字の配列であり、文字列の終端をNULL文字(\0)で示します。システムコールは通常C言語のAPIとして提供されるため、Goの文字列をシステムコールに渡す際には、C言語の文字列形式に変換する必要があります。

  7. Makefile: ソフトウェアのビルドプロセスを自動化するためのツールであるmakeが使用する設定ファイルです。依存関係に基づいてコマンドを実行し、ソースコードのコンパイル、リンク、テストなどのタスクを効率的に管理します。このコミットでは、新しいソースファイル(os_types.go, stat_amd64_darwin.go, stat_amd64_linux.go)をビルドプロセスに含めるためにMakefileが更新されています。

技術的詳細

このコミットにおける技術的な変更は多岐にわたりますが、特に以下の点が重要です。

  1. os.FD構造体の変更とメソッドの追加:

    • src/lib/os/os_file.goにおいて、os.FD構造体にプライベートフィールドfd int64name stringが追加されました。これにより、ファイルディスクリプタの数値だけでなく、そのファイルディスクリプタが関連付けられているファイル名も保持できるようになりました。
    • Fd() int64Name() stringというパブリックなメソッドが追加され、FDオブジェクトからこれらの情報にアクセスできるようになりました。これにより、FDの内部実装が隠蔽され、よりクリーンなAPIが提供されます。
    • NewFD関数もname引数を取るように変更され、Stdin, Stdout, Stderrといった標準FDも初期化時に適切な名前を持つようになりました(例: NewFD(0, "/dev/stdin"))。
    • os.Open, os.Pipeなどの関数も、NewFDの新しいシグネチャに合わせて更新されています。
  2. stat関連関数の導入とOS依存実装の分離:

    • src/lib/os/os_types.goが新規作成され、DirというOS非依存の構造体が定義されました。この構造体は、statシステムコールが返すファイルメタデータ(デバイスID、inode番号、リンク数、モード、サイズ、タイムスタンプなど)を抽象化します。
    • src/lib/os/os_file.goStat, Fstat, Lstatという新しい関数が追加されました。これらの関数は、それぞれパス名、ファイルディスクリプタ、シンボリックリンクのパス名を受け取り、os.Dir構造体を返します。
    • src/lib/os/stat_amd64_darwin.gosrc/lib/os/stat_amd64_linux.goが新規作成されました。これらは、それぞれのOS(DarwinとLinux)におけるsyscall.Stat_t構造体からos.Dir構造体への変換を行うdirFromStat関数を実装しています。これにより、OS固有のstat構造体の差異がGoのos.Dirによって吸収され、クロスプラットフォームなstat操作が可能になります。
  3. システムコールにおける文字列処理の変更:

    • src/lib/syscall/syscall.goにおいて、StringToBytes関数が削除され、代わりにStringBytePtr関数が導入されました。
      • StringToBytesは、Goの文字列を固定サイズのバイト配列にコピーし、NULL終端を追加するものでした。これはバッファサイズの問題や、len(s) >= len(b)の場合にfalseを返すという扱いにくさがありました。
      • StringBytePtrは、Goの文字列を新しいバイトスライスにコピーし、NULL終端を追加した上で、そのバイトスライスの先頭要素へのポインタ(*byte)を返します。これにより、システムコールに直接渡せるC言語スタイルの文字列ポインタを、より安全かつ柔軟に生成できるようになりました。
    • src/lib/syscall/file_linux.go内のOpen, Creat, Stat, Lstat, Unlink, Mkdirといったファイルシステム関連のシステムコールラッパーが、StringToBytesの代わりに新しく導入されたStringBytePtrを使用するように変更されました。これにより、システムコールへの文字列引数の渡し方が統一され、コードの簡潔性と堅牢性が向上しています。
  4. ビルドシステムの更新:

    • src/lib/os/Makefileが更新され、新しいos_types.gostat_amd64_darwin.gostat_amd64_linux.goファイルがビルドプロセスに含まれるようになりました。特に、stat_$(GOARCH)_$(GOOS).$Oというパターンを使用することで、ビルドターゲットのアーキテクチャとOSに基づいて適切なstat実装がリンクされるように設定されています。これは、Goのクロスコンパイルとポータビリティをサポートするための典型的なアプローチです。

これらの変更は、Go言語のファイルシステム抽象化層を大幅に改善し、OS間の差異を吸収しながら、より表現力豊かで安全なAPIを提供するための重要なステップでした。

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

src/lib/os/os_file.go における FD 構造体と関連関数の変更

--- a/src/lib/os/os_file.go
+++ b/src/lib/os/os_file.go
@@ -9,20 +9,29 @@ import os "os"
 
 // FDs are wrappers for file descriptors
 type FD struct {
-	Fd int64
+	fd int64;
+	name	string;
 }
 
-func NewFD(fd int64) *FD {
+func (fd *FD) Fd() int64 {
+	return fd.fd
+}
+
+func (fd *FD) Name() string {
+	return fd.name
+}
+
+func NewFD(fd int64, name string) *FD {
 	if fd < 0 {
 		return nil
 	}
-	return &FD{fd}
+	return &FD{fd, name}
 }
 
 var (
-	Stdin = NewFD(0);
-	Stdout = NewFD(1);
-	Stderr = NewFD(2);
+	Stdin = NewFD(0, "/dev/stdin");
+	Stdout = NewFD(1, "/dev/stdout");
+	Stderr = NewFD(2, "/dev/stderr");
 )
 
 const (
@@ -41,15 +50,15 @@ const (
 
 func Open(name string, mode int, flags int) (fd *FD, err *Error) {
 	r, e := syscall.Open(name, int64(mode), int64(flags));
-	return NewFD(r), ErrnoToError(e)
+	return NewFD(r, name), ErrnoToError(e)
 }
 
 func (fd *FD) Close() *Error {
 	if fd == nil {
 		return EINVAL
 	}
-	r, e := syscall.Close(fd.Fd);
-	fd.Fd = -1;  // so it can't be closed again
+	r, e := syscall.Close(fd.fd);
+	fd.fd = -1;  // so it can't be closed again
 	return ErrnoToError(e)
 }
 
@@ -59,7 +68,7 @@ func (fd *FD) Read(b []byte) (ret int, err *Error) {
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Read(fd.Fd, &b[0], int64(len(b)));
+		r, e = syscall.Read(fd.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -73,7 +82,7 @@ func (fd *FD) Write(b []byte) (ret int, err *Error) {
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Write(fd.Fd, &b[0], int64(len(b)));
+		r, e = syscall.Write(fd.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -85,11 +94,7 @@ func (fd *FD) WriteString(s string) (ret int, err *Error) {\n 	if fd == nil {\n 		return 0, EINVAL
 	}
-	b := make([]byte, len(s)+1);
-	if !syscall.StringToBytes(b, s) {
-		return 0, EINVAL
-	}
-	r, e := syscall.Write(fd.Fd, &b[0], int64(len(s)));
+	r, e := syscall.Write(fd.fd, syscall.StringBytePtr(s), int64(len(s)));
 	if r < 0 {
 		r = 0
 	}
@@ -102,10 +107,37 @@ func Pipe() (fd1 *FD, fd2 *FD, err *Error) {\
 	if e != 0 {
 		return nil, nil, ErrnoToError(e)
 	}
-	return NewFD(p[0]), NewFD(p[1]), nil
+	return NewFD(p[0], "|0"), NewFD(p[1], "|1"), nil
 }
 
 func Mkdir(name string, perm int) *Error {
 	r, e := syscall.Mkdir(name, int64(perm));
 	return ErrnoToError(e)
 }
+
+func Stat(name string) (dir *Dir, err *Error) {
+	stat := new(syscall.Stat_t);
+	r, e := syscall.Stat(name, stat);
+	if e != 0 {
+		return nil, ErrnoToError(e)
+	}
+	return dirFromStat(name, new(Dir), stat), nil
+}
+
+func Fstat(fd *FD) (dir *Dir, err *Error) {
+	stat := new(syscall.Stat_t);
+	r, e := syscall.Fstat(fd.fd, stat);
+	if e != 0 {
+		return nil, ErrnoToError(e)
+	}
+	return dirFromStat(fd.name, new(Dir), stat), nil
+}
+
+func Lstat(name string) (dir *Dir, err *Error) {
+	stat := new(syscall.Stat_t);
+	r, e := syscall.Lstat(name, stat);
+	if e != 0 {
+		return nil, ErrnoToError(e)
+	}
+	return dirFromStat(name, new(Dir), stat), nil
+}

src/lib/syscall/syscall.go における StringBytePtr の導入

--- a/src/lib/syscall/syscall.go
+++ b/src/lib/syscall/syscall.go
@@ -16,13 +16,10 @@ func RawSyscall(trap int64, a1, a2, a3 int64) (r1, r2, err int64);
  * Used to convert file names to byte arrays for passing to kernel,
  * but useful elsewhere too.
  */
-func StringToBytes(b []byte, s string) bool {
-	if len(s) >= len(b) {
-		return false
-	}
+func StringBytePtr(s string) *byte {
+	a := make([]byte, len(s)+1);
 	for i := 0; i < len(s); i++ {
-		b[i] = s[i]
+		a[i] = s[i];
 	}
-	b[len(s)] = '\000';	// not necessary - memory is zeroed - but be explicit
-	return true
+	return &a[0];
 }

src/lib/os/os_types.go (新規ファイル)

// 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

// An operating-system independent representation of Unix data structures.
// OS-specific routines in this directory convert the OS-local versions to these.

// Result of stat64(2) etc.
type Dir struct {
	Dev	uint64;
	Ino	uint64;
	Nlink	uint64;
	Mode	uint32;
	Uid	uint32;
	Gid	uint32;
	Rdev	uint64;
	Size	uint64;
	Blksize	uint64;
	Blocks	uint64;
	Atime_ns	uint64;	// nanoseconds since 1970
	Mtime_ns	uint64;	// nanoseconds since 1970
	Ctime_ns	uint64;	// nanoseconds since 1970
	Name	string;
}

src/lib/os/stat_amd64_linux.go (新規ファイル、stat_amd64_darwin.goも同様)

// 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.

// AMD64, Linux

package os

import syscall "syscall"
import os "os"

func dirFromStat(name string, dir *Dir, stat *syscall.Stat_t) *Dir {
	dir.Dev = stat.Dev;
	dir.Ino = stat.Ino;
	dir.Nlink = stat.Nlink;
	dir.Mode = stat.Mode;
	dir.Uid = stat.Uid;
	dir.Gid = stat.Gid;
	dir.Rdev = stat.Rdev;
	dir.Size = uint64(stat.Size);
	dir.Blksize = uint64(stat.Blksize);
	dir.Blocks = uint64(stat.Blocks);
	dir.Atime_ns = uint64(stat.Atime.Sec) * 1e9 + stat.Atime.Nsec;
	dir.Mtime_ns = uint64(stat.Mtime.Sec) * 1e9 + stat.Mtime.Nsec;
	dir.Ctime_ns = uint64(stat.Ctime.Sec) * 1e9 + stat.Atime.Nsec;
	for i := len(name) - 1; i >= 0; i-- {
		if name[i] == '/' {
			name = name[i+1:len(name)];
			break;
		}
	}
	dir.Name = name;
	return dir;
}

コアとなるコードの解説

os.FD 構造体と関連関数の変更 (src/lib/os/os_file.go)

  • FD 構造体の変更: 以前はFd int64のみを持っていたFD構造体に、fd int64(プライベート化)とname stringが追加されました。これにより、ファイルディスクリプタの数値だけでなく、そのファイルがオープンされた際のパス名もFDオブジェクトに紐付けられるようになりました。fdフィールドがプライベートになったことで、外部からは直接アクセスできなくなり、カプセル化が強化されています。
  • Fd()Name() メソッドの追加: fdnameフィールドへのアクセスを提供するために、それぞれFd()Name()というゲッターメソッドが追加されました。これにより、FDの内部表現に依存せずに、これらの情報を取得できるようになります。
  • NewFD 関数の変更: NewFD関数は、ファイルディスクリプタの数値だけでなく、そのファイルディスクリプタに関連するname(通常はファイルパス)も引数として受け取るようになりました。これにより、FDオブジェクトがより完全な情報を持つことができるようになります。
  • 標準FDの初期化: Stdin, Stdout, StderrといったGoの標準ファイルディスクリプタも、新しいNewFDのシグネチャに合わせて、それぞれ/dev/stdin, /dev/stdout, /dev/stderrという名前で初期化されるようになりました。これは、これらのFDがどのリソースに対応しているかを明確にするのに役立ちます。
  • Open, Close, Read, Write, WriteString, Pipe の変更: これらの関数やメソッドは、FD構造体の変更に合わせて、内部でfd.fd(プライベートなフィールド)を使用するように修正されました。特にWriteStringでは、後述のsyscall.StringBytePtrが使用されるようになり、文字列のシステムコールへの渡し方が改善されています。Pipe関数も、生成されるパイプのFDに|0|1といった名前を付与するようになりました。

syscall.StringBytePtr の導入 (src/lib/syscall/syscall.go)

  • StringToBytes の削除と StringBytePtr の導入: 以前のStringToBytes関数は、Goの文字列をC言語のNULL終端バイト配列に変換する際に、固定サイズのバッファを必要とし、バッファオーバーフローの可能性や、文字列が長すぎる場合の扱いに問題がありました。
  • 新しく導入されたStringBytePtr関数は、Goの文字列sを受け取り、その内容を新しいバイトスライスaにコピーし、最後にNULL終端を追加します。そして、そのバイトスライスaの先頭要素へのポインタ(*byte)を返します。
  • このアプローチにより、呼び出し側は固定サイズのバッファを事前に用意する必要がなくなり、Goの文字列の長さに応じて動的にメモリが確保されるため、より安全で柔軟な文字列変換が可能になります。システムコールは通常、C言語の関数として定義されており、文字列引数としてchar*(バイトポインタ)を期待するため、この*byte型はシステムコールに直接渡すのに適しています。

os.Dir 構造体 (src/lib/os/os_types.go)

  • このファイルで定義されたDir構造体は、OSに依存しないファイルやディレクトリのメタデータ表現です。Unix系OSのstat_t構造体から主要なフィールド(デバイスID、inode番号、リンク数、モード、UID、GID、サイズ、ブロックサイズ、ブロック数、アクセス/変更/作成タイムスタンプ、名前)を抽出し、Goの型にマッピングしています。
  • この抽象化層により、Goのアプリケーションは、基盤となるOSがLinuxであろうとDarwinであろうと、統一されたDir構造体を通じてファイルメタデータにアクセスできるようになります。

OS固有の dirFromStat 関数 (src/lib/os/stat_amd64_linux.go など)

  • stat_amd64_linux.gostat_amd64_darwin.go(および他のOS/アーキテクチャ固有のファイル)は、それぞれのOSのsyscall.Stat_t構造体からos.Dir構造体への変換を行うdirFromStat関数を実装しています。
  • これらの関数は、OS固有のstat_tフィールドをos.Dirの対応するフィールドにマッピングします。例えば、Linuxではstat.Sizeint64ですが、os.Dir.Sizeuint64であるため、型変換が行われます。
  • また、name引数からファイル名部分を抽出してdir.Nameに設定するロジックも含まれています。これは、statシステムコールがパス名全体を受け取るのに対し、Dir.Nameにはファイル名のみを格納するためです。
  • この分離された実装により、Goのosパッケージは、異なるOSのstatシステムコールの差異を透過的に扱うことができ、Go開発者はOSの違いを意識することなくファイルメタデータにアクセスできます。

これらの変更は、Go言語の初期段階におけるシステムプログラミングの基盤を固め、クロスプラットフォーム対応とAPIの使いやすさを大幅に向上させるための重要なステップでした。

関連リンク

参考にした情報源リンク

  • Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語の設計に関する議論 (初期のメーリングリストなど): Goの初期の設計に関する情報は、主にGoの公式ブログやメーリングリストのアーカイブに散在しています。具体的なURLはコミットから直接特定できませんが、Goの歴史を辿る上で重要な情報源です。
  • Unix系OSのシステムコールに関する一般的な情報源 (例: Wikipedia, man pages)。
  • Go言語のソースコード自体。
  • Go言語のioパッケージのStringBytesの歴史的な変更に関する情報。 I have generated the detailed technical explanation of the commit in Markdown format, following all the specified instructions and including all required sections. The output is in Japanese and is as detailed as possible, covering the background, prerequisite knowledge, technical details, core code changes, and explanations. I have also included relevant and reference links. The response is ready to be outputted to standard output.# [インデックス 1639] ファイルの概要

このコミットは、Go言語の標準ライブラリにおけるファイルシステム操作とシステムコールインターフェースの重要な改善を導入しています。主な目的は、statシステムコール(ファイルやディレクトリのメタデータ取得)のクロスプラットフォーム対応を強化し、ファイルディスクリプタ(os.FD)に名前フィールドを追加することで、より堅牢で使いやすいAPIを提供することです。また、システムコールにおける文字列の扱い方を根本的に変更し、より効率的で安全な方法に移行しています。

コミット

commit 704bc9d5c95858fd43151f19e7e4de6c99142f1c
Author: Rob Pike <r@golang.org>
Date:   Fri Feb 6 17:54:26 2009 -0800

    portable stat for os
    add name to os.FD
    clean up some interfaces
    
    R=rsc
    DELTA=318  (231 added, 44 deleted, 43 changed)
    OCL=24624
    CL=24627
---
 src/lib/io/io.go                | 30 +++++++---------\
 src/lib/net/fd.go               | 10 +++---\
 src/lib/os/Makefile             | 22 ++++++++----\
 src/lib/os/os_file.go           | 66 +++++++++++++++++++++++++---------\
 src/lib/os/os_test.go           | 79 +++++++++++++++++++++++++++++++++++++++++
 src/lib/os/os_types.go          | 26 ++++++++++++++\
 src/lib/os/stat_amd64_darwin.go | 34 ++++++++++++++++++\
 src/lib/os/stat_amd64_linux.go  | 34 ++++++++++++++++++\
 src/lib/syscall/file_linux.go   | 40 +++++++--------------
 src/lib/syscall/syscall.go      | 11 +++---\
 src/lib/utf8_test.go            | 11 +++---\
 11 files changed, 277 insertions(+), 86 deletions(-)

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

https://github.com/golang/go/commit/704bc9d5c95858fd43151f19e7e4de6c99142f1c

元コミット内容

portable stat for os
add name to os.FD
clean up some interfaces

変更の背景

このコミットは、Go言語の非常に初期の段階(2009年2月)に行われたもので、Goがまだオープンソース化されて間もない頃の基盤的な改善を反映しています。当時のGoは、異なるオペレーティングシステム(特にLinuxとDarwin/macOS)上での動作を安定させ、標準ライブラリのAPIを洗練させる途上にありました。

主な変更の背景には以下の点が挙げられます。

  1. クロスプラットフォーム対応の強化: statシステムコールは、ファイルやディレクトリに関する詳細な情報(サイズ、パーミッション、タイムスタンプなど)を取得するために不可欠です。しかし、その構造体(stat_t)やフィールドはOSによって異なります。Goが真にポータブルな言語となるためには、これらのOS固有の差異を抽象化し、統一されたインターフェースを提供する必要がありました。このコミットでは、os.DirというOS非依存の構造体を導入し、各OS(amd64/darwin, amd64/linux)向けのstat実装でこれを埋めることで、この課題に対処しています。

  2. ファイルディスクリプタの利便性向上: os.FDはファイルディスクリプタをラップするGoの型です。これまでの実装では、ファイルディスクリプタの数値(Fd)しか持っていませんでした。しかし、デバッグやロギング、あるいは特定の操作において、そのファイルディスクリプタがどのファイル名に対応しているかを知ることは非常に有用です。nameフィールドの追加は、この利便性を向上させ、APIの使いやすさを改善することを目的としています。

  3. システムコールにおける文字列処理の改善: 従来のGoのシステムコールでは、Goの文字列をC言語スタイルのNULL終端バイト配列に変換するためにsyscall.StringToBytesのような関数を使用していました。この方法は、固定サイズのバッファを必要とし、バッファオーバーフローのリスクや、文字列長がバッファサイズを超える場合の扱いに問題がありました。より効率的で安全なsyscall.StringBytePtrへの移行は、システムコール層の堅牢性を高めるための重要なリファクタリングです。これは、Goの文字列が内部的にUTF-8でエンコードされており、C言語のAPIと連携する際に適切な変換が必要となるため、特に重要でした。

これらの変更は、Go言語が多様な環境で安定して動作し、開発者が低レベルのOSインターフェースを意識することなく、高レベルなファイルシステム操作を行えるようにするための基盤を築くものでした。

前提知識の解説

このコミットを理解するためには、以下の概念についての基本的な知識が必要です。

  1. ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数です。プログラムはファイル名ではなく、このFDを使ってI/O操作を行います。標準入力(stdin)は0、標準出力(stdout)は1、標準エラー出力(stderr)は2というFDが予約されています。

  2. stat システムコール: Unix系OSで提供されるシステムコールの一つで、指定されたファイルやディレクトリのメタデータ(ファイルの種類、サイズ、パーミッション、所有者、最終アクセス/変更時刻など)を取得するために使用されます。statはパス名を引数にとり、fstatはファイルディスクリプタを引数にとり、lstatはシンボリックリンク自体を対象とします(statはリンク先のファイルを対象とする)。これらのシステムコールは、通常、stat_tという構造体に結果を格納します。

  3. システムコール (Syscall): オペレーティングシステムが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイルI/O、メモリ管理、プロセス制御など、OSのカーネルが管理するリソースへのアクセスは、システムコールを介して行われます。Go言語では、syscallパッケージがこれらの低レベルなインターフェースを提供します。

  4. Go言語のosパッケージ: Goの標準ライブラリの一部で、OSに依存しないインターフェースを提供し、ファイルシステム操作、プロセス管理、環境変数へのアクセスなどを可能にします。このパッケージは、内部的にOS固有のシステムコールを呼び出すことで、クロスプラットフォームな抽象化を実現しています。

  5. Go言語のioパッケージ: Goの標準ライブラリの一部で、I/Oプリミティブ(Reader, Writerインターフェースなど)を提供します。ファイルやネットワーク接続など、様々なI/Oソースとシンクに対して統一的なインターフェースを提供します。

  6. Go言語の文字列とC言語の文字列: Go言語の文字列は、不変のバイトスライスであり、UTF-8でエンコードされています。文字列の長さはバイト数で管理され、NULL終端ではありません。一方、C言語の文字列は、文字の配列であり、文字列の終端をNULL文字(\0)で示します。システムコールは通常C言語のAPIとして提供されるため、Goの文字列をシステムコールに渡す際には、C言語の文字列形式に変換する必要があります。

  7. Makefile: ソフトウェアのビルドプロセスを自動化するためのツールであるmakeが使用する設定ファイルです。依存関係に基づいてコマンドを実行し、ソースコードのコンパイル、リンク、テストなどのタスクを効率的に管理します。このコミットでは、新しいソースファイル(os_types.go, stat_amd64_darwin.go, stat_amd64_linux.go)をビルドプロセスに含めるためにMakefileが更新されています。

技術的詳細

このコミットにおける技術的な変更は多岐にわたりますが、特に以下の点が重要です。

  1. os.FD構造体の変更とメソッドの追加:

    • src/lib/os/os_file.goにおいて、os.FD構造体にプライベートフィールドfd int64name stringが追加されました。これにより、ファイルディスクリプタの数値だけでなく、そのファイルディスクリプタが関連付けられているファイル名も保持できるようになりました。
    • Fd() int64Name() stringというパブリックなメソッドが追加され、FDオブジェクトからこれらの情報にアクセスできるようになりました。これにより、FDの内部実装が隠蔽され、よりクリーンなAPIが提供されます。
    • NewFD関数もname引数を取るように変更され、Stdin, Stdout, Stderrといった標準FDも初期化時に適切な名前を持つようになりました(例: NewFD(0, "/dev/stdin"))。
    • os.Open, os.Pipeなどの関数も、NewFDの新しいシグネチャに合わせて更新されています。
  2. stat関連関数の導入とOS依存実装の分離:

    • src/lib/os/os_types.goが新規作成され、DirというOS非依存の構造体が定義されました。この構造体は、statシステムコールが返すファイルメタデータ(デバイスID、inode番号、リンク数、モード、サイズ、タイムスタンプなど)を抽象化します。
    • src/lib/os/os_file.goStat, Fstat, Lstatという新しい関数が追加されました。これらの関数は、それぞれパス名、ファイルディスクリプタ、シンボリックリンクのパス名を受け取り、os.Dir構造体を返します。
    • src/lib/os/stat_amd64_darwin.gosrc/lib/os/stat_amd64_linux.goが新規作成されました。これらは、それぞれのOS(DarwinとLinux)におけるsyscall.Stat_t構造体からos.Dir構造体への変換を行うdirFromStat関数を実装しています。これにより、OS固有のstat構造体の差異がGoのos.Dirによって吸収され、クロスプラットフォームなstat操作が可能になります。
  3. システムコールにおける文字列処理の変更:

    • src/lib/syscall/syscall.goにおいて、StringToBytes関数が削除され、代わりにStringBytePtr関数が導入されました。
      • StringToBytesは、Goの文字列を固定サイズのバイト配列にコピーし、NULL終端を追加するものでした。これはバッファサイズの問題や、len(s) >= len(b)の場合にfalseを返すという扱いにくさがありました。
      • StringBytePtrは、Goの文字列を新しいバイトスライスにコピーし、NULL終端を追加した上で、そのバイトスライスの先頭要素へのポインタ(*byte)を返します。これにより、システムコールに直接渡せるC言語スタイルの文字列ポインタを、より安全かつ柔軟に生成できるようになりました。
    • src/lib/syscall/file_linux.go内のOpen, Creat, Stat, Lstat, Unlink, Mkdirといったファイルシステム関連のシステムコールラッパーが、StringToBytesの代わりに新しく導入されたStringBytePtrを使用するように変更されました。これにより、システムコールへの文字列引数の渡し方が統一され、コードの簡潔性と堅牢性が向上しています。
  4. ビルドシステムの更新:

    • src/lib/os/Makefileが更新され、新しいos_types.gostat_amd64_darwin.gostat_amd64_linux.goファイルがビルドプロセスに含まれるようになりました。特に、stat_$(GOARCH)_$(GOOS).$Oというパターンを使用することで、ビルドターゲットのアーキテクチャとOSに基づいて適切なstat実装がリンクされるように設定されています。これは、Goのクロスコンパイルとポータビリティをサポートするための典型的なアプローチです。

これらの変更は、Go言語のファイルシステム抽象化層を大幅に改善し、OS間の差異を吸収しながら、より表現力豊かで安全なAPIを提供するための重要なステップでした。

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

src/lib/os/os_file.go における FD 構造体と関連関数の変更

--- a/src/lib/os/os_file.go
+++ b/src/lib/os/os_file.go
@@ -9,20 +9,29 @@ import os "os"
 
 // FDs are wrappers for file descriptors
 type FD struct {
-	Fd int64
+	fd int64;
+	name	string;
 }
 
-func NewFD(fd int64) *FD {
+func (fd *FD) Fd() int64 {
+	return fd.fd
+}
+
+func (fd *FD) Name() string {
+	return fd.name
+}
+
+func NewFD(fd int64, name string) *FD {
 	if fd < 0 {
 		return nil
 	}
-	return &FD{fd}
+	return &FD{fd, name}
 }
 
 var (
-	Stdin = NewFD(0);
-	Stdout = NewFD(1);
-	Stderr = NewFD(2);
+	Stdin = NewFD(0, "/dev/stdin");
+	Stdout = NewFD(1, "/dev/stdout");
+	Stderr = NewFD(2, "/dev/stderr");
 )
 
 const (
@@ -41,15 +50,15 @@ const (
 
 func Open(name string, mode int, flags int) (fd *FD, err *Error) {
 	r, e := syscall.Open(name, int64(mode), int64(flags));
-	return NewFD(r), ErrnoToError(e)
+	return NewFD(r, name), ErrnoToError(e)
 }
 
 func (fd *FD) Close() *Error {
 	if fd == nil {
 		return EINVAL
 	}
-	r, e := syscall.Close(fd.Fd);
-	fd.Fd = -1;  // so it can't be closed again
+	r, e := syscall.Close(fd.fd);
+	fd.fd = -1;  // so it can't be closed again
 	return ErrnoToError(e)
 }
 
@@ -59,7 +68,7 @@ func (fd *FD) Read(b []byte) (ret int, err *Error) {
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Read(fd.Fd, &b[0], int64(len(b)));
+		r, e = syscall.Read(fd.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -73,7 +82,7 @@ func (fd *FD) Write(b []byte) (ret int, err *Error) {
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Write(fd.Fd, &b[0], int64(len(b)));
+		r, e = syscall.Write(fd.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -85,11 +94,7 @@ func (fd *FD) WriteString(s string) (ret int, err *Error) {\
 	if fd == nil {
 		return 0, EINVAL
 	}
-	b := make([]byte, len(s)+1);
-	if !syscall.StringToBytes(b, s) {
-		return 0, EINVAL
-	}
-	r, e := syscall.Write(fd.Fd, &b[0], int64(len(s)));
+	r, e := syscall.Write(fd.fd, syscall.StringBytePtr(s), int64(len(s)));
 	if r < 0 {
 		r = 0
 	}
@@ -102,10 +107,37 @@ func Pipe() (fd1 *FD, fd2 *FD, err *Error) {\
 	if e != 0 {
 		return nil, nil, ErrnoToError(e)
 	}
-	return NewFD(p[0]), NewFD(p[1]), nil
+	return NewFD(p[0], "|0"), NewFD(p[1], "|1"), nil
 }
 
 func Mkdir(name string, perm int) *Error {
 	r, e := syscall.Mkdir(name, int64(perm));
 	return ErrnoToError(e)
 }
+
+func Stat(name string) (dir *Dir, err *Error) {
+	stat := new(syscall.Stat_t);
+	r, e := syscall.Stat(name, stat);
+	if e != 0 {
+		return nil, ErrnoToError(e)
+	}
+	return dirFromStat(name, new(Dir), stat), nil
+}
+
+func Fstat(fd *FD) (dir *Dir, err *Error) {
+	stat := new(syscall.Stat_t);
+	r, e := syscall.Fstat(fd.fd, stat);
+	if e != 0 {
+		return nil, ErrnoToError(e)
+	}
+	return dirFromStat(fd.name, new(Dir), stat), nil
+}
+
+func Lstat(name string) (dir *Dir, err *Error) {
+	stat := new(syscall.Stat_t);
+	r, e := syscall.Lstat(name, stat);
+	if e != 0 {
+		return nil, ErrnoToError(e)
+	}
+	return dirFromStat(name, new(Dir), stat), nil
+}

src/lib/syscall/syscall.go における StringBytePtr の導入

--- a/src/lib/syscall/syscall.go
+++ b/src/lib/syscall/syscall.go
@@ -16,13 +16,10 @@ func RawSyscall(trap int64, a1, a2, a3 int64) (r1, r2, err int64);\
  * Used to convert file names to byte arrays for passing to kernel,
  * but useful elsewhere too.
  */
-func StringToBytes(b []byte, s string) bool {
-	if len(s) >= len(b) {
-		return false
-	}
+func StringBytePtr(s string) *byte {
+	a := make([]byte, len(s)+1);
 	for i := 0; i < len(s); i++ {
-		b[i] = s[i]
+		a[i] = s[i];
 	}
-	b[len(s)] = '\000';	// not necessary - memory is zeroed - but be explicit
-	return true
+	return &a[0];
 }

src/lib/os/os_types.go (新規ファイル)

// 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

// An operating-system independent representation of Unix data structures.
// OS-specific routines in this directory convert the OS-local versions to these.

// Result of stat64(2) etc.
type Dir struct {
	Dev	uint64;
	Ino	uint64;
	Nlink	uint64;
	Mode	uint32;
	Uid	uint32;
	Gid	uint32;
	Rdev	uint64;
	Size	uint64;
	Blksize	uint64;
	Blocks	uint64;
	Atime_ns	uint64;	// nanoseconds since 1970
	Mtime_ns	uint64;	// nanoseconds since 1970
	Ctime_ns	uint64;	// nanoseconds since 1970
	Name	string;
}

src/lib/os/stat_amd64_linux.go (新規ファイル、stat_amd64_darwin.goも同様)

// 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.

// AMD64, Linux

package os

import syscall "syscall"
import os "os"

func dirFromStat(name string, dir *Dir, stat *syscall.Stat_t) *Dir {
	dir.Dev = stat.Dev;
	dir.Ino = stat.Ino;
	dir.Nlink = stat.Nlink;
	dir.Mode = stat.Mode;
	dir.Uid = stat.Uid;
	dir.Gid = stat.Gid;
	dir.Rdev = stat.Rdev;
	dir.Size = uint64(stat.Size);
	dir.Blksize = uint64(stat.Blksize);
	dir.Blocks = uint64(stat.Blocks);
	dir.Atime_ns = uint64(stat.Atime.Sec) * 1e9 + stat.Atime.Nsec;
	dir.Mtime_ns = uint64(stat.Mtime.Sec) * 1e9 + stat.Mtime.Nsec;
	dir.Ctime_ns = uint64(stat.Ctime.Sec) * 1e9 + stat.Atime.Nsec;
	for i := len(name) - 1; i >= 0; i-- {
		if name[i] == '/' {
			name = name[i+1:len(name)];
			break;
		}
	}
	dir.Name = name;
	return dir;
}

コアとなるコードの解説

os.FD 構造体と関連関数の変更 (src/lib/os/os_file.go)

  • FD 構造体の変更: 以前はFd int64のみを持っていたFD構造体に、fd int64(プライベート化)とname stringが追加されました。これにより、ファイルディスクリプタの数値だけでなく、そのファイルがオープンされた際のパス名もFDオブジェクトに紐付けられるようになりました。fdフィールドがプライベートになったことで、外部からは直接アクセスできなくなり、カプセル化が強化されています。
  • Fd()Name() メソッドの追加: fdnameフィールドへのアクセスを提供するために、それぞれFd()Name()というゲッターメソッドが追加されました。これにより、FDの内部表現に依存せずに、これらの情報を取得できるようになります。
  • NewFD 関数の変更: NewFD関数は、ファイルディスクリプタの数値だけでなく、そのファイルディスクリプタに関連するname(通常はファイルパス)も引数として受け取るようになりました。これにより、FDオブジェクトがより完全な情報を持つことができるようになります。
  • 標準FDの初期化: Stdin, Stdout, StderrといったGoの標準ファイルディスクリプタも、新しいNewFDのシグネチャに合わせて、それぞれ/dev/stdin, /dev/stdout, /dev/stderrという名前で初期化されるようになりました。これは、これらのFDがどのリソースに対応しているかを明確にするのに役立ちます。
  • Open, Close, Read, Write, WriteString, Pipe の変更: これらの関数やメソッドは、FD構造体の変更に合わせて、内部でfd.fd(プライベートなフィールド)を使用するように修正されました。特にWriteStringでは、後述のsyscall.StringBytePtrが使用されるようになり、文字列のシステムコールへの渡し方が改善されています。Pipe関数も、生成されるパイプのFDに|0|1といった名前を付与するようになりました。

syscall.StringBytePtr の導入 (src/lib/syscall/syscall.go)

  • StringToBytes の削除と StringBytePtr の導入: 以前のStringToBytes関数は、Goの文字列をC言語のNULL終端バイト配列に変換する際に、固定サイズのバッファを必要とし、バッファオーバーフローの可能性や、文字列が長すぎる場合の扱いに問題がありました。
  • 新しく導入されたStringBytePtr関数は、Goの文字列sを受け取り、その内容を新しいバイトスライスaにコピーし、最後にNULL終端を追加します。そして、そのバイトスライスaの先頭要素へのポインタ(*byte)を返します。
  • このアプローチにより、呼び出し側は固定サイズのバッファを事前に用意する必要がなくなり、Goの文字列の長さに応じて動的にメモリが確保されるため、より安全で柔軟な文字列変換が可能になります。システムコールは通常、C言語の関数として定義されており、文字列引数としてchar*(バイトポインタ)を期待するため、この*byte型はシステムコールに直接渡すのに適しています。

os.Dir 構造体 (src/lib/os/os_types.go)

  • このファイルで定義されたDir構造体は、OSに依存しないファイルやディレクトリのメタデータ表現です。Unix系OSのstat_t構造体から主要なフィールド(デバイスID、inode番号、リンク数、モード、UID、GID、サイズ、ブロックサイズ、ブロック数、アクセス/変更/作成タイムスタンプ、名前)を抽出し、Goの型にマッピングしています。
  • この抽象化層により、Goのアプリケーションは、基盤となるOSがLinuxであろうとDarwinであろうと、統一されたDir構造体を通じてファイルメタデータにアクセスできるようになります。

OS固有の dirFromStat 関数 (src/lib/os/stat_amd64_linux.go など)

  • stat_amd64_linux.gostat_amd64_darwin.go(および他のOS/アーキテクチャ固有のファイル)は、それぞれのOSのsyscall.Stat_t構造体からos.Dir構造体への変換を行うdirFromStat関数を実装しています。
  • これらの関数は、OS固有のstat_tフィールドをos.Dirの対応するフィールドにマッピングします。例えば、Linuxではstat.Sizeint64ですが、os.Dir.Sizeuint64であるため、型変換が行われます。
  • また、name引数からファイル名部分を抽出してdir.Nameに設定するロジックも含まれています。これは、statシステムコールがパス名全体を受け取るのに対し、Dir.Nameにはファイル名のみを格納するためです。
  • この分離された実装により、Goのosパッケージは、異なるOSのstatシステムコールの差異を透過的に扱うことができ、Go開発者はOSの違いを意識することなくファイルメタデータにアクセスできます。

これらの変更は、Go言語の初期段階におけるシステムプログラミングの基盤を固め、クロスプラットフォーム対応とAPIの使いやすさを大幅に向上させるための重要なステップでした。

関連リンク

参考にした情報源リンク

  • Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語の設計に関する議論 (初期のメーリングリストなど): Goの初期の設計に関する情報は、主にGoの公式ブログやメーリングリストのアーカイブに散在しています。具体的なURLはコミットから直接特定できませんが、Goの歴史を辿る上で重要な情報源です。
  • Unix系OSのシステムコールに関する一般的な情報源 (例: Wikipedia, man pages)。
  • Go言語のソースコード自体。
  • Go言語のioパッケージのStringBytesの歴史的な変更に関する情報。