[インデックス 16882] ファイルの概要
このコミットは、Go言語のツールチェインの一部である cmd/pack
の src/cmd/pack/ar.c
ファイルに対する変更です。cmd/pack
は、Goのビルドシステムが内部的に使用するアーカイブファイル(通常 .a
拡張子を持つ)を操作するためのツールであり、Unixの ar
コマンドの簡易版として機能します。このファイルは、特にアーカイブメンバーの読み込みと、その際にファイルパスのプレフィックスを処理するロジックを含んでいます。
コミット
- コミットハッシュ:
a79e125b2f4c064f54689705fe2dd70899012df5
- Author: Alex Brainman alex.brainman@gmail.com
- Date: Fri Jul 26 16:56:24 2013 +1000
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a79e125b2f4c064f54689705fe2dd70899012df5
元コミット内容
cmd/pack: support removing of leading file prefix on windows
Fixes #5550
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/11904043
変更の背景
このコミットの主な目的は、Windows環境において cmd/pack
ツールがアーカイブファイル(.a
)を処理する際に、ファイルパスの先頭にあるプレフィックス(例: C:\
や c:/
)を正しく除去できるようにすることです。
Goのビルドプロセスでは、コンパイルされたオブジェクトファイルなどがアーカイブファイルにまとめられます。このアーカイブファイル内に格納されるファイルパスは、通常、ビルド環境のルートからの相対パスとして扱われることが期待されます。しかし、Windows環境では、絶対パスがドライブレター(例: C:
)とそれに続くパス区切り文字(\
または /
)で始まることが一般的です。
従来の cmd/pack
は、このようなWindows特有のパスプレフィックスを適切に処理できず、アーカイブ内のファイルパスが意図しない形式で格納されたり、後続の処理で問題を引き起こしたりする可能性がありました。特に、Goのツールチェインが内部的にパスを正規化する際に、これらのプレフィックスが邪魔になることが考えられます。
この問題は、GoのIssue #5550として報告されており、このコミットはその修正を目的としています。Issue #5550の具体的な内容は不明ですが、コミットメッセージから、Windows環境でのパス処理の不整合が原因であったと推測されます。この修正により、Windows上でのGoのビルドプロセスにおけるアーカイブファイルの取り扱いがより堅牢になり、クロスプラットフォームでの互換性が向上します。
前提知識の解説
Goのcmd/pack
ツールとアーカイブファイル(.aファイル)
cmd/pack
は、Go言語の標準ツールセットに含まれるユーティリティの一つで、主にGoのビルドシステムが内部的に使用します。その役割は、オブジェクトファイル(.o
)やその他のコンパイル済みコードをまとめて、ライブラリとして機能するアーカイブファイル(.a
)を作成・操作することです。
アーカイブファイルは、複数のファイルを一つのファイルにまとめるための形式であり、Unix系のシステムで広く使われている ar
コマンドによって生成・管理されます。Goの cmd/pack
は、この ar
形式のアーカイブを扱うためのGo独自の簡易実装と考えることができます。
Goのビルドプロセスでは、ソースコードがコンパイルされると、その結果として生成されるオブジェクトファイルが cmd/pack
を通じて .a
ファイルに格納されます。これにより、リンカは必要なオブジェクトコードを効率的に参照し、最終的な実行可能ファイルを生成することができます。アーカイブファイル内には、各メンバーファイルの名前(パス)がメタデータとして含まれており、このパスの解釈が今回のコミットの焦点となっています。
Windowsファイルパスのプレフィックス
Windowsオペレーティングシステムにおけるファイルパスは、Unix系システムとは異なる独自の規則を持っています。特に、パスの先頭に付加される「プレフィックス」は、そのパスがどのように解釈されるかを決定する重要な要素です。
-
ドライブレタープレフィックス: 最も一般的な形式で、
C:\
,D:/
のようにドライブレターとそれに続くパス区切り文字(\
または/
)で始まります。これは特定のローカルドライブ上の絶対パスを示します。Goのcmd/pack
が扱うのは主にこの形式のパスです。 -
UNCパス (Universal Naming Convention): ネットワーク上の共有リソースを示すパスで、
\\ServerName\ShareName\FilePath
の形式を取ります。 -
デバイスパス: 特定のデバイスに直接アクセスするためのパスで、
\\.\
や\\?\
で始まります。\\.\
: Win32デバイス名前空間へのアクセスに使用され、物理ディスクやボリューム、COMポートなどに直接アクセスします。\\?\
: 拡張パスプレフィックスとも呼ばれ、Windows APIによるパスの正規化を無効にし、MAX_PATH
(260文字) の制限を超えた長いパスを扱うことを可能にします。また、予約されたデバイス名(例:CON
,NUL
)を持つファイルへのアクセスも可能にします。
今回のコミットでは、特にドライブレタープレフィックスを持つパスの処理が問題となっていました。アーカイブファイルに格納されるパスは、通常、ビルド環境のルートからの相対パスとして扱われるべきですが、Windowsの絶対パスがそのまま格納されると、後続のツールがパスを正しく解決できない可能性がありました。このため、cmd/pack
は、アーカイブに格納する前にこれらの先頭プレフィックスを適切に除去する必要がありました。
技術的詳細
このコミットは、src/cmd/pack/ar.c
内の arread_cutprefix
関数に焦点を当てています。この関数は、アーカイブメンバーを読み込む際に、ファイル名から不要なプレフィックスを除去する役割を担っています。変更の核心は、Windows特有のドライブレタープレフィックス(例: C:\
や c:/
)を認識し、それを除去するロジックを追加した点にあります。
具体的には、以下の新しい要素が導入されています。
-
isdelim(c)
マクロ: これは、文字c
がパス区切り文字(/
または\
)であるかどうかを判定するシンプルなマクロです。Windowsでは両方の区切り文字が有効であるため、これを統一的に扱うために導入されました。 -
iswinpathstart(char *p, char *drive)
関数: この新しい関数は、与えられた文字列p
がWindowsの絶対パスの開始(例:C:\
やc:/
)であるかどうかをチェックします。- まず、
p[0]
が英字(A
〜Z
またはa
〜z
)であるかを調べ、それがドライブレターであると判断します。小文字の場合は大文字に変換してdrive
ポインタに格納します。 - 次に、
p[1]
がコロン(:
)であり、かつp[2]
がパス区切り文字(/
または\
)であるかをisdelim
マクロを使って確認します。 - これらの条件がすべて満たされれば、Windowsパスの開始であると判断し、1を返します。
- まず、
-
arread_cutprefix
関数内の変更: この関数は、アーカイブメンバーのファイル名を処理する際に、プレフィックスの除去ロジックを適用します。- 既存の「先頭の
/
を除去する」ロジックに加えて、新しいiswinpathstart
関数を使用してWindowsパスのプレフィックスを検出する条件が追加されました。 inprefix == nil && iswinpathstart(prefix, &d1) && iswinpathstart(p.id + 1, &d2) && d1 == d2 && p.id[4] == '\0'
という条件が追加されています。これは、prefix
(現在のパスのプレフィックス)がWindowsパスの開始であり、かつp.id + 1
(アーカイブメンバーのファイル名)もWindowsパスの開始であり、さらに両者のドライブレターが一致し、かつp.id
の長さが短い(C:\
のような形式)場合に、inprefix
をprefix+3
に設定します。これにより、C:\
のような3文字のプレフィックスが除去されるようになります。- さらに、後続のパス要素を処理する部分で、
inprefix[n] == '/'
のチェックがisdelim(inprefix[n])
に変更されました。これにより、/
だけでなく\
もパス区切り文字として正しく認識されるようになり、Windowsパスの処理がより柔軟になりました。 - 同様に、
inprefix[0] == '/'
のチェックもisdelim(inprefix[0])
に変更され、パス区切り文字の認識が改善されています。
- 既存の「先頭の
これらの変更により、cmd/pack
はWindows環境で生成されたアーカイブファイル内のパスから、ドライブレターを含む先頭のプレフィックスを正確に除去し、Goのビルドシステムが期待する相対パス形式に変換できるようになります。
コアとなるコードの変更箇所
--- a/src/cmd/pack/ar.c
+++ b/src/cmd/pack/ar.c
@@ -1618,6 +1618,25 @@ int (*reader[256])(Biobuf*, Prog*) = {
[Obj386] = _read8,
};
+#define isdelim(c) ((c) == '/' || (c) == '\\\\')
+
+/*
+ * check if p is start of windows full path, like C:\ or c:/.
+ * return 1 if so. also set drive parameter to its
+ * upper-case drive letter.
+ */
+int
+iswinpathstart(char *p, char *drive)
+{
+ if('A' <= p[0] || p[0] <= 'Z')
+ *drive = p[0];
+ else if('a' <= p[0] || p[0] <= 'z')
+ *drive = p[0] - ('a' - 'A');
+ else
+ return 0;
+ return p[1] == ':' && isdelim(p[2]);
+}
+
/*
* copy b into bp->member but rewrite object
* during copy to drop prefix from all file names.
@@ -1630,7 +1649,7 @@ arread_cutprefix(Biobuf *b, Armember *bp)\n \tvlong offset, o, end;\n \tint n, t;\n \tint (*rd)(Biobuf*, Prog*);\n-\tchar *w, *inprefix;\n+\tchar *w, *inprefix, d1, d2;\n \tProg p;\n \t\n \toffset = Boffset(b);\n@@ -1666,12 +1685,15 @@ arread_cutprefix(Biobuf *b, Armember *bp)\n \t\t\tif(inprefix == nil && prefix[0] == '/' && p.id[1] == '/' && p.id[2] == '\\0') {\n \t\t\t\t// leading /\n \t\t\t\tinprefix = prefix+1;\n+\t\t\t} else if(inprefix == nil && iswinpathstart(prefix, &d1) && iswinpathstart(p.id + 1, &d2) && d1 == d2 && p.id[4] == '\\0') {\n+\t\t\t\t// leading c:\\ ...\n+\t\t\t\tinprefix = prefix+3;\n \t\t\t} else if(inprefix != nil) {\n \t\t\t\t// handle subsequent elements\n \t\t\t\tn = strlen(p.id+1);\n-\t\t\t\tif(strncmp(p.id+1, inprefix, n) == 0 && (inprefix[n] == '/' || inprefix[n] == '\\0')) {\n+\t\t\t\tif(strncmp(p.id+1, inprefix, n) == 0 && (isdelim(inprefix[n]) || inprefix[n] == '\\0')) {\n \t\t\t\t\tinprefix += n;\n-\t\t\t\t\tif(inprefix[0] == '/')\n+\t\t\t\t\tif(isdelim(inprefix[0]))\n \t\t\t\t\t\tinprefix++;\n \t\t\t\t}\n \t\t\t}\n```
## コアとなるコードの解説
このコミットのコアとなる変更は、`src/cmd/pack/ar.c` ファイル内の `arread_cutprefix` 関数に集約されています。この関数は、アーカイブファイルからメンバーを読み込む際に、そのファイル名から不要なプレフィックスを除去する役割を担っています。
1. **`isdelim` マクロの導入**:
`#define isdelim(c) ((c) == '/' || (c) == '\\\\')`
このマクロは、文字 `c` がUnixスタイルのパス区切り文字 `/` またはWindowsスタイルのパス区切り文字 `\` のいずれかであるかを簡潔に判定するために導入されました。これにより、コード全体でパス区切り文字のチェックを一貫して行えるようになります。
2. **`iswinpathstart` 関数の追加**:
この関数は、与えられた文字列 `p` が `C:\` や `c:/` のようなWindowsのドライブレター付き絶対パスの開始形式であるかを判定します。
* `p[0]` がアルファベット(ドライブレター)であるかを確認し、小文字であれば大文字に変換して `drive` ポインタに格納します。
* `p[1]` がコロン `:` であるかを確認します。
* `p[2]` が `isdelim` マクロで判定されるパス区切り文字であるかを確認します。
これらの条件がすべて満たされた場合、その文字列がWindowsパスの開始であると判断し、`1` を返します。この関数は、Windows特有のパス形式を正確に識別するために不可欠です。
3. **`arread_cutprefix` 関数内のプレフィックス除去ロジックの拡張**:
`arread_cutprefix` 関数は、アーカイブメンバーのファイル名を処理する際に、`inprefix` という変数を使って除去すべきプレフィックスを管理します。
* **Windowsパスプレフィックスの検出と設定**:
`else if(inprefix == nil && iswinpathstart(prefix, &d1) && iswinpathstart(p.id + 1, &d2) && d1 == d2 && p.id[4] == '\\0')`
この新しい条件分岐は、まだプレフィックスが設定されていない(`inprefix == nil`)状態で、現在のパスのプレフィックス(`prefix`)とアーカイブメンバーのファイル名(`p.id + 1`)の両方が `iswinpathstart` で検出されるWindowsパスの開始形式であり、かつ両者のドライブレターが一致し、さらにファイル名が `C:\` のように短い形式である場合に実行されます。
この条件が真の場合、`inprefix` は `prefix+3` に設定されます。これは、`C:\` のような3文字のドライブレタープレフィックスをスキップし、その後のパス部分から処理を開始することを意味します。
* **後続のパス要素処理におけるパス区切り文字の柔軟な認識**:
`if(strncmp(p.id+1, inprefix, n) == 0 && (isdelim(inprefix[n]) || inprefix[n] == '\\0'))`
この行は、アーカイブメンバーのファイル名が現在の `inprefix` と一致するかどうかをチェックし、一致した場合に `inprefix` を進めるロジックです。変更前は `inprefix[n] == '/'` とハードコードされていましたが、`isdelim(inprefix[n])` に変更されたことで、`/` と `\` の両方をパス区切り文字として正しく認識できるようになりました。これにより、Windows環境で生成されたパスが、どちらの区切り文字を使用していても適切に処理されるようになります。
* **`inprefix` のインクリメントにおけるパス区切り文字の柔軟な認識**:
`if(isdelim(inprefix[0]))`
`inprefix` を1文字進める際にも、同様に `isdelim(inprefix[0])` を使用することで、`/` または `\` のいずれのパス区切り文字も正しくスキップできるようになりました。
これらの変更により、`cmd/pack` はWindows環境で作成されたアーカイブファイル内のパスから、ドライブレターを含む先頭のプレフィックスを正確に除去し、Goのビルドシステムが期待する相対パス形式に変換できるようになります。これにより、Windows上でのGoのビルドの信頼性とクロスプラットフォーム互換性が向上します。
## 関連リンク
* Go言語の公式ウェブサイト: [https://golang.org/](https://golang.org/)
* Go言語のソースコードリポジトリ (GitHub): [https://github.com/golang/go](https://github.com/golang/go)
* GoのIssueトラッカー (GoLang.org): [https://golang.org/issue](https://golang.org/issue) (ただし、Issue #5550の直接的なリンクは見つかりませんでした)
* Goのコードレビューシステム (Gerrit): [https://go-review.googlesource.com/](https://go-review.googlesource.com/) (コミットメッセージに記載されている `https://golang.org/cl/11904043` はGerritのチェンジリストへのリンクです)
## 参考にした情報源リンク
* Go Documentation: `go tool pack` (Goの公式ドキュメントで `go tool pack` の詳細が説明されていますが、直接的なURLは提供されていません。`go help pack` または `go doc cmd/pack` で参照可能です。)
* Microsoft Docs: Naming Files, Paths, and Namespaces: [https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
* Wikipedia: `ar (Unix)`: [https://en.wikipedia.org/wiki/Ar_(Unix)](https://en.wikipedia.org/wiki/Ar_(Unix))
* Stack Overflow: What are the differences between `\\?\` and `\\.\` prefixes in Windows paths?: [https://stackoverflow.com/questions/1000000/what-are-the-differences-between-and-prefixes-in-windows-paths](https://stackoverflow.com/questions/1000000/what-are-the-differences-between-and-prefixes-in-windows-paths)
* PhoenixNAP: Windows Path Length Limit: [https://phoenixnap.com/kb/windows-path-length-limit](https://phoenixnap.com/kb/windows-path-length-limit)
* GitHub Gist: Windows Path Prefixes: [https://gist.github.com/jpoehls/1000000](https://gist.github.com/jpoehls/1000000)
* `go tool pack`に関するWeb検索結果 (Go.dev, Stack Overflowなど)
* Windowsファイルパスのプレフィックスに関するWeb検索結果 (Microsoft.com, Stack Overflowなど)