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

[インデックス 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言語の標準ライブラリにおけるコードの責務分離とモジュール性の向上です。

  1. 責務の分離: osパッケージは、オペレーティングシステムに依存しない汎用的なファイルシステム操作(ファイルの読み書き、ディレクトリのリスト、ファイル情報の取得など)を提供することを目的としています。一方、syscallパッケージは、特定のOSのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。Plan 9のディレクトリ情報のマーシャリングは、Plan 9の9Pプロトコルという特定のOSの通信規約に深く関連する低レベルな処理です。これをosパッケージ内に置くことは、osパッケージの抽象度を下げ、特定のOSの実装詳細に依存させることになります。
  2. コードの整理と保守性: Plan 9のディレクトリ情報のマーシャリングコードは、バイト列と構造体の間の変換という、システムコール層に近い処理です。これをsyscallパッケージに移動することで、関連する低レベルなコードが一箇所に集約され、コードの可読性、保守性、およびテストのしやすさが向上します。
  3. 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パッケージへ移管した点にあります。

具体的には、以下の変更が行われました。

  1. 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プロトコルのデータ形式に準拠しています。
    • エラー定数ErrShortStatErrBadStatsyscallパッケージに定義されました。
  2. src/pkg/os/dir_plan9.goの変更:

    • 以前このファイルに存在したdir構造体、qid構造体、pdir関数(マーシャリング)、unmarshalDir関数(アンマーシャリング)、および関連するヘルパー関数(gbitX, pbitX, gstring, pstring)がすべて削除されました。
    • (*File).readdirメソッド内で、syscall.UnmarshalDirが使用されるように変更されました。これにより、osパッケージはPlan 9のディレクトリ情報の解析をsyscallパッケージに委譲する形になりました。
    • エラー処理もsyscall.ErrShortStatを使用するように更新されました。
  3. src/pkg/os/file_plan9.goの変更:

    • Truncate, Chmod, Sync, Rename, Chtimesといったファイル操作関数において、osパッケージ独自のdir構造体ではなく、syscall.Dir構造体を使用するように変更されました。
    • これらの関数内で、syscall.Fwstatsyscall.Wstatを呼び出す際に、syscall.DirMarshalメソッドを使用してStatメッセージのバイト列を生成するようになりました。これにより、osパッケージはファイル属性の変更要求を、syscallパッケージが提供する低レベルなマーシャリング機能を通じてPlan 9システムに伝達します。
  4. src/pkg/os/stat_plan9.goの変更:

    • sameFile, fileInfoFromStat, dirstat関数において、引数や戻り値の型が*dirから*syscall.Dirに変更されました。
    • dirstat関数内で、syscall.UnmarshalDirが使用されるようになりました。これにより、ファイル情報の取得と解析もsyscallパッケージに委譲されます。
  5. src/pkg/syscall/exec_plan9.goの変更:

    • 重複していたgbit16gstring関数が削除されました。これらの関数はsyscall/dir_plan9.goに移動され、一元化されました。
    • readdirnames関数内で、gstringの呼び出しが新しいsyscall.gstring(戻り値にokブール値が追加された)を使用するように更新され、エラー処理もsyscall.ErrBadStatを使用するように変更されました。

この変更により、osパッケージはPlan 9固有の低レベルなプロトコル処理から解放され、よりクリーンで汎用的なファイルシステムAPIを提供できるようになりました。Plan 9のStatメッセージの構造やそのバイト列への変換ロジックは、syscallパッケージ内にカプセル化され、OS固有の実装詳細として適切に配置されました。

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

このコミットのコアとなる変更は、主に以下のファイルに集中しています。

  1. 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構造体と関連関数の削除)
    
  2. 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関数の定義)
    
  3. 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を返します。
    • この関数は、バイト列から各フィールド(数値、文字列)を読み出すために、gbitXgstringといったヘルパー関数を内部的に使用します。
  • ヘルパー関数群 (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言語のソースコード (特にsrc/pkg/ossrc/pkg/syscallディレクトリ)
  • Plan 9のドキュメント (特に9Pプロトコルに関するセクション)
  • Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/6157045
  • Go言語のIssueトラッカー (該当するIssueがあれば)
  • 一般的なオペレーティングシステムの設計原則とシステムプログラミングに関する知識