[インデックス 14499] ファイルの概要
このコミットは、Go言語の標準ライブラリにおいて、Plan 9オペレーティングシステムに特化したディレクトリ情報のマーシャリング(構造化されたデータをバイト列に変換する処理)およびアンマーシャリング(バイト列を構造化されたデータに変換する処理)に関するコードを、os
パッケージからsyscall
パッケージへ移動させるものです。これにより、システムコールに関連する低レベルな処理がsyscall
パッケージに集約され、os
パッケージはより高レベルな抽象化を提供できるようになります。
コミット
commit 4ce3df5074d1ab4e0440a4d19ed6d26a54025578
Author: Anthony Martin <ality@pbrane.org>
Date: Mon Nov 26 15:26:46 2012 -0800
os: move Plan 9 directory marshaling code to syscall
The API additions to syscall are in dir_plan9.go.
R=seed, rsc, rminnich, mirtchovski, dave
CC=golang-dev, lucio.dere
https://golang.org/cl/6157045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4ce3df5074d1ab4e0440a4d19ed6d26a5402578
元コミット内容
os: move Plan 9 directory marshaling code to syscall
The API additions to syscall are in dir_plan9.go.
R=seed, rsc, rminnich, mirtchovski, dave
CC=golang-dev, lucio.dere
https://golang.org/cl/6157045
変更の背景
この変更の主な背景は、Go言語の標準ライブラリにおけるコードの責務分離とモジュール性の向上です。
- 責務の分離:
os
パッケージは、オペレーティングシステムに依存しない汎用的なファイルシステム操作(ファイルの読み書き、ディレクトリのリスト、ファイル情報の取得など)を提供することを目的としています。一方、syscall
パッケージは、特定のOSのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。Plan 9のディレクトリ情報のマーシャリングは、Plan 9の9Pプロトコルという特定のOSの通信規約に深く関連する低レベルな処理です。これをos
パッケージ内に置くことは、os
パッケージの抽象度を下げ、特定のOSの実装詳細に依存させることになります。 - コードの整理と保守性: Plan 9のディレクトリ情報のマーシャリングコードは、バイト列と構造体の間の変換という、システムコール層に近い処理です。これを
syscall
パッケージに移動することで、関連する低レベルなコードが一箇所に集約され、コードの可読性、保守性、およびテストのしやすさが向上します。 - APIの一貫性:
syscall
パッケージは、様々なOSのシステムコールインターフェースを提供しており、OS固有のデータ構造やその操作に関するAPIを持つことが自然です。Plan 9のディレクトリ情報もその一部としてsyscall
パッケージに含めることで、API設計の一貫性が保たれます。
前提知識の解説
このコミットを理解するためには、以下の概念について知っておく必要があります。
- Plan 9 from Bell Labs: ベル研究所で開発された分散オペレーティングシステムです。Unixの後継として設計され、"Everything is a file"(すべてはファイルである)という哲学を徹底しています。ネットワーク上のリソースもファイルとして扱われ、9Pプロトコルを通じてアクセスされます。
- 9Pプロトコル (Plan 9 File Protocol): Plan 9システムにおけるファイルシステム、プロセス、ネットワークなどのリソースへのアクセスを抽象化するためのプロトコルです。クライアントとサーバー間でメッセージを交換し、ファイル操作(読み書き、ディレクトリのリスト、属性の取得など)を行います。このプロトコルでは、ファイルやディレクトリのメタデータは「Statメッセージ」と呼ばれる特定の形式のバイト列で表現されます。
- マーシャリング (Marshaling): プログラム内で使用されるデータ構造(オブジェクト)を、ネットワーク転送やファイル保存に適した形式(通常はバイト列)に変換するプロセスです。
- アンマーシャリング (Unmarshaling): マーシャリングされたバイト列を、元のデータ構造に復元するプロセスです。
- Go言語の
os
パッケージ: オペレーティングシステムに依存しない、基本的なファイルシステム操作(ファイルやディレクトリの作成、読み書き、削除、情報取得など)を提供するパッケージです。 - Go言語の
syscall
パッケージ: オペレーティングシステム固有の低レベルなシステムコールインターフェースを提供するパッケージです。OSに直接アクセスする必要がある場合や、OS固有の機能を利用する場合に使用されます。 FileInfo
インターフェース:os
パッケージで定義されているインターフェースで、ファイルの名前、サイズ、パーミッション、更新時刻などのファイル情報を抽象的に表現します。os.Stat
関数などがこのインターフェースを実装した値を返します。Stat
メッセージ (Plan 9): 9Pプロトコルにおいて、ファイルやディレクトリのメタデータ(ファイルタイプ、パーミッション、サイズ、更新時刻、所有者など)を表現するための構造化されたバイト列です。このコミットで移動されるコードは、このStatメッセージのバイト列とGo言語のDir
構造体(ファイル情報を表す)との間の変換を扱います。
技術的詳細
このコミットの核心は、Plan 9の9PプロトコルにおけるStatメッセージのマーシャリング/アンマーシャリングロジックをos
パッケージからsyscall
パッケージへ移管した点にあります。
具体的には、以下の変更が行われました。
-
src/pkg/syscall/dir_plan9.go
の新規作成:- このファイルが新しく作成され、Plan 9のディレクトリ情報に関する構造体と、そのマーシャリング/アンマーシャリング関数が定義されました。
Qid
構造体: Plan 9におけるファイルのユニークな識別子(パス、バージョン、タイプ)を表現します。Dir
構造体: Plan 9におけるファイルのメタデータ(タイプ、デバイス、Qid、モード、アクセス時刻、更新時刻、長さ、名前、UID、GID、MUID)を表現します。これは、以前os
パッケージにあったdir
構造体に対応します。(*Dir).Marshal(b []byte) (n int, err error)
メソッド:Dir
構造体の内容を9P Statメッセージ形式のバイト列にマーシャリングし、指定されたバイトスライスb
に書き込みます。UnmarshalDir(b []byte) (*Dir, error)
関数: 9P Statメッセージ形式のバイト列b
を解析し、対応するDir
構造体を返します。pbitX
,gbitX
,pstring
,gstring
などのヘルパー関数: バイト列からの数値や文字列の読み書き(リトルエンディアン形式)を行うための低レベルな関数群も、このファイルに移動されました。これらは9Pプロトコルのデータ形式に準拠しています。- エラー定数
ErrShortStat
とErrBadStat
もsyscall
パッケージに定義されました。
-
src/pkg/os/dir_plan9.go
の変更:- 以前このファイルに存在した
dir
構造体、qid
構造体、pdir
関数(マーシャリング)、unmarshalDir
関数(アンマーシャリング)、および関連するヘルパー関数(gbitX
,pbitX
,gstring
,pstring
)がすべて削除されました。 (*File).readdir
メソッド内で、syscall.UnmarshalDir
が使用されるように変更されました。これにより、os
パッケージはPlan 9のディレクトリ情報の解析をsyscall
パッケージに委譲する形になりました。- エラー処理も
syscall.ErrShortStat
を使用するように更新されました。
- 以前このファイルに存在した
-
src/pkg/os/file_plan9.go
の変更:Truncate
,Chmod
,Sync
,Rename
,Chtimes
といったファイル操作関数において、os
パッケージ独自のdir
構造体ではなく、syscall.Dir
構造体を使用するように変更されました。- これらの関数内で、
syscall.Fwstat
やsyscall.Wstat
を呼び出す際に、syscall.Dir
のMarshal
メソッドを使用してStatメッセージのバイト列を生成するようになりました。これにより、os
パッケージはファイル属性の変更要求を、syscall
パッケージが提供する低レベルなマーシャリング機能を通じてPlan 9システムに伝達します。
-
src/pkg/os/stat_plan9.go
の変更:sameFile
,fileInfoFromStat
,dirstat
関数において、引数や戻り値の型が*dir
から*syscall.Dir
に変更されました。dirstat
関数内で、syscall.UnmarshalDir
が使用されるようになりました。これにより、ファイル情報の取得と解析もsyscall
パッケージに委譲されます。
-
src/pkg/syscall/exec_plan9.go
の変更:- 重複していた
gbit16
とgstring
関数が削除されました。これらの関数はsyscall/dir_plan9.go
に移動され、一元化されました。 readdirnames
関数内で、gstring
の呼び出しが新しいsyscall.gstring
(戻り値にok
ブール値が追加された)を使用するように更新され、エラー処理もsyscall.ErrBadStat
を使用するように変更されました。
- 重複していた
この変更により、os
パッケージはPlan 9固有の低レベルなプロトコル処理から解放され、よりクリーンで汎用的なファイルシステムAPIを提供できるようになりました。Plan 9のStatメッセージの構造やそのバイト列への変換ロジックは、syscall
パッケージ内にカプセル化され、OS固有の実装詳細として適切に配置されました。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に以下のファイルに集中しています。
-
src/pkg/os/dir_plan9.go
:dir
構造体、qid
構造体、およびそれらに関連するマーシャリング/アンマーシャリング関数(pdir
,unmarshalDir
,gbitX
,pbitX
,gstring
,pstring
)が完全に削除されました。(*File).readdir
メソッド内で、unmarshalDir
の代わりにsyscall.UnmarshalDir
が呼び出されるようになりました。
--- a/src/pkg/os/dir_plan9.go +++ b/src/pkg/os/dir_plan9.go @@ -5,15 +5,11 @@ package os import ( - "errors" "io" "syscall" ) -var errShortStat = errors.New("short stat message") -var errBadStat = errors.New("bad stat message format") - -func (file *File) readdir(n int) (fi []FileInfo, err error) { +func (file *File) readdir(n int) ([]FileInfo, error) { // If this file has no dirinfo, create one. if file.dirinfo == nil { file.dirinfo = new(dirInfo) @@ -24,44 +20,47 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) { size = 100 n = -1 } - result := make([]FileInfo, 0, size) // Empty with room to grow. + fi := make([]FileInfo, 0, size) // Empty with room to grow. for n != 0 { - // Refill the buffer if necessary + // Refill the buffer if necessary. if d.bufp >= d.nbuf { - d.bufp = 0 - var e error - d.nbuf, e = file.Read(d.buf[:]) - if e != nil && e != io.EOF { - return result, &PathError{"readdir", file.name, e} - } - if e == io.EOF { - break + nb, err := file.Read(d.buf[:]) + + // Update the buffer state before checking for errors. + d.bufp, d.nbuf = 0, nb + + if err != nil { + if err == io.EOF { + break + } + return fi, &PathError{"readdir", file.name, err} } - if d.nbuf < syscall.STATFIXLEN { - return result, &PathError{"readdir", file.name, errShortStat} + if nb < syscall.STATFIXLEN { + return fi, &PathError{"readdir", file.name, syscall.ErrShortStat} } } - // Get a record from buffer - m, _ := gbit16(d.buf[d.bufp:]) - m += 2 + // Get a record from the buffer. + b := d.buf[d.bufp:] + m := int(uint16(b[0])|uint16(b[1])<<8) + 2 if m < syscall.STATFIXLEN { - return result, &PathError{"readdir", file.name, errShortStat} + return fi, &PathError{"readdir", file.name, syscall.ErrShortStat} } - dir, e := unmarshalDir(d.buf[d.bufp : d.bufp+int(m)]) - if e != nil { - return result, &PathError{"readdir", file.name, e} + + dir, err := syscall.UnmarshalDir(b[:m]) + if err != nil { + return fi, &PathError{"readdir", file.name, err} } - result = append(result, fileInfoFromStat(dir)) + fi = append(fi, fileInfoFromStat(dir)) - d.bufp += int(m) + d.bufp += m n-- } - if n >= 0 && len(result) == 0 { - return result, io.EOF + if n >= 0 && len(fi) == 0 { + return fi, io.EOF } - return result, nil + return fi, nil } func (file *File) readdirnames(n int) (names []string, err error) { @@ -72,205 +71,3 @@ func (file *File) readdirnames(n int) (names []string, err error) {\n }\n return\n }\n -... (以下、dir, qid構造体と関連関数の削除)
-
src/pkg/syscall/dir_plan9.go
:- このファイルが新規作成され、Plan 9のディレクトリ情報に関するすべての低レベルな構造体と関数が定義されました。
// Copyright 2012 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. // Plan 9 directory marshalling. See intro(5). package syscall import "errors" var ( ErrShortStat = errors.New("stat buffer too short") ErrBadStat = errors.New("malformed stat buffer") ) // A Qid represents a 9P server's unique identification for a file. type Qid struct { Path uint64 // the file server's unique identification for the file Vers uint32 // version number for given Path Type uint8 // the type of the file (syscall.QTDIR for example) } // A Dir contains the metadata for a file. type Dir struct { // system-modified data Type uint16 // server type Dev uint32 // server subtype // file data Qid Qid // unique id from server Mode uint32 // permissions Atime uint32 // last read time Mtime uint32 // last write time Length int64 // file length Name string // last element of path Uid string // owner name Gid string // group name Muid string // last modifier name } // ... (nullDir, (*Dir).Null(), (*Dir).Marshal(), UnmarshalDir, pbitX, gbitX, pstring, gstring関数の定義)
-
src/pkg/os/file_plan9.go
:Truncate
,Chmod
,Sync
,Rename
などの関数で、os
パッケージ独自のdir
構造体の代わりにsyscall.Dir
を使用し、pdir(nil, &d)
の代わりにd.Marshal(buf[:])
とsyscall.Fwstat(f.fd, buf[:n])
を呼び出すように変更されました。
--- a/src/pkg/os/file_plan9.go +++ b/src/pkg/os/file_plan9.go @@ -169,13 +169,18 @@ func (f *File) Stat() (fi FileInfo, err error) { // It does not change the I/O offset. // If there is an error, it will be of type *PathError. func (f *File) Truncate(size int64) error { - var d dir - d.Null() + var d syscall.Dir - d.Length = uint64(size) + d.Null() + d.Length = size - if e := syscall.Fwstat(f.fd, pdir(nil, &d)); e != nil { - return &PathError{"truncate", f.name, e} + var buf [syscall.STATFIXLEN]byte + n, err := d.Marshal(buf[:]) + if err != nil { + return &PathError{"truncate", f.name, err} + } + if err = syscall.Fwstat(f.fd, buf[:n]); err != nil { + return &PathError{"truncate", f.name, err} } return nil }
コアとなるコードの解説
このコミットの核となるのは、Plan 9のファイルシステムメタデータを表現するDir
構造体と、それをバイト列に変換(マーシャリング)およびバイト列から復元(アンマーシャリング)する機能がsyscall
パッケージに集約されたことです。
src/pkg/syscall/dir_plan9.go
で定義された主要な要素:
-
type Qid struct { ... }
:Path uint64
: ファイルサーバーがファイルに割り当てる一意の識別子。Vers uint32
:Path
に対するバージョン番号。Type uint8
: ファイルのタイプ(例:syscall.QTDIR
はディレクトリを示す)。- これは、Plan 9におけるファイルの永続的な識別子であり、ファイルの内容が変更されても
Path
は変わらず、Vers
が更新されます。
-
type Dir struct { ... }
:Type uint16
,Dev uint32
: サーバータイプとサブタイプ。システムが変更するデータ。Qid Qid
: 上記のQid
構造体。Mode uint32
: ファイルのパーミッションとタイプ(例: ディレクトリ、シンボリックリンクなど)。Atime uint32
,Mtime uint32
: 最終アクセス時刻と最終更新時刻(Unixエポックからの秒数)。Length int64
: ファイルの長さ(バイト単位)。Name string
: パスの最後の要素(ファイル名またはディレクトリ名)。Uid string
,Gid string
,Muid string
: 所有者、グループ、最終変更者の名前。- この
Dir
構造体は、Plan 9のstat
システムコールが返すファイルメタデータに対応します。
-
func (d *Dir) Null()
:Dir
構造体の各フィールドに、Plan 9プロトコルで「変更しない」ことを意味する特別な「don't care」値を設定します。これは、syscall.Wstat
(ファイル属性の変更)を呼び出す際に、特定の属性のみを変更し、他の属性はそのままにしておくために使用されます。
-
func (d *Dir) Marshal(b []byte) (n int, err error)
:Dir
構造体の内容を、9PプロトコルのStatメッセージ形式のバイト列に変換し、引数b
に書き込みます。- Statメッセージのフォーマットは厳密に定義されており、各フィールド(長さ、タイプ、デバイス、Qid、モードなど)が特定のバイト数でリトルエンディアン形式で格納されます。
- この関数は、必要なバイト数を計算し、
b
の容量が不足している場合はErrShortStat
を返します。
-
func UnmarshalDir(b []byte) (*Dir, error)
:- 9PプロトコルのStatメッセージ形式のバイト列
b
を解析し、対応するDir
構造体を生成して返します。 - バイト列の長さがStatメッセージとして短すぎる場合は
ErrShortStat
を、フォーマットが不正な場合はErrBadStat
を返します。 - この関数は、バイト列から各フィールド(数値、文字列)を読み出すために、
gbitX
やgstring
といったヘルパー関数を内部的に使用します。
- 9PプロトコルのStatメッセージ形式のバイト列
-
ヘルパー関数群 (
pbitX
,gbitX
,pstring
,gstring
):pbit8
,pbit16
,pbit32
,pbit64
: 指定された数値(8, 16, 32, 64ビット)をリトルエンディアン形式でバイトスライスに書き込み、残りのスライスを返します。gbit8
,gbit16
,gbit32
,gbit64
: バイトスライスからリトルエンディアン形式で数値(8, 16, 32, 64ビット)を読み出し、読み出した値と残りのスライスを返します。pstring
: 文字列の長さを16ビットでプレフィックスとして付加し、その後に文字列のバイト列を書き込みます。gstring
: 16ビットの長さプレフィックスを読み出し、その長さ分の文字列をバイトスライスから読み出します。
これらの変更により、os
パッケージはPlan 9固有のデータ形式の解釈や生成のロジックを持つ必要がなくなり、syscall
パッケージがその役割を担うことで、Go言語の標準ライブラリ全体のアーキテクチャがよりクリーンで、OS固有の依存関係が適切に分離されました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語の
os
パッケージ: https://pkg.go.dev/os - Go言語の
syscall
パッケージ: https://pkg.go.dev/syscall - Plan 9 from Bell Labs: https://9p.io/plan9/
- 9Pプロトコル (intro(5)): https://9p.io/magic/man2html/5/intro
参考にした情報源リンク
- Go言語のソースコード (特に
src/pkg/os
とsrc/pkg/syscall
ディレクトリ) - Plan 9のドキュメント (特に9Pプロトコルに関するセクション)
- Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/6157045
- Go言語のIssueトラッカー (該当するIssueがあれば)
- 一般的なオペレーティングシステムの設計原則とシステムプログラミングに関する知識