[インデックス 12009] ファイルの概要
このコミットは、Go言語のビルドツールである cmd/dist におけるディレクトリチェックの堅牢性を向上させるための変更です。具体的には、GOROOT 環境変数の末尾のスラッシュの扱いと、シンボリックリンクの解決に関する問題に対処しています。
コミット
commit 710d0540e27f57b2589552965418b95b88187fa7
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Fri Feb 17 11:29:34 2012 -0500
cmd/dist: make dir check in defaulttarg() more robust
1, strip last path separator from $GOROOT
The user might define GOROOT=/path/to/go/, but then the dir
check in defaulttarg() will always complain the current dir
is not within $GOROOT/src/.\n
2, resolve symlinks in the default goroot
Or if getcwd() returns a fully-resolved path, the check in
defaulttarg() will always fail.
R=rsc
CC=golang-dev
https://golang.org/cl/5649073
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/710d0540e27f57b2589552965418b95b88187fa7
元コミット内容
cmd/dist: make dir check in defaulttarg() more robust
1, strip last path separator from $GOROOT
The user might define GOROOT=/path/to/go/, but then the dir
check in defaulttarg() will always complain the current dir
is not within $GOROOT/src/.
2, resolve symlinks in the default goroot
Or if getcwd() returns a fully-resolved path, the check in
defaulttarg() will always fail.
R=rsc
CC=golang-dev
https://golang.org/cl/5649073
変更の背景
このコミットは、Go言語のビルドシステムにおけるパス解決の堅牢性を高めることを目的としています。Goのビルドプロセスでは、GOROOT 環境変数がGoのインストールディレクトリを指し、その中の src ディレクトリがソースコードの基準点となります。cmd/dist はGoの配布ツールであり、ビルドやインストールに関連するタスクを管理します。
defaulttarg() 関数は、現在の作業ディレクトリが GOROOT/src の下にあるかどうかを検証する役割を担っていました。しかし、この検証ロジックには2つの既知の問題がありました。
GOROOTの末尾スラッシュ問題: ユーザーがGOROOT環境変数を/path/to/go/のように末尾にパスセパレータ(スラッシュ)を付けて定義した場合、defaulttarg()内のディレクトリチェックが常に失敗していました。これは、パスの比較が厳密に行われるため、末尾のスラッシュの有無が不一致を引き起こしていたためです。- シンボリックリンクの解決問題:
getcwd()(現在の作業ディレクトリを取得する関数)がシンボリックリンクを完全に解決したパス(実パス)を返す場合、もしGOROOT自体にシンボリックリンクが含まれていると、defaulttarg()内のチェックが常に失敗していました。これは、GOROOT/srcのパスがシンボリックリンクを解決しないまま構築され、実パスと比較されることで不一致が生じていたためです。
これらの問題は、ユーザーがGoのビルドシステムを特定の環境設定(GOROOT の定義方法やシンボリックリンクの使用)で使用する際に、予期せぬエラーやビルドの失敗を引き起こす可能性がありました。このコミットは、これらの一般的なシナリオに対応し、cmd/dist のパスチェックをより柔軟で堅牢なものにすることで、ユーザーエクスペリエンスを向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
Go言語のビルドシステム (
cmd/dist):cmd/distは、Go言語のソースコードからGoツールチェイン自体をビルドするためのツールです。Goのコンパイラ、リンカ、標準ライブラリなどを構築する際に使用されます。これは、Goのユーザーが日常的にgo buildやgo installを使うのとは異なり、Goの開発者がGoの新しいバージョンをビルドする際や、Goをソースからインストールする際に主に利用されます。cmd/distは、Goのソースツリーの構造に深く依存しており、GOROOT環境変数によって指定されるGoのルートディレクトリ内のsrcディレクトリを基準として動作します。
-
GOROOT環境変数:GOROOTは、Goのインストールディレクトリ(またはGoのソースコードのルートディレクトリ)を指す環境変数です。Goのツールチェインは、この変数を使って標準ライブラリやその他の必要なファイルを見つけます。- 例えば、
/usr/local/goや/home/user/goなどがGOROOTとして設定されることがあります。
-
パスの正規化とシンボリックリンク:
- ファイルシステムにおけるパスは、絶対パス、相対パス、シンボリックリンクなど、様々な形式で表現されます。
- パスの正規化: パスを比較する際には、末尾のスラッシュの有無や、
.(カレントディレクトリ) や..(親ディレクトリ) のような特殊な要素を解決して、一意の形式に変換する「正規化」の概念が重要になります。 - シンボリックリンク (Symbolic Link / Symlink): あるファイルやディレクトリを指し示す別のファイルです。シンボリックリンクを介してアクセスすると、実際にはリンクが指す元のファイルやディレクトリにリダイレクトされます。
getcwd()(Get Current Working Directory): 現在の作業ディレクトリのパスを返します。システムによっては、この関数がシンボリックリンクを解決した「実パス」を返す場合と、シンボリックリンクを含む「論理パス」を返す場合があります。real_path()/xrealwd(): シンボリックリンクを完全に解決し、ファイルシステム上の実際の物理的なパス(正規化された絶対パス)を返す関数です。このコミットでは、xrealwdというGoのビルドシステム内部のヘルパー関数が使用されています。
-
hasprefix()関数:- 文字列が特定のプレフィックス(接頭辞)で始まるかどうかをチェックする一般的な関数です。このコミットでは、現在の作業ディレクトリのパスが
GOROOT/srcのパスで始まるかどうかを検証するために使用されています。パスの比較において、末尾のスラッシュやシンボリックリンクの解決が適切に行われていないと、このhasprefix()チェックが誤った結果を返す可能性があります。
- 文字列が特定のプレフィックス(接頭辞)で始まるかどうかをチェックする一般的な関数です。このコミットでは、現在の作業ディレクトリのパスが
これらの概念は、ファイルシステム操作、パス解決、およびGoのような複雑なビルドシステムがどのようにパスを解釈し、検証するかを理解する上で不可欠です。
技術的詳細
このコミットは、src/cmd/dist/build.c ファイル内の init() 関数と defaulttarg() 関数に焦点を当てています。
init() 関数における GOROOT の処理
変更前は、GOROOT 環境変数を取得した後、末尾のスラッシュを削除する処理がありませんでした。
// 変更前
if(b.len > 0)
goroot = btake(&b);
変更後は、GOROOT の長さが2以上で、かつ末尾がパスセパレータ(/)である場合に、そのセパレータを削除するロジックが追加されました。これにより、ユーザーが GOROOT=/path/to/go/ のように設定しても、内部的には /path/to/go として扱われるようになります。
// 変更後
if(b.len > 0) {
// if not "/", then strip trailing path separator
if(b.len >= 2 && b.p[b.len - 1] == slash[0])
b.len--;
goroot = btake(&b);
}
この変更は、GOROOT のパスが常に正規化された形式(末尾にスラッシュがない形式)で goroot 変数に格納されることを保証し、後続のパス比較処理での不整合を防ぎます。
defaulttarg() 関数におけるディレクトリチェックの改善
defaulttarg() 関数は、現在の作業ディレクトリが GOROOT/src の下にあることを確認する主要なロジックを含んでいます。
変更前は、現在の作業ディレクトリ (pwd) を取得し、GOROOT/src/ のパスを構築し、単純に hasprefix() で比較していました。
// 変更前
xgetwd(&pwd); // get current working directory
p = btake(&pwd);
bpathf(&src, "%s/src/", goroot); // construct GOROOT/src/ path
if(!hasprefix(p, bstr(&src))) // check if current directory starts with GOROOT/src/
fatal("current directory %s is not under %s", p, bstr(&src));
p += src.len;
このアプローチには、前述のシンボリックリンクの問題がありました。xgetwd() が実パスを返す一方で、bstr(&src) はシンボリックリンクを解決しない論理パスを返す可能性があるため、比較が失敗することがありました。
変更後は、この問題を解決するために xrealwd() 関数が導入されました。
// 変更後
// xgetwd might return a path with symlinks fully resolved, and if
// there happens to be symlinks in goroot, then the hasprefix test
// will never succeed. Instead, we use xrealwd to get a canonical
// goroot/src before the comparison to avoid this problem.
xgetwd(&pwd);
p = btake(&pwd);
bpathf(&src, "%s/src/", goroot);
xrealwd(&real_src, bstr(&src)); // Get the real path of GOROOT/src/
if(!hasprefix(p, bstr(&real_src))) // Compare current directory with the real path of GOROOT/src/
fatal("current directory %s is not under %s", p, bstr(&real_src));
p += real_src.len;
// guard againt xrealwd return the directory without the trailing /
if(*p == slash[0])
p++;
ここで重要なのは、xrealwd(&real_src, bstr(&src)) の呼び出しです。これは、GOROOT/src のパス(bstr(&src))を、シンボリックリンクを解決した「実パス」に変換し、real_src に格納します。その後、hasprefix() は、現在の作業ディレクトリの実パス (p) と、GOROOT/src の実パス (bstr(&real_src)) を比較します。これにより、シンボリックリンクの有無に関わらず、正確なパス比較が可能になります。
また、xrealwd が末尾のスラッシュなしでディレクトリを返す可能性に対するガードとして、if(*p == slash[0]) p++; という行が追加されています。これは、real_src.len で p を進めた後、もし p がまだスラッシュを指している場合(つまり、xrealwd が末尾のスラッシュを削除してパスを返した場合)、さらに1文字進めて、実際のサブパスの開始位置を正確に指すようにするためのものです。
これらの変更により、cmd/dist は、ユーザーが GOROOT をどのように設定しているか、あるいはファイルシステムにシンボリックリンクが存在するかに関わらず、現在の作業ディレクトリがGoのソースツリーの正しい場所にあるかをより堅牢に判断できるようになりました。
コアとなるコードの変更箇所
変更は src/cmd/dist/build.c ファイルに集中しています。
diff --git a/src/cmd/dist/build.c b/src/cmd/dist/build.c
index f31c83ea7a..6cb33ab10f 100644
--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -77,8 +77,12 @@ init(void)\n \tbinit(&b);\n \n \txgetenv(&b, "GOROOT");\n-\tif(b.len > 0)\n+\tif(b.len > 0) {\n+\t\t// if not "/", then strip trailing path separator\n+\t\tif(b.len >= 2 && b.p[b.len - 1] == slash[0])\n+\t\t\tb.len--;\n \t\tgoroot = btake(&b);\n+\t}\n \n \txgetenv(&b, "GOBIN");\n \tif(b.len == 0)\n@@ -1373,20 +1377,30 @@ static char*\n defaulttarg(void)\n {\n \tchar *p;\n-\tBuf pwd, src;\n+\tBuf pwd, src, real_src;\n \t\n \tbinit(&pwd);\n \tbinit(&src);\n+\tbinit(&real_src);\n \n+\t// xgetwd might return a path with symlinks fully resolved, and if\n+\t// there happens to be symlinks in goroot, then the hasprefix test\n+\t// will never succeed. Instead, we use xrealwd to get a canonical\n+\t// goroot/src before the comparison to avoid this problem.\n \txgetwd(&pwd);\n \tp = btake(&pwd);\n \tbpathf(&src, "%s/src/", goroot);\n-\tif(!hasprefix(p, bstr(&src)))\n-\t\tfatal("current directory %s is not under %s", p, bstr(&src));\n-\tp += src.len;\n+\txrealwd(&real_src, bstr(&src));\n+\tif(!hasprefix(p, bstr(&real_src)))\n+\t\tfatal("current directory %s is not under %s", p, bstr(&real_src));\n+\tp += real_src.len;\n+\t// guard againt xrealwd return the directory without the trailing /\n+\tif(*p == slash[0])\n+\t\tp++;\n \n \tbfree(&pwd);\n \tbfree(&src);\n+\tbfree(&real_src);\n \t\n \treturn p;\n }\n```
## コアとなるコードの解説
### `init()` 関数内の変更
```c
+\tif(b.len > 0) {\n+\t\t// if not "/", then strip trailing path separator\n+\t\tif(b.len >= 2 && b.p[b.len - 1] == slash[0])\n+\t\t\tb.len--;\n \t\tgoroot = btake(&b);\n+\t}
このコードブロックは、GOROOT 環境変数から取得したパス b の末尾にパスセパレータ(/)が存在する場合、それを削除する処理です。
b.len >= 2: パスの長さが少なくとも2文字以上であることを確認します。これは、ルートディレクトリ/のような単一のセパレータパスを誤って処理しないためのガードです。b.p[b.len - 1] == slash[0]: パスの最後の文字がパスセパレータであるかをチェックします。slash[0]はシステムに応じたパスセパレータ(Unix系では/)を表します。b.len--: 条件が真の場合、パスの長さを1減らすことで、末尾のセパレータを論理的に削除します。これにより、goroot変数には常に末尾にスラッシュがない正規化されたパスが格納されます。
defaulttarg() 関数内の変更
+\tBuf pwd, src, real_src;\n \t\n \tbinit(&pwd);\n \tbinit(&src);\n+\tbinit(&real_src);\n \n+\t// xgetwd might return a path with symlinks fully resolved, and if\n+\t// there happens to be symlinks in goroot, then the hasprefix test\n+\t// will never succeed. Instead, we use xrealwd to get a canonical\n+\t// goroot/src before the comparison to avoid this problem.\n \txgetwd(&pwd);\n \tp = btake(&pwd);\n \tbpathf(&src, "%s/src/", goroot);\n+\txrealwd(&real_src, bstr(&src));\n+\tif(!hasprefix(p, bstr(&real_src)))\n+\t\tfatal("current directory %s is not under %s", p, bstr(&real_src));\n+\tp += real_src.len;\n+\t// guard againt xrealwd return the directory without the trailing /\n+\tif(*p == slash[0])\n+\t\tp++;
この部分が、シンボリックリンクの問題を解決する主要な変更です。
Buf real_src;: 新たにreal_srcというBuf型の変数が宣言され、初期化されます。これはGOROOT/srcの実パスを格納するために使用されます。xrealwd(&real_src, bstr(&src));: ここが最も重要な変更点です。bstr(&src)は、gorootと"/src/"を結合して構築されたGOROOT/src/の論理パスです。xrealwdは、この論理パスを受け取り、ファイルシステム上のシンボリックリンクをすべて解決した「実パス」(canonical path)をreal_srcに書き込みます。
if(!hasprefix(p, bstr(&real_src))):hasprefix関数による比較が、現在の作業ディレクトリの実パスpと、GOROOT/srcの実パスbstr(&real_src)の間で行われるようになりました。これにより、シンボリックリンクの有無によるパスの不一致が解消されます。p += real_src.len;:pは、GOROOT/srcの部分を除いた、現在の作業ディレクトリの相対パスの開始位置を指すように更新されます。if(*p == slash[0]) p++;:xrealwdが返す実パスが末尾にスラッシュを含まない場合があるため、pがまだスラッシュを指している場合は、さらに1文字進めて、正確な相対パスの開始位置を確保します。これは、パスの正規化をさらに確実にするためのものです。
これらの変更により、cmd/dist は、GOROOT の設定方法やファイルシステム上のシンボリックリンクの存在に関わらず、現在の作業ディレクトリがGoのソースツリーの正しい場所にあるかをより正確かつ堅牢に判断できるようになりました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goのソースコードリポジトリ: https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/
参考にした情報源リンク
- Goのコミットメッセージとコード変更: https://github.com/golang/go/commit/710d0540e27f57b2589552965418b95b88187fa7
- Goのコードレビュー (CL 5649073): https://golang.org/cl/5649073
- Unix/Linuxにおけるパスとシンボリックリンクの概念に関する一般的な情報源 (例: Wikipedia, man pages for
readlink,realpath,getcwd) - C言語における文字列操作とパス操作に関する一般的な情報源 (例:
string.hの関数、ファイルシステムAPIのドキュメント) - Goのビルドシステムに関する一般的な議論やドキュメント (Goの公式ブログやメーリングリストのアーカイブなど)