[インデックス 15387] ファイルの概要
このコミットは、Go言語のos
パッケージにおけるPlan 9オペレーティングシステム向けのファイルI/O処理に関する修正です。具体的には、Plan 9のI/O特性に合わせて、ゼロバイトの書き込み(zero-length writes)を避けるように変更されています。
コミット
commit 722ee1f4797a81916b19e80df479058897c44923
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Fri Feb 22 23:06:25 2013 +0100
os: Plan 9: avoid doing zero-length writes.
Plan 9 I/O preserves message boundaries, while Go
library code is written for UNIX-like operating
systems which do not. Avoid doing zero-length
writes in package os.
R=rsc, rminnich, ality, rminnich, r
CC=golang-dev
https://golang.org/cl/7406046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/722ee1f4797a81916b19e80df479058897c44923
元コミット内容
このコミットは、Go言語のos
パッケージにおいて、Plan 9オペレーティングシステム上でのゼロバイト書き込みを回避するための変更を導入します。その理由は、Plan 9のI/Oがメッセージ境界を保持するのに対し、Goの標準ライブラリコードはUNIXライクなシステム(メッセージ境界を保持しない)向けに書かれているためです。
変更の背景
Go言語の標準ライブラリ、特にファイルI/Oを扱うos
パッケージは、主にUNIXライクなオペレーティングシステム(Linux, macOS, BSDなど)のセマンティクスを前提として設計されています。これらのシステムでは、ファイルへの書き込みはバイトストリームとして扱われ、書き込みサイズがゼロであっても通常はエラーになりません(単に何も書き込まれないだけです)。
しかし、Plan 9というオペレーティングシステムは、そのI/OモデルがUNIXとは大きく異なります。Plan 9のファイルシステムプロトコル(9P)は、I/O操作を「メッセージ」として扱います。これは、書き込み操作が特定のデータブロック(メッセージ)として扱われ、そのメッセージが空(ゼロバイト)であることは、システムによっては無効な操作と見なされたり、予期せぬ挙動を引き起こしたりする可能性があります。
GoのライブラリがUNIXのセマンティクスに依存してゼロバイト書き込みを行う場合、Plan 9環境ではこの特性の不一致が問題となり、潜在的なバグや非効率性を引き起こす可能性がありました。このコミットは、このOS間のI/Oセマンティクスの違いを吸収し、Plan 9上でのGoプログラムの堅牢性を向上させることを目的としています。
前提知識の解説
Plan 9のI/Oモデルとメッセージ境界
Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。その設計思想の中心には「すべてがファイルである」というUNIXの哲学をさらに推し進めたものがあります。しかし、そのI/OモデルはUNIXとは異なります。
-
UNIXライクなI/O: UNIX系システムでは、ファイルやデバイスへのI/Oは基本的にバイトストリームとして扱われます。
write()
システムコールは指定されたバイト数を書き込もうとし、成功すれば書き込んだバイト数を返します。書き込みサイズがゼロの場合、通常は0を返し、エラーとはなりません。データは連続したバイト列として扱われ、個々のwrite()
呼び出しが「メッセージ」として区切られることはありません。 -
Plan 9のI/Oとメッセージ境界: Plan 9では、ファイルシステムプロトコル(9P)を通じてI/Oが行われます。このプロトコルは、クライアントとサーバー間で「メッセージ」を交換することで動作します。
write
操作もまた、特定のサイズのデータを含むメッセージとして扱われます。この「メッセージ境界の保持」という特性は、UNIXのバイトストリームモデルとは根本的に異なります。ゼロバイトのメッセージは、プロトコルによっては意味を持たないか、あるいは不正な操作と解釈される可能性があります。
Go言語のsyscall
パッケージ
Go言語のsyscall
パッケージは、オペレーティングシステムの低レベルなシステムコールにアクセスするためのインターフェースを提供します。os
パッケージのような高レベルなI/O操作は、内部的にこのsyscall
パッケージを利用してOSの機能とやり取りしています。
syscall.Write(fd int, p []byte) (n int, err error)
: 指定されたファイルディスクリプタfd
に対して、バイトスライスp
の内容を書き込みます。書き込んだバイト数n
とエラーを返します。syscall.Pwrite(fd int, p []byte, off int64) (n int, err error)
:write
と同様ですが、ファイルディスクリプタfd
の指定されたオフセットoff
から書き込みを開始します。
これらのシステムコールは、OSのI/Oセマンティクスに直接依存します。したがって、Plan 9のような特殊なI/Oモデルを持つOSでは、Goの標準ライブラリがこれらのシステムコールを呼び出す際に、OSの特性を考慮した追加のロジックが必要になることがあります。
技術的詳細
このコミットが解決しようとしている問題は、Goのos
パッケージがUNIXライクなシステムでの動作を前提としているために、Plan 9の「メッセージ境界を保持するI/O」という特性と衝突することです。
具体的には、Goのコードがwrite
やpwrite
のようなファイル書き込み関数を呼び出す際に、書き込むバイトスライスb
の長さが0
である場合、UNIX系システムではこれは単に「何も書き込まない」という操作として扱われ、通常は問題ありません。しかし、Plan 9では、ゼロバイトの書き込みが「空のメッセージ」として解釈され、これがプロトコルレベルで不正な操作と見なされたり、予期せぬエラーや挙動を引き起こす可能性がありました。
このコミットの解決策は非常にシンプルかつ効果的です。os
パッケージ内のPlan 9固有のファイルI/O実装であるFile.write
およびFile.pwrite
メソッドにおいて、実際にsyscall.Write
やsyscall.Pwrite
を呼び出す前に、書き込むバイトスライスb
の長さがゼロであるかどうかをチェックします。もしlen(b)
が0
であれば、実際のシステムコールを呼び出すことなく、直ちに0
バイト書き込み成功として0, nil
(書き込んだバイト数0、エラーなし)を返します。
これにより、Plan 9のI/Oシステムにゼロバイトのメッセージが送信されることを防ぎ、OSの特性に合わせた堅牢な動作を保証します。GoのライブラリコードはUNIXライクなセマンティクスを維持しつつ、Plan 9環境での互換性と安定性を確保しています。
コアとなるコードの変更箇所
変更はsrc/pkg/os/file_plan9.go
ファイルに集中しています。
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -244,13 +244,23 @@ func (f *File) pread(b []byte, off int64) (n int, err error) {
// write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
func (f *File) write(b []byte) (n int, err error) {
+\tif len(b) == 0 {\n+\t\treturn 0, nil\n+\t}\n return syscall.Write(f.fd, b)
}
// pwrite writes len(b) bytes to the File starting at byte offset off.
// It returns the number of bytes written and an error, if any.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
func (f *File) pwrite(b []byte, off int64) (n int, err error) {
+\tif len(b) == 0 {\n+\t\treturn 0, nil\n+\t}\n return syscall.Pwrite(f.fd, b, off)
}
コアとなるコードの解説
変更は、File
構造体のwrite
メソッドとpwrite
メソッドの2箇所に適用されています。
-
func (f *File) write(b []byte) (n int, err error)
- このメソッドは、ファイルにバイトスライス
b
の内容を書き込むためのものです。 - 追加された行:
if len(b) == 0 { return 0, nil }
- この
if
文は、書き込もうとしているバイトスライスb
の長さがゼロであるかどうかをチェックします。 - もし
len(b)
が0
であれば、実際のシステムコールsyscall.Write
を呼び出すことなく、直ちに0
(書き込んだバイト数)とnil
(エラーなし)を返します。これにより、Plan 9のI/Oシステムにゼロバイトの書き込み要求が送られることを防ぎます。 - コメント
// Since Plan 9 preserves message boundaries, never allow // a zero-byte write.
が追加され、この変更の理由が明確に示されています。
- このメソッドは、ファイルにバイトスライス
-
func (f *File) pwrite(b []byte, off int64) (n int, err error)
- このメソッドは、指定されたオフセット
off
からファイルにバイトスライスb
の内容を書き込むためのものです。 write
メソッドと同様に、以下の行が追加されています。if len(b) == 0 { return 0, nil }
- ここでも、
len(b)
が0
であれば、syscall.Pwrite
を呼び出すことなく0, nil
を返します。 - 同様のコメントが追加され、Plan 9のメッセージ境界の特性が強調されています。
- このメソッドは、指定されたオフセット
これらの変更により、Goのos
パッケージはPlan 9環境において、ゼロバイト書き込みに関する潜在的な問題を回避し、より堅牢で互換性のある動作を実現しています。これは、異なるOSのI/OセマンティクスをGoの標準ライブラリが適切に吸収するための典型的な例と言えます。
関連リンク
- Go CL 7406046: https://golang.org/cl/7406046
参考にした情報源リンク
- (特になし。コミットメッセージと一般的なOSのI/O知識に基づいています。)