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

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

このコミットは、Go言語のビルドシステムにおけるアーカイブファイル(.aファイル)内のファイル名の長さを、以前の64バイトから16バイトに戻す変更です。これは、Goのコンパイルプロセスが進化し、個々のGoソースファイルがアーカイブ内で一意の名前を持つ必要がなくなったこと、およびPlan 9オペレーティングシステムとの互換性を改善するために行われました。

コミット

commit 7b04471dfaaddc49efad470275fcc45546870a73
Author: Russ Cox <rsc@golang.org>
Date:   Tue Nov 1 00:29:16 2011 -0400

    gopack: change archive file name length back to 16
    
    This CL grew the archive file name length from 16 to 64:
    
            changeset:   909:58574851d792
            user:        Russ Cox <rsc@golang.org>
            date:        Mon Oct 20 13:53:56 2008 -0700
    
    Back then, every x.go file in a package became an x.6 file
    in the archive.  It was important to be able to allow the
    use of long Go source file names, hence the increase in size.
    
    Today, all Go source files compile into a single _go_.6 file
    regardless of their names, so the archive file name length
    no longer needs to be long.  The longer name causes some
    problems on Plan 9, where the native archive format is the
    same but with 16-byte names, so revert back to 16.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5333050

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

https://github.com/golang/go/commit/7b04471dfaaddc49efad470275fcc45546870a73

元コミット内容

このコミットは、Goのビルドツールであるgopackが生成するアーカイブファイル(通常は.a拡張子を持つ静的ライブラリ)内のファイル名(メンバー名)の長さを、64バイトから16バイトに戻すものです。

コミットメッセージによると、以前(2008年10月20日の変更セット909:58574851d792)は、パッケージ内の各.goファイルがアーカイブ内でx.6のような個別のファイルとして扱われていたため、長いGoソースファイル名をサポートするためにファイル名の長さを16バイトから64バイトに拡張する必要がありました。

しかし、このコミットが作成された時点(2011年11月)では、Goのコンパイルプロセスが変更され、すべてのGoソースファイルがパッケージ内で単一の_go_.6ファイルにコンパイルされるようになりました。この変更により、個々のソースファイル名がアーカイブ内のメンバー名として直接使用される必要がなくなり、ファイル名の長さを長く保つ必要がなくなりました。

さらに、ファイル名の長さが長いと、Plan 9オペレーティングシステム上で問題が発生することが判明しました。Plan 9のネイティブアーカイブフォーマットはGoが使用しているものと似ていますが、ファイル名の長さが16バイトに制限されています。この互換性の問題を解決するためにも、ファイル名の長さを16バイトに戻すことが決定されました。

変更の背景

この変更の背景には、主に以下の2つの要因があります。

  1. Goコンパイルプロセスの進化: Go言語の初期段階では、パッケージ内の各Goソースファイル(例: foo.go, bar.go)が、コンパイル後にそれぞれfoo.6, bar.6といった形で個別のオブジェクトファイルとしてアーカイブ(静的ライブラリ)に格納されていました。このため、開発者が長いファイル名を使用した場合でも、それらがアーカイブ内で適切に表現されるように、アーカイブメンバー名の長さを十分に確保する必要がありました。これが、以前にファイル名長を16バイトから64バイトに拡張した理由です。 しかし、このコミットがなされた時期には、Goのビルドシステムが進化し、パッケージ内のすべてのGoソースファイルがコンパイルされて、最終的に単一の_go_.6という名前のオブジェクトファイルとしてアーカイブに格納されるようになりました。これにより、個々のGoソースファイル名がアーカイブ内のメンバー名として直接使用される必要がなくなり、長いファイル名をサポートするための64バイトの長さが不要になりました。

  2. Plan 9との互換性: Go言語は、その設計思想や開発チームのルーツにおいて、Bell LabsのPlan 9オペレーティングシステムから大きな影響を受けています。そのため、Goのツールチェインや内部フォーマットには、Plan 9の慣習やフォーマットが採用されている部分が多くあります。 arアーカイブフォーマットもその一つです。Plan 9のネイティブなarフォーマットでは、アーカイブメンバー名の長さが16バイトに固定されています。Goのgopackが64バイトのファイル名を使用していると、Plan 9環境でGoの生成したアーカイブファイルを処理する際に互換性の問題が生じました。この問題を解消し、Plan 9上でのGoのツールチェインの動作を円滑にするために、ファイル名の長さをPlan 9の標準に合わせて16バイトに戻す必要がありました。

これらの理由から、不要になった長いファイル名長を元に戻し、同時にPlan 9との互換性を確保することが、このコミットの目的となりました。

前提知識の解説

1. Go言語の初期のビルドプロセスとgopack

Go言語の初期のビルドシステムでは、gopackというツールが重要な役割を担っていました。gopackは、Goのソースファイルをコンパイルして生成されたオブジェクトファイル(.6拡張子を持つファイル)を、Unix系のシステムで広く使われているar(archiver)フォーマットのアーカイブファイル(通常は.a拡張子を持つ静的ライブラリ)にまとめるためのツールでした。

当時のGoのコンパイルフローは、おおよそ以下のようでした。

  1. go tool 6g(Goコンパイラ)が.goソースファイルをコンパイルし、.6オブジェクトファイルを生成する。
  2. go tool packgopack)が、これらの.6オブジェクトファイルをまとめて.aアーカイブファイルを生成する。
  3. go tool 6l(Goリンカ)が、これらの.aアーカイブファイルと他のライブラリをリンクして実行可能ファイルを生成する。

このコミットの時点では、gopackはまだGoのビルドプロセスの一部として機能していましたが、後にgo buildコマンドの内部処理に統合され、ユーザーが直接意識することはなくなりました。

2. arアーカイブフォーマット

ar(archiver)は、複数のファイルを単一のアーカイブファイルにまとめるためのUnix系のユーティリティおよびファイルフォーマットです。主に静的ライブラリ(.aファイル)の作成に使用されます。arアーカイブは、ヘッダとそれに続くメンバー(アーカイブに格納された個々のファイル)で構成されます。

各メンバーは、そのファイル名、サイズ、パーミッション、タイムスタンプなどの情報を含む独自のヘッダを持っています。このコミットで問題となっているのは、このメンバーヘッダ内の「ファイル名」フィールドの長さです。

標準的なarフォーマット(特にBSDやSystem Vのバリアント)では、メンバー名のフィールド長は通常16バイトに制限されています。長いファイル名を格納するためには、特別な拡張(例えば、ファイル名が16バイトを超える場合は、そのファイル名をアーカイブの末尾に別途格納し、ヘッダのファイル名フィールドにはそのオフセットを記述する)が必要になることがあります。

3. Plan 9オペレーティングシステム

Plan 9 from Bell Labsは、Unixの後継として設計された分散オペレーティングシステムです。ファイルシステムを中心とした設計思想、UTF-8の採用、9Pプロトコルによるネットワーク透過性など、多くの革新的なアイデアを導入しました。Go言語の開発者の一部はPlan 9の開発にも携わっており、Go言語の設計にはPlan 9の哲学が色濃く反映されています。

Plan 9のツールチェインやファイルフォーマットは、Goの初期のツールチェインに大きな影響を与えました。特に、Goのオブジェクトファイル(.6)やアーカイブファイル(.a)のフォーマットは、Plan 9のそれと非常に似ています。このコミットで言及されているように、Plan 9のネイティブなarフォーマットは、ファイル名の長さに16バイトの制限を持っています。

4. Goのコンパイルにおける_go_.6ファイル

Go言語のコンパイルプロセスは、時間の経過とともに進化してきました。初期のGoでは、パッケージ内の各Goソースファイル(例: main.go, utils.go)が個別にコンパイルされ、それぞれmain.6, utils.6といったオブジェクトファイルが生成され、これらがgopackによってアーカイブにまとめられていました。この方式では、アーカイブ内の各メンバーが元のGoソースファイル名に対応するため、長いソースファイル名をサポートするためにアーカイブメンバー名の長さを長くする必要がありました。

しかし、このコミットの時点では、Goのコンパイル戦略が変更されていました。現在では、パッケージ内のすべてのGoソースファイルは、コンパイル時にまとめて処理され、最終的に単一のオブジェクトファイル(通常は_go_.6という名前)としてアーカイブに格納されます。この_go_.6ファイルには、パッケージ内のすべてのGoソースコードから生成されたコンパイル済みコードが含まれます。

この変更により、アーカイブ内のメンバー名は常に_go_.6となるため、元のGoソースファイル名が何であっても、アーカイブメンバー名の長さを長く保つ必要がなくなりました。これは、アーカイブフォーマットの設計を簡素化し、互換性の問題を解決する上で重要な変更でした。

技術的詳細

このコミットの技術的詳細は、主にarアーカイブフォーマットのヘッダ構造と、Goのツールチェインがそのヘッダをどのように解釈・生成するかに焦点を当てています。

arアーカイブの各メンバーは、ar_hdrという構造体で定義されるヘッダを持ちます。このヘッダには、メンバーのファイル名、サイズ、所有者、グループ、パーミッション、最終更新日時などのメタデータが含まれます。

このコミットで変更されたのは、include/ar.hで定義されているSARNAMEマクロの値です。

  • 変更前: #define SARNAME 64
  • 変更後: #define SARNAME 16

SARNAMEは、ar_hdr構造体内のファイル名フィールドの長さを定義しています。この値を64から16に戻すことで、Goが生成するアーカイブファイルのメンバー名フィールドが16バイトに制限されるようになります。

この変更は、Goのリンカ(src/cmd/ld/lib.c)と、実験的な型システムのエクスポートデータ処理(src/pkg/exp/types/exportdata.go)に影響を与えます。

src/cmd/ld/lib.cにおける変更

src/cmd/ld/lib.cnextar関数は、アーカイブファイルから次のメンバーヘッダを読み込む役割を担っています。この関数は、読み込んだヘッダのサイズに基づいて、それが標準的なヘッダ(SAR_HDR)であるか、あるいは古いPlan 9形式のヘッダであるかを判断していました。

変更前は、SARNAMEが64バイトであったため、SAR_HDRのサイズもそれに応じて大きくなっていました。古いPlan 9のアーカイブは16バイトのファイル名を使用していたため、Goのリンカは、読み込んだヘッダのサイズがSAR_HDR-SARNAME+16(つまり、Goの64バイト名ヘッダから48バイト減らしたサイズ)である場合に、それを古いPlan 9形式のヘッダとして特別に処理していました。この処理には、ヘッダ内のファイル名部分を適切にコピーし直すロジックが含まれていました。

変更後、SARNAMEが16バイトに戻されたことで、Goが生成するアーカイブのヘッダサイズはPlan 9の標準と一致するようになりました。これにより、古いPlan 9形式のヘッダを特別に扱う必要がなくなり、nextar関数内の条件分岐と複雑なmemmove処理が削除され、コードが大幅に簡素化されました。リンカは、ヘッダのサイズがSAR_HDRと一致することだけを確認すればよくなりました。

src/pkg/exp/types/exportdata.goにおける変更

src/pkg/exp/types/exportdata.goreadGopackHeader関数は、gopackが生成するアーカイブのヘッダを読み込み、その中のパッケージ名とサイズを抽出するために使用されていました。この関数は、ヘッダの特定のオフセットからファイル名やサイズ情報を読み取るために、SARNAMEの値に依存していました。

変更前は、ヘッダの読み取りバッファサイズや、ファイル名とサイズ情報を抽出するためのオフセット計算に64という値がハードコードされていました。これは、以前のSARNAMEの値(64)に対応していました。

変更後、SARNAMEが16バイトに戻されたことに合わせて、これらのハードコードされた64という値が16に修正されました。これにより、readGopackHeader関数が新しい16バイトのファイル名長を持つアーカイブヘッダを正しく解析できるようになります。具体的には、ヘッダのバッファサイズが小さくなり、ファイル名とサイズ情報の開始オフセットが調整されました。

これらの変更は、Goのビルドシステム全体でarアーカイブフォーマットのファイル名長に関する一貫性を保ち、特にPlan 9との互換性を確保するために不可欠でした。

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

このコミットにおける主要なコード変更は、以下の3つのファイルにわたります。

  1. include/ar.h:

    --- a/include/ar.h
    +++ b/include/ar.h
    @@ -32,7 +32,7 @@
     #define	SARMAG	8
     
     #define	ARFMAG	"`\n"
    -#define SARNAME	64
    +#define	SARNAME	16
     
     struct	ar_hdr
     {
    
    • SARNAMEマクロの定義が64から16に変更されました。これは、アーカイブメンバー名の最大長を定義する最も根本的な変更です。
  2. src/cmd/ld/lib.c:

    --- a/src/cmd/ld/lib.c
    +++ b/src/cmd/ld/lib.c
    @@ -313,15 +313,9 @@ nextar(Biobuf *bp, int off, struct ar_hdr *a)
     			return 0;
     		return -1;
     	}
    -	if(r == SAR_HDR) {
    -		memmove(a, buf, SAR_HDR);
    -	} else if (r == SAR_HDR-SARNAME+16) {	// old Plan 9
    -		memset(a->name, ' ', sizeof a->name);
    -		memmove(a, buf, 16);
    -		memmove((char*)a+SARNAME, buf+16, SAR_HDR-SARNAME);
    -	} else {	// unexpected
    +	if(r != SAR_HDR)
     		return -1;
    -	}
    +	memmove(a, buf, SAR_HDR);
     	if(strncmp(a->fmag, ARFMAG, sizeof a->fmag))\
     		return -1;
     	arsize = strtol(a->size, 0, 0);
    
    • nextar関数内の、アーカイブヘッダの読み込みと解析ロジックが簡素化されました。
    • 以前は、読み込んだヘッダのサイズがSAR_HDRと一致する場合と、SAR_HDR-SARNAME+16(古いPlan 9形式)と一致する場合で異なる処理を行っていましたが、この複雑な条件分岐が削除されました。
    • 新しいコードでは、読み込んだサイズrSAR_HDRと一致しない場合はエラーとし、一致する場合は単純にmemmove(a, buf, SAR_HDR)でヘッダをコピーするだけになりました。これは、SARNAMEが16バイトに戻ったことで、Goが生成するヘッダとPlan 9のヘッダが同じ形式になったため、特別な処理が不要になったことを示しています。
  3. src/pkg/exp/types/exportdata.go:

    --- a/src/pkg/exp/types/exportdata.go
    +++ b/src/pkg/exp/types/exportdata.go
    @@ -17,7 +17,7 @@ import (
     
     func readGopackHeader(buf *bufio.Reader) (name string, size int, err os.Error) {
     	// See $GOROOT/include/ar.h.
    -	hdr := make([]byte, 64+12+6+6+8+10+2)
    +	hdr := make([]byte, 16+12+6+6+8+10+2)
     	_, err = io.ReadFull(buf, hdr)
     	if err != nil {
     		return
    @@ -25,13 +25,13 @@ func readGopackHeader(buf *bufio.Reader) (name string, size int, err os.Error) {
     	if trace {
     		fmt.Printf("header: %s", hdr)
     	}
    -	s := strings.TrimSpace(string(hdr[64+12+6+6+8:][:10]))
    +	s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
     	size, err = strconv.Atoi(s)
     	if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
     		err = os.NewError("invalid archive header")
     		return
     	}
    -	name = strings.TrimSpace(string(hdr[:64]))
    +	name = strings.TrimSpace(string(hdr[:16]))
     	return
     }
     
    
    • readGopackHeader関数内で、アーカイブヘッダを読み込むためのバッファサイズと、ヘッダ内のファイル名およびサイズ情報を抽出するためのオフセット計算が修正されました。
    • make([]byte, 64+...)make([]byte, 16+...)に変更され、ヘッダの読み取りサイズがSARNAMEの新しい値に合わせて調整されました。
    • hdr[64+12+6+6+8:][:10]hdr[16+12+6+6+8:][:10]に変更され、サイズ情報のオフセットが調整されました。
    • hdr[:64]hdr[:16]に変更され、ファイル名(name)を抽出する際の範囲が調整されました。

コアとなるコードの解説

include/ar.hの変更

SARNAMEは、arアーカイブフォーマットのヘッダ構造体ar_hdr内で、メンバーのファイル名を格納するフィールドの長さを定義しています。この値を64から16に変更することは、Goが生成するアーカイブファイルが、ファイル名に最大16バイトしか使用しないことを意味します。これは、Plan 9のarフォーマットの標準的なファイル名長と一致します。

src/cmd/ld/lib.cの変更

nextar関数は、Goのリンカが静的ライブラリ(.aファイル)を読み込む際に、その中の個々のオブジェクトファイル(メンバー)のヘッダを解析するために使用されます。

変更前のコードは、Goが以前に64バイトのファイル名長を使用していた時期の名残です。この時期、Goのリンカは、Plan 9のシステムで生成された可能性のある16バイトのファイル名長を持つ古いアーカイブも処理できるように、特別なロジックを持っていました。具体的には、読み込んだヘッダのサイズがGoの標準(SAR_HDR)と異なる場合(特にSAR_HDR-SARNAME+16、つまりGoの64バイト名ヘッダから48バイト減らしたサイズ)は、それを古いPlan 9形式のヘッダとみなし、ファイル名部分を適切に調整してコピーしていました。

SARNAMEが16バイトに戻されたことで、Goが生成するアーカイブのヘッダ構造はPlan 9の標準と完全に一致するようになりました。これにより、リンカはもはや「古いPlan 9形式」のヘッダを特別扱いする必要がなくなりました。読み込んだヘッダのサイズがSAR_HDR(新しい16バイト名に対応したサイズ)と一致することだけを確認すればよくなり、コードが大幅に簡素化され、保守性が向上しました。これは、GoのツールチェインがPlan 9のarフォーマットと完全に互換性を持つようになったことを示しています。

src/pkg/exp/types/exportdata.goの変更

readGopackHeader関数は、Goのパッケージのエクスポートデータ(他のパッケージから参照可能な型や関数などの情報)を含むアーカイブのヘッダを読み取るために使用されます。この関数は、ヘッダのバイト列からファイル名(パッケージ名)とサイズ情報を抽出します。

変更前のコードでは、ヘッダのバッファサイズや、ファイル名とサイズ情報を抽出するためのオフセット計算に、以前のSARNAMEの値である64がハードコードされていました。これは、ヘッダの構造が64バイトのファイル名フィールドを前提としていたためです。

SARNAMEが16バイトに変更されたことに伴い、この関数内のハードコードされた64という値が16に修正されました。これにより、readGopackHeaderは、新しい16バイトのファイル名長を持つアーカイブヘッダから、正しいオフセットでパッケージ名とサイズ情報を正確に読み取ることができるようになりました。この変更は、Goのビルドシステム全体でアーカイブフォーマットの定義と解析ロジックの一貫性を保つために必要でした。

関連リンク

参考にした情報源リンク

  • ar (Unix): https://en.wikipedia.org/wiki/Ar_(Unix)
  • Plan 9 from Bell Labs: https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs
  • Go言語の初期のビルドプロセスに関する議論やドキュメント: (特定のURLは提供できませんが、Goの公式ブログやメーリングリストのアーカイブ、初期のGoのソースコードリポジトリなどを参照しました。)
  • Go言語のコンパイルとパッケージングに関する情報: (Goの公式ドキュメントやGoのソースコード内の関連ファイルを参照しました。)
  • gopackに関する情報: (Goの初期のツールチェインに関するドキュメントやソースコードを参照しました。)
  • _go_.6ファイルに関する情報: (Goのコンパイルプロセスに関する技術記事やGoのソースコードを参照しました。)
  • ar.hヘッダファイル: (Unix系システムの標準的なar.hファイルや、Goのソースコード内のinclude/ar.hを参照しました。)
  • src/cmd/ld/lib.c: (Goのリンカのソースコードを参照しました。)
  • src/pkg/exp/types/exportdata.go: (Goの実験的な型システムのエクスポートデータ処理に関するソースコードを参照しました。)
  • Russ Coxの過去のコミット: (コミットメッセージで言及されている変更セット909:58574851d792のコミットログを参照しました。)