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

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

このコミットは、Go言語のビルドツールである cmd/dist におけるエラー出力の挙動を改善するものです。具体的には、Windows環境で fatal() 関数がエラーメッセージを標準出力 (stdout) ではなく標準エラー出力 (stderr) に出力するように変更し、エラー発生時のサイレント失敗を防ぐことを目的としています。

コミット

commit 735780c27ee72a4a2cdfac306118cf015d922c93
Author: Pieter Droogendijk <pieter@binky.org.uk>
Date:   Sun Jul 1 00:27:05 2012 +1000

    cmd/dist: Make windows.c's fatal() print to stderr
    
    Generating env.bat using dist env -wp > env.bat failed silently
    if case of an error, because the message was redirected to env.bat.
    Verbose messages still go to stdout, causing problems, but that's
    a seperate change.
    Made errprintf() identical to xprintf(), except for the output handle.
    Yes, it's duplicate code, but most of the function is unpacking
    the argument list and preparing it for WriteFile(), which has to be
    done anyway.
    
    R=golang-dev, alex.brainman
    CC=golang-dev
    https://golang.org/cl/6343047

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

https://github.com/golang/go/commit/735780c27ee72a4a2cdfac306118cf015d922c93

元コミット内容

cmd/dist: Make windows.c's fatal() print to stderr

Generating env.bat using dist env -wp > env.bat failed silently
if case of an error, because the message was redirected to env.bat.
Verbose messages still go to stdout, causing problems, but that's
a seperate change.
Made errprintf() identical to xprintf(), except for the output handle.
Yes, it's duplicate code, but most of the function is unpacking
the argument list and preparing it for WriteFile(), which has to be
done anyway.

R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/6343047

変更の背景

この変更の背景には、Go言語のビルドツール cmd/dist がWindows環境で env.bat ファイルを生成する際に発生していた問題があります。

従来、dist env -wp > env.bat のようにコマンドの出力をファイルにリダイレクトした場合、エラーメッセージも標準出力に書き込まれていたため、リダイレクト先の env.bat ファイルにエラーメッセージが混入するか、あるいはエラーが発生してもユーザーに通知されずにサイレントに失敗するという問題がありました。

特に、fatal() 関数はプログラムの致命的なエラーを報告し、通常はプログラムを終了させる役割を担います。このような重要なエラーメッセージがリダイレクトによって見過ごされてしまうと、デバッグが困難になり、ビルドプロセスの健全性を損なう可能性がありました。

このコミットは、fatal() 関数からのエラーメッセージを標準エラー出力 (stderr) に明示的に送ることで、リダイレクトの影響を受けずにエラーがユーザーに確実に通知されるようにし、サイレント失敗の問題を解決することを目的としています。コミットメッセージにもあるように、冗長なメッセージがまだ標準出力に行く問題は別の変更で対応されるべき課題とされています。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

  • 標準出力 (stdout) と標準エラー出力 (stderr):

    • 標準出力 (stdout): プログラムが通常の結果や情報を出力するために使用するストリームです。シェルで >>> を使うことでファイルにリダイレクトできます。
    • 標準エラー出力 (stderr): プログラムがエラーメッセージや診断情報を出力するために使用するストリームです。通常、stdoutとは独立しており、エラーメッセージが他の出力に埋もれるのを防ぎます。stderrをリダイレクトするには、多くのシェルで 2>2>> を使用します。
    • この区別は、特にスクリプトや自動化されたビルドプロセスにおいて、プログラムの正常な出力とエラーを明確に分離し、エラーハンドリングを容易にするために非常に重要です。
  • cmd/dist:

    • Go言語のソースコードリポジトリに含まれるビルドツールの一つです。Goのツールチェイン自体をビルドするために使用されます。Goのコンパイラ、リンカ、その他のユーティリティを構築する際の低レベルな処理を担当します。
    • cmd/dist は、Goのブートストラッププロセスにおいて重要な役割を果たします。
  • fatal() 関数:

    • 多くのプログラミング言語やシステムツールにおいて、fatal() (または die(), exit_on_error() など) と名付けられた関数は、回復不可能なエラーが発生した際にプログラムを終了させるために使用されます。
    • 通常、エラーメッセージを出力し、非ゼロの終了コードでプロセスを終了させます。これにより、呼び出し元のプロセスやシェルスクリプトがエラーの発生を検知できます。
  • Windows API (WriteFile, GetStdHandle, STD_ERROR_HANDLE, ExitProcess):

    • Windows API (Application Programming Interface): Microsoft Windowsオペレーティングシステムが提供する関数群で、アプリケーションがシステムリソースやサービスにアクセスするために使用します。
    • WriteFile: 指定されたファイルまたはI/Oデバイスにデータを書き込むためのWindows API関数です。ここでは、コンソール(標準エラー出力)にバイト列を書き込むために使用されます。
    • GetStdHandle: 標準入力、標準出力、または標準エラー出力のハンドルを取得するためのWindows API関数です。
    • STD_ERROR_HANDLE: GetStdHandle 関数に渡す定数で、標準エラー出力のハンドルを取得するために使用されます。
    • ExitProcess: 現在のプロセスとそのすべてのスレッドを終了させるためのWindows API関数です。fatal() 関数内でプログラムを終了させるために使用されます。
  • 可変引数関数 (va_list, vsnprintf):

    • C言語において、引数の数が可変である関数(例: printf)を実装するために使用されるメカニズムです。
    • va_list: 可変引数リストを走査するための型です。
    • vsnprintf: 可変引数リストから書式付き文字列をバッファに書き込むための関数です。バッファオーバーフローを防ぐために、書き込む最大文字数を指定できます。このコミットでは、まず必要なバッファサイズを計算するために n = vsnprintf(NULL, 0, fmt, arg); のように使用され、その後実際に文字列を書き込むために使用されています。
  • メモリ管理 (xmalloc, xfree):

    • xmallocxfree は、Goのビルドツール内で使用されるカスタムのメモリ割り当て/解放関数である可能性が高いです。これらは通常、標準の mallocfree のラッパーであり、エラーハンドリング(例: メモリ割り当て失敗時にプログラムを終了させる)やデバッグ機能を追加していることがあります。

技術的詳細

このコミットの技術的な核心は、src/cmd/dist/windows.c ファイルに新しい関数 errprintf() を導入し、既存の fatal() 関数がエラーメッセージを標準エラー出力にリダイレクトするように変更した点にあります。

  1. errprintf() 関数の追加:

    • errprintf() は、xprintf() とほぼ同じロジックを持つ新しい関数として追加されました。xprintf() は標準出力に書き込むための関数であると推測されます。
    • errprintf() は可変引数 (...) を受け取り、va_listvsnprintf を使用して、与えられたフォーマット文字列と引数から最終的なエラーメッセージ文字列を構築します。
    • メッセージの構築後、xmalloc で必要なメモリを動的に確保し、vsnprintf でそのバッファにメッセージを書き込みます。
    • 最も重要な点は、メッセージの出力先です。WriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0); というWindows API呼び出しを使用しています。
      • GetStdHandle(STD_ERROR_HANDLE) は、標準エラー出力のハンドルを取得します。
      • WriteFile は、このハンドルを通じて、構築されたエラーメッセージ (p) を標準エラー出力に書き込みます。
    • メッセージの書き込み後、xfree(p) で動的に確保したメモリを解放します。
    • コミットメッセージにあるように、この関数は xprintf() とコードが重複していますが、可変引数の処理と WriteFile のための準備が必要なため、重複が許容されています。
  2. fatal() 関数の変更:

    • 既存の fatal() 関数は、エラーメッセージを出力するために xprintf("go tool dist: %s\\n", buf1); を呼び出していました。
    • このコミットにより、xprintf の呼び出しが errprintf("go tool dist: %s\\n", buf1); に変更されました。
    • これにより、fatal() が報告するすべてのエラーメッセージは、標準出力ではなく、新しく追加された errprintf() 関数を通じて標準エラー出力に送られるようになります。
    • fatal() 関数は引き続き bgwait() を呼び出し、最終的に ExitProcess(1) でプログラムを終了させます。ExitProcess(1) は、非ゼロの終了コード (1) を返すことで、エラーが発生したことをオペレーティングシステムに通知します。

この変更により、cmd/dist がWindows上でエラーを報告する際に、そのメッセージがリダイレクトの影響を受けずに、常にユーザーのコンソールに表示されるようになり、デバッグとエラー検知の信頼性が向上しました。

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

diff --git a/src/cmd/dist/windows.c b/src/cmd/dist/windows.c
index 0caee73f53..ec26f78724 100644
--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -121,6 +121,22 @@ errstr(void)
 	return bstr(&b);  // leak but we're dying anyway
 }
 
+static void
+errprintf(char *fmt, ...) {
+	va_list arg;
+	char *p;
+	DWORD n, w;
+
+	va_start(arg, fmt);
+	n = vsnprintf(NULL, 0, fmt, arg);
+	p = xmalloc(n+1);
+	vsnprintf(p, n+1, fmt, arg);
+	va_end(arg);
+	w = 0;
+	WriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0);
+	xfree(p);
+}
+
 void
 xgetenv(Buf *b, char *name)
 {
@@ -709,7 +725,7 @@ fatal(char *msg, ...)
 	vsnprintf(buf1, sizeof buf1, msg, arg);
 	va_end(arg);
 
-	xprintf("go tool dist: %s\n", buf1);
+	errprintf("go tool dist: %s\n", buf1);
 	
 	bgwait();
 	ExitProcess(1);

コアとなるコードの解説

errprintf 関数の追加

+static void
+errprintf(char *fmt, ...) {
+	va_list arg;
+	char *p;
+	DWORD n, w;
+
+	va_start(arg, fmt);
+	n = vsnprintf(NULL, 0, fmt, arg);
+	p = xmalloc(n+1);
+	vsnprintf(p, n+1, fmt, arg);
+	va_end(arg);
+	w = 0;
+	WriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0);
+	xfree(p);
+}
  • static void errprintf(char *fmt, ...): errprintf という名前の静的関数を定義しています。static はこの関数がこのファイル内でのみ使用されることを意味します。char *fmt, ... は、printf のように可変個の引数を受け取ることを示します。
  • va_list arg;: 可変引数リストを処理するための va_list 型の変数を宣言します。
  • char *p;: フォーマットされた文字列を格納するためのポインタを宣言します。
  • DWORD n, w;: n は文字列の長さを、wWriteFile が実際に書き込んだバイト数を格納するための変数です。DWORD はWindows固有の32ビット符号なし整数型です。
  • va_start(arg, fmt);: 可変引数リスト arg を初期化し、最後の固定引数 fmt の直後から引数を読み取れるようにします。
  • n = vsnprintf(NULL, 0, fmt, arg);: vsnprintf を使用して、フォーマットされた文字列に必要なバッファサイズを計算します。最初の引数に NULL、2番目の引数に 0 を渡すことで、実際に書き込みは行わず、必要な文字数(ヌル終端文字を除く)を返します。
  • p = xmalloc(n+1);: 計算されたサイズ n にヌル終端文字のための 1 を加えたバイト数でメモリを割り当てます。xmalloc はカスタムのメモリ割り当て関数です。
  • vsnprintf(p, n+1, fmt, arg);: 再び vsnprintf を呼び出し、今度は割り当てられたバッファ p にフォーマットされた文字列を書き込みます。n+1 はバッファの最大サイズです。
  • va_end(arg);: 可変引数リストの処理を終了し、クリーンアップを行います。
  • w = 0;: WriteFilelpNumberOfBytesWritten 引数に渡す変数を初期化します。
  • WriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0);: Windows APIの WriteFile 関数を呼び出して、メッセージを標準エラー出力に書き込みます。
    • GetStdHandle(STD_ERROR_HANDLE): 標準エラー出力のハンドルを取得します。
    • p: 書き込むデータ(フォーマットされたエラーメッセージ)のポインタ。
    • n: 書き込むバイト数(文字列の長さ)。
    • &w: 実際に書き込まれたバイト数を受け取るポインタ。
    • 0: オーバーラップI/Oのためのオプション(ここでは使用しない)。
  • xfree(p);: xmalloc で割り当てたメモリを解放します。

fatal 関数の変更

@@ -709,7 +725,7 @@ fatal(char *msg, ...)
 	vsnprintf(buf1, sizeof buf1, msg, arg);
 	va_end(arg);
 
-	xprintf("go tool dist: %s\n", buf1);
+	errprintf("go tool dist: %s\n", buf1);
 	
 	bgwait();
 	ExitProcess(1);
  • xprintf("go tool dist: %s\n", buf1); の行が、errprintf("go tool dist: %s\n", buf1); に変更されています。
  • これにより、fatal 関数がエラーメッセージを出力する際に、従来の標準出力 (xprintf) ではなく、新しく定義された標準エラー出力 (errprintf) を使用するようになります。
  • fatal 関数は引き続き、bgwait()(バックグラウンド処理の完了を待つ関数と推測される)を呼び出し、最終的に ExitProcess(1) を呼び出して、エラーを示す終了コード 1 でプロセスを終了させます。

この変更により、cmd/dist がWindows環境でエラーを報告する際の信頼性と可視性が大幅に向上しました。

関連リンク

参考にした情報源リンク