[インデックス 15508] ファイルの概要
このコミットは、Go言語のos
パッケージにおけるPlan 9固有のRename
関数のバグ修正に関するものです。具体的には、syscall
パッケージで利用されるstat
構造体のName
フィールドが可変長であるにもかかわらず、バッファの確保が固定長で行われていた問題を修正し、適切な長さのバッファを確保するように変更しています。
コミット
commit bd889907228024c1c682e86859611002e894abf8
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Thu Feb 28 14:20:42 2013 -0800
os: Plan 9: allocate space for a string in Rename
The Name field of the stat structure is variable length
and the marshalling code in package syscall requires
a buf long enough to contain the Name as well as the
static data. This change makes sure that the buffer in
os.Rename is allocated with the appropriate length.
R=rsc, rminnich, ality, r
CC=golang-dev
https://golang.org/cl/7453044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bd889907228024c1c682e86859611002e894abf8
元コミット内容
os: Plan 9: allocate space for a string in Rename
stat
構造体のName
フィールドは可変長であり、syscall
パッケージのマーシャリングコードは、Name
と静的データの両方を含むのに十分な長さのバッファを必要とします。この変更は、os.Rename
内のバッファが適切な長さで割り当てられるようにします。
変更の背景
この変更は、Go言語のos
パッケージがPlan 9オペレーティングシステム上でファイルをリネームする際に発生する可能性のあるバグを修正するために行われました。
Plan 9は、ベル研究所で開発された分散オペレーティングシステムであり、そのファイルシステムは独特の設計思想を持っています。特に、ファイルやディレクトリのメタデータ(属性)を表現するstat
構造体において、ファイル名(Name
フィールド)が可変長で扱われるという特徴があります。
Go言語のsyscall
パッケージは、低レベルのシステムコールをGoから呼び出すためのインターフェースを提供します。Plan 9環境では、ファイルシステム操作のためにsyscall
パッケージを通じてPlan 9のシステムコールを呼び出す必要があります。
問題は、os.Rename
関数が内部でsyscall
パッケージを利用してstat
構造体をマーシャリング(メモリ上のデータ構造をバイト列に変換すること)する際に、Name
フィールドの可変長性を考慮せずに固定長のバッファを割り当てていた点にありました。これにより、ファイル名がsyscall.STATFIXLEN
(stat
構造体の固定部分の長さ)を超える場合に、バッファオーバーフローやデータ破損が発生する可能性がありました。
このコミットは、このバッファ割り当ての不備を修正し、Name
フィールドの長さに応じて動的にバッファサイズを調整することで、Rename
操作の堅牢性を向上させることを目的としています。
前提知識の解説
Plan 9オペレーティングシステム
Plan 9は、Unixの設計思想をさらに推し進め、すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルとして表現するという「すべてはファイルである」という原則を徹底した分散オペレーティングシステムです。特徴としては、以下が挙げられます。
- ファイルシステム中心の設計: すべてのリソースがファイルシステムツリーにマウントされ、ファイル操作のAPIを通じてアクセスされます。
- 9Pプロトコル: 分散環境でのファイルシステムアクセスを可能にするための独自のネットワークプロトコルです。
- UTF-8の採用: システム全体でUTF-8が採用されており、多言語対応が容易です。
Go言語は、Plan 9の開発者であるRob Pike、Ken Thompson、Robert Griesemerによって設計されたこともあり、Plan 9に対するサポートが初期から組み込まれています。
stat
構造体とファイルシステムメタデータ
stat
構造体は、ファイルやディレクトリに関するメタデータ(属性)を格納するためのデータ構造です。Unix系システムにおけるstat
構造体と同様に、ファイルサイズ、パーミッション、最終更新時刻などの情報が含まれます。Plan 9のstat
構造体は、特にファイル名(Name
フィールド)が可変長であるという点で特徴的です。これは、ファイル名が固定長に制限されることなく、柔軟に扱えるようにするためです。
マーシャリングとアンマーシャリング
マーシャリング(Marshalling)とは、メモリ上のデータ構造を、ネットワーク転送やファイル保存に適した形式(通常はバイト列)に変換するプロセスを指します。逆に、バイト列から元のデータ構造に復元するプロセスをアンマーシャリング(Unmarshalling)と呼びます。
このコミットの文脈では、Goのsyscall
パッケージがPlan 9のシステムコールを呼び出す際に、Goのデータ構造(stat
構造体)をPlan 9のシステムが理解できるバイト列形式にマーシャリングする必要がありました。このマーシャリング処理において、stat
構造体のName
フィールドの長さが適切に考慮されていないことが問題でした。
syscall.STATFIXLEN
syscall.STATFIXLEN
は、Plan 9のstat
構造体における固定部分のバイト長を定義する定数です。stat
構造体は、固定長のヘッダ部分と、可変長のファイル名(Name
フィールド)から構成されます。syscall.STATFIXLEN
は、この固定部分の長さを表しており、ファイル名を含まないstat
構造体の最小サイズを示します。
技術的詳細
Go言語のos
パッケージは、オペレーティングシステムに依存しないファイルシステム操作の抽象化を提供しますが、内部的には各OS固有のシステムコールを呼び出すためにsyscall
パッケージを利用します。Plan 9の場合、src/pkg/os/file_plan9.go
ファイルにPlan 9固有の実装が含まれています。
os.Rename
関数は、oldname
で指定されたファイルをnewname
にリネームする機能を提供します。Plan 9では、この操作はファイルシステムへのwstat
(write stat)システムコールを通じて行われます。wstat
システムコールは、ファイルのメタデータを変更するためにstat
構造体を引数として受け取ります。
コミット前のコードでは、os.Rename
関数内でstat
構造体をマーシャリングするためのバッファを次のように宣言していました。
var buf [syscall.STATFIXLEN]byte
この宣言では、buf
のサイズがsyscall.STATFIXLEN
バイトに固定されています。しかし、前述の通り、Plan 9のstat
構造体のName
フィールドは可変長であり、新しいファイル名newname
の長さによってstat
構造体全体のサイズが変化します。
d.Marshal(buf[:])
という呼び出しは、stat
構造体d
をbuf
にマーシャリングしようとします。もしnewname
の長さがsyscall.STATFIXLEN
で確保されたバッファの残りのスペースを超えてしまうと、マーシャリングが失敗するか、あるいはバッファオーバーフローを引き起こし、予期せぬ動作やクラッシュにつながる可能性がありました。
このコミットは、この問題を解決するために、バッファのサイズをsyscall.STATFIXLEN
に加えて、新しいファイル名d.Name
の長さも考慮するように変更しました。
buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
make([]byte, size)
は、指定されたサイズのバイトスライスを動的に割り当てます。これにより、Rename
操作で指定される新しいファイル名の長さに応じて、適切なサイズのバッファが確保されるようになり、バッファオーバーフローのリスクが解消されました。
コアとなるコードの変更箇所
変更はsrc/pkg/os/file_plan9.go
ファイルのRename
関数内で行われています。
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -308,7 +308,7 @@ func Rename(oldname, newname string) error {
d.Null()
d.Name = newname
- var buf [syscall.STATFIXLEN]byte
+ buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
n, err := d.Marshal(buf[:])
if err != nil {
return &PathError{"rename", oldname, err}
コアとなるコードの解説
変更された行は以下の通りです。
- var buf [syscall.STATFIXLEN]byte
+ buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
-
変更前:
var buf [syscall.STATFIXLEN]byte
- これは、
syscall.STATFIXLEN
バイトの固定サイズの配列buf
を宣言しています。Goでは、配列のサイズはコンパイル時に決定されるため、この配列のサイズは実行中に変更できません。 - この固定サイズは、
stat
構造体の固定部分の長さのみを考慮しており、可変長であるName
フィールドの長さは含まれていませんでした。
- これは、
-
変更後:
buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
- これは、
make
関数を使用してバイトスライスbuf
を動的に作成しています。 - スライスのサイズは、
syscall.STATFIXLEN
(stat
構造体の固定部分の長さ)に、d.Name
(新しいファイル名)の長さlen(d.Name)
を加算した値になります。 - これにより、
stat
構造体全体(固定部分と可変長のファイル名を含む)を格納するのに十分なサイズのバッファが確保されることが保証されます。 d.Name
は、d.Null()
の後にd.Name = newname
で設定されており、newname
の文字列長がlen(d.Name)
として正確に計算されます。
- これは、
この変更により、d.Marshal(buf[:])
が呼び出された際に、stat
構造体のすべてのデータ(特に新しいファイル名)がバッファに適切にマーシャリングされるようになり、Plan 9上でのファイルリネーム操作の信頼性が向上しました。
関連リンク
- Go言語の
os
パッケージのドキュメント: https://pkg.go.dev/os - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Plan 9 from Bell Labs: https://9p.io/plan9/
- Go CL 7453044 (Gerrit Code Review): https://golang.org/cl/7453044
参考にした情報源リンク
- https://github.com/golang/go/commit/bd889907228024c1c682e86859611002e894abf8
- Go言語のソースコード(
src/pkg/os/file_plan9.go
) - Plan 9の
stat
構造体に関する情報 (一般的なPlan 9のドキュメントやリソース) - Go言語におけるスライスと配列の概念に関する情報 (Go言語の公式ドキュメントやチュートリアル)
- マーシャリングの概念に関する一般的なプログラミング知識I have provided the detailed commit explanation in Markdown format to standard output, as requested. I have followed all the instructions, including the chapter structure and language.
Let me know if you need anything else!