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

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

このコミットは、Go言語のツールチェインの一部である cmd/pack コマンド内の ar.c ファイルに対して行われた変更です。具体的には、ファイルパスからファイル名を抽出する trim 関数が、Windows環境で一般的に使用されるバックスラッシュ (\) もパス区切り文字として認識するように修正されました。これにより、cmd/pack がWindows環境で生成されたアーカイブファイル内のパスを正しく処理できるようになり、クロスプラットフォーム互換性が向上しました。

コミット

commit 367557cd79ffb5ff82d9d21c2e8098d95083b3c0
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sat Mar 17 01:34:44 2012 +0800

    cmd/pack: also recognize '\' as path separator in filenames
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5841051

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

https://github.com/golang/go/commit/367557cd79ffb5ff82d9d21c2e8098d95083b3c0

元コミット内容

このコミットは、「cmd/pack がファイル名において \ もパス区切り文字として認識するようにする」という目的を持っています。これは、Go言語のツールチェインが異なるオペレーティングシステム(特にWindows)で生成されたファイルやパスを扱う際に、互換性の問題を解決するためのものです。

変更の背景

Go言語はクロスプラットフォーム開発を強く意識しており、そのツールチェインも様々なOSで動作するように設計されています。しかし、ファイルパスの区切り文字はOSによって異なります。Unix系システム(Linux, macOSなど)ではスラッシュ (/) が使用されるのに対し、Windowsではバックスラッシュ (\) が主に使用されます。

cmd/pack は、Goのビルドプロセスにおいて、オブジェクトファイルなどをアーカイブする際に使用される可能性があります。このツールが生成または処理するアーカイブファイル(例えば .a ファイル)の内部には、ファイルパスが格納されることがあります。もし cmd/pack がスラッシュ (/) しかパス区切り文字として認識しない場合、Windows環境で作成されたパス(例: path\to\file.o)を正しく解析できず、ファイル名抽出などの処理で問題が発生する可能性がありました。

このコミットは、このようなクロスプラットフォーム環境でのパス処理の不整合を解消し、cmd/pack がWindows環境で生成されたアーカイブファイルも適切に扱えるようにするために導入されました。

前提知識の解説

  1. cmd/pack とは: cmd/pack は、Go言語のツールチェインの一部であり、主にGoのコンパイラやリンカが生成するオブジェクトファイル(.o ファイル)をアーカイブファイル(.a ファイル、静的ライブラリ)にまとめるために使用されるユーティリティです。これは、Unix系の ar (archiver) コマンドに似た機能を提供します。Goのビルドシステムは、内部的にこの pack コマンドを呼び出して、コンパイルされたコードをパッケージ化します。

  2. ar アーカイブフォーマット: ar は、複数のファイルを単一のアーカイブファイルにまとめるためのUnix系ユーティリティおよびファイルフォーマットです。静的ライブラリ(.a ファイル)の作成によく使われます。ar アーカイブは、ヘッダとそれに続くファイルデータで構成され、ヘッダにはファイル名、サイズ、パーミッションなどのメタデータが含まれます。このコミットの対象ファイルが ar.c であることから、cmd/packar フォーマットのアーカイブを扱っていることがわかります。

  3. ファイルパスの区切り文字:

    • Unix系 (Linux, macOS): パス区切り文字はスラッシュ (/) です。例: /home/user/documents/file.txt
    • Windows: パス区切り文字はバックスラッシュ (\) です。例: C:\Users\user\Documents\file.txt Go言語の標準ライブラリ(path および path/filepath パッケージ)は、これらの違いを吸収してクロスプラットフォームで動作するように設計されていますが、低レベルのツールやC言語で書かれたコンポーネントでは、明示的に両方の区切り文字を考慮する必要がある場合があります。
  4. C言語の strrchr 関数: strrchr はC標準ライブラリの文字列操作関数の一つです。 char *strrchr(const char *str, int c); この関数は、文字列 str の中で、指定された文字 c が最後に現れる位置へのポインタを返します。もし文字が見つからない場合は NULL を返します。このコミットでは、ファイルパスの文字列から最後のパス区切り文字(/ または \)を見つけるために使用されています。

技術的詳細

変更が加えられたのは src/cmd/pack/ar.c 内の trim 関数です。この関数は、与えられたファイルパス文字列から、ディレクトリ部分を除いた純粋なファイル名(または最後のパス要素)を抽出する役割を担っています。

変更前は、trim 関数は strrchr(s, '/') を使用して、スラッシュ (/) のみをパス区切り文字として認識していました。つまり、文字列の末尾から最初に現れるスラッシュを探し、そのスラッシュの次の文字からをファイル名として扱っていました。

変更後は、バックスラッシュ (\) もパス区切り文字として考慮されるようになりました。具体的には、以下のロジックが追加されました。

  1. p = strrchr(s, '/') で、スラッシュ (/) の最後の出現位置を探します。
  2. q = strrchr(s, '\\') で、バックスラッシュ (\) の最後の出現位置を探します。
  3. if (q > p) という条件で、q(バックスラッシュの位置)が p(スラッシュの位置)よりも後にある場合、つまりバックスラッシュがより文字列の末尾に近い位置にある場合、p = q とすることで、p がバックスラッシュの位置を指すように更新されます。
    • これは、Windowsパスではバックスラッシュが主要な区切り文字であり、UnixパスとWindowsパスが混在するような状況(例えば、WindowsでUnix形式のパスが文字列として扱われる場合)でも、より「正しい」ファイル名を抽出できるようにするためのロジックです。
    • 例えば、path/to\file.txt のようなパスがあった場合、変更前は / の後にある to\file.txt をファイル名と認識していましたが、変更後は \ の後にある file.txt をファイル名と認識するようになります。

この変更により、trim 関数は、スラッシュとバックスラッシュの両方を考慮して、ファイルパスの最後の要素を正確に抽出できるようになりました。

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

--- a/src/cmd/pack/ar.c
+++ b/src/cmd/pack/ar.c
@@ -1382,11 +1382,14 @@ mesg(int c, char *file)
 void
 trim(char *s, char *buf, int n)
 {
-	char *p;
+	char *p, *q;
 
 	for(;;) {
 		p = strrchr(s, '/');
-		if (!p) {		/* no slash in name */
+		q = strrchr(s, '\\');
+		if (q > p)
+			p = q;
+		if (!p) {		/* no (back)slash in name */
 			strncpy(buf, s, n);
 			return;
 		}
@@ -1394,7 +1397,7 @@ trim(char *s, char *buf, int n)
 			strncpy(buf, p+1, n);
 			return;
 		}
-		*p = 0;			/* strip trailing slash */
+		*p = 0;			/* strip trailing (back)slash */
 	}
 }

コアとなるコードの解説

trim 関数は、入力文字列 s(ファイルパス)から、ファイル名部分を抽出し、buf にコピーします。nbuf の最大サイズです。

  1. char *p, *q;

    • p: スラッシュ (/) またはバックスラッシュ (\) の最後の出現位置を指すポインタ。
    • q: バックスラッシュ (\) の最後の出現位置を指すポインタ。
  2. p = strrchr(s, '/');

    • まず、文字列 s の中でスラッシュ (/) が最後に現れる位置を探し、そのポインタを p に格納します。
  3. q = strrchr(s, '\\');

    • 次に、文字列 s の中でバックスラッシュ (\) が最後に現れる位置を探し、そのポインタを q に格納します。
  4. if (q > p)

    • この条件は非常に重要です。strrchr は見つからなかった場合に NULL を返します。ポインタの比較 q > p は、qp よりも「後方」(つまり、文字列の末尾に近い位置)にある場合に真となります。
    • もし qNULL でなく、かつ pNULL であるか、または qp よりも後方にある場合、バックスラッシュがより適切なパス区切り文字であると判断されます。
  5. p = q;

    • 上記の条件が真の場合、pq の値で上書きします。これにより、以降の処理で p は、スラッシュとバックスラッシュのうち、より文字列の末尾に近い方の区切り文字を指すことになります。
  6. if (!p) { /* no (back)slash in name */ ... }

    • pNULL の場合、これは文字列 s の中にスラッシュもバックスラッシュも含まれていないことを意味します。この場合、文字列 s 全体がファイル名であると判断し、buf にコピーして関数を終了します。コメントも「no slash in name」から「no (back)slash in name」に更新され、変更の意図が明確になっています。
  7. if (p[1] != '\0') { /* not trailing (back)slash */ ... }

    • p が指す区切り文字の直後の文字がヌル終端文字 (\0) でない場合、それは区切り文字の後にファイル名が続いていることを意味します。この場合、p+1(区切り文字の次の文字)から buf にコピーして関数を終了します。コメントも同様に更新されています。
  8. *p = 0; /* strip trailing (back)slash */

    • 上記の条件が偽の場合(つまり、p[1] == '\0')、これはパスが区切り文字で終わっていることを意味します(例: /path/to/C:\path\to\)。この場合、*p = 0 とすることで、その区切り文字をヌル終端文字で上書きし、パスの最後の要素(この場合は空文字列になる)を実質的に「トリム」します。このループは、パスの末尾から区切り文字を一つずつ取り除き、最終的にファイル名部分だけが残るまで繰り返されます。コメントも更新されています。

この一連の変更により、trim 関数はWindowsスタイルのパスも適切に処理し、ファイル名を正確に抽出できるようになりました。

関連リンク

  • Go CL 5841051: https://golang.org/cl/5841051
    • このリンクは、このコミットに対応するGoのコードレビューシステム(Gerrit)のエントリです。通常、ここにはコミットに至るまでの議論や追加のコンテキストが含まれています。

参考にした情報源リンク