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

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

このコミットは、Go言語のsyscallパッケージにおけるシステムコール生成スクリプトの堅牢性を向上させるための変更です。具体的には、システムコール定義ファイルからGoのソースコードを生成する際に、中間ファイルを使用することで、生成処理が失敗した場合に既存のソースファイルが破損(clobber)するのを防ぎます。

コミット

commit 4dc85d67cdaef157e0907c63cb276e448b6cbafe
Author: Han-Wen Nienhuys <hanwen@google.com>
Date:   Sun Jul 29 17:59:14 2012 -0400

    syscall: don't clobber source files if mksyscall.pl fails.
    
    R=golang-dev, minux.ma, r, rsc
    CC=golang-dev
    https://golang.org/cl/6353069

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

https://github.com/golang/go/commit/4dc85d67cdaef157e0907c63cb276e448b6cbafe

元コミット内容

このコミットは、syscallパッケージのmkall.shスクリプトにおける、システムコール定義ファイルからGoソースファイルを生成するロジックを修正しています。以前のバージョンでは、生成された内容を直接gofmtにパイプし、その結果を一時ファイルにリダイレクトした後、元のファイルに上書きしていました。このプロセス中に何らかのエラーが発生した場合、元のファイルが不完全な状態で上書きされ、破損する可能性がありました。

変更の背景

Go言語のsyscallパッケージは、オペレーティングシステム固有のシステムコールをGoプログラムから呼び出すためのインターフェースを提供します。これらのシステムコールは、OSのバージョンやアーキテクチャによって異なるため、Goプロジェクトではmksyscall.plのようなスクリプトを使用して、定義ファイルからGoのソースコードを自動生成しています。

この自動生成プロセスにおいて、mkall.shスクリプトがzsyscall*.goという形式のファイル(これらはシステムコール定義から生成されるGoソースファイルのテンプレートのようなもの)を処理していました。以前の実装では、生成されたコードを整形するためにgofmtコマンドに直接パイプしていましたが、もしgofmtが何らかの理由で失敗したり、パイプラインの途中でエラーが発生したりした場合、元のzsyscall*.goファイルが不完全な内容で上書きされてしまうリスクがありました。これは、ビルドプロセスや開発環境において、予期せぬファイル破損やビルドエラーを引き起こす可能性がありました。

このコミットの目的は、このような「clobber」(上書きによる破損)を防ぎ、システムコール生成プロセスの堅牢性を高めることです。

前提知識の解説

  • Go言語のsyscallパッケージ: Goプログラムがオペレーティングシステム(OS)のカーネル機能(システムコール)にアクセスするためのインターフェースを提供する標準ライブラリです。ファイル操作、ネットワーク通信、プロセス管理など、OSレベルの低レベルな操作を行う際に使用されます。OSごとにシステムコールの定義が異なるため、Goではこれらの定義を自動生成する仕組みを持っています。
  • mksyscall.pl: Goプロジェクト内で使用されるPerlスクリプトの一つで、システムコール定義ファイル(通常はコメント形式でGoソースファイル内に埋め込まれている)を解析し、対応するGoのシステムコール呼び出しコードを生成するために使われます。これは、Goのクロスプラットフォーム対応と、各OSのシステムコールAPIへの適応を容易にするための重要なツールです。
  • mkall.sh: src/pkg/syscallディレクトリに存在するシェルスクリプトで、mksyscall.plなどのツールを呼び出して、syscallパッケージに必要なGoソースファイルを生成・更新する役割を担っています。このスクリプトは、Goのビルドプロセスの一部として実行されることがあります。
  • gofmt: Go言語の公式フォーマッタです。Goのソースコードを標準的なスタイルに自動的に整形します。Go開発においては、コードの一貫性を保つために非常に広く使われています。gofmtは、入力としてGoのソースコードを受け取り、整形されたコードを標準出力に出力します。
  • パイプ (|) とリダイレクト (>, >>): Unix/Linuxシェルにおける基本的なコマンド連携の仕組みです。
    • | (パイプ): あるコマンドの標準出力を、別のコマンドの標準入力に接続します。
    • > (リダイレクト): コマンドの標準出力をファイルに書き込みます。既存のファイルは上書きされます。
    • < (入力リダイレクト): ファイルの内容をコマンドの標準入力として渡します。
  • 一時ファイル: プログラムやスクリプトが一時的にデータを保存するために作成するファイルです。処理が完了した後に削除されることが一般的です。一時ファイルを使用することで、元のファイルを直接変更する前に、新しい内容が完全に生成され、検証されることを保証できます。これにより、処理の途中でエラーが発生した場合でも、元のファイルが破損するリスクを低減できます。

技術的詳細

このコミットの技術的な核心は、ファイルの上書き処理におけるアトミック性(不可分性)の向上にあります。

変更前の処理フロー(問題点):

sed 1q $i | sed 's;^// ;;' | sh | gofmt >_$i && mv _$i $i
  1. sed 1q $i | sed 's;^// ;;': zsyscall*.goファイル($i)の最初の行を読み込み、特定のコメント行を削除します。
  2. | sh: その結果をシェルスクリプトとして実行します。このshの出力が、mksyscall.plによって生成されるGoコードの断片に相当します。
  3. | gofmt: shの出力をgofmtの標準入力にパイプし、整形します。
  4. >_$i: gofmtの整形結果を_プレフィックスを持つ一時ファイル(例: _zsyscall_linux_amd64.go)にリダイレクトして書き込みます。
  5. && mv _$i $i: 一時ファイルへの書き込みが成功した場合(&&)、その一時ファイルを元のファイル名($i)にリネーム(上書き)します。

このフローの問題点は、gofmtが失敗した場合や、gofmtへの入力が不正でエラーになった場合でも、gofmtコマンド自体は成功とみなされ、空の出力や不完全な出力が_$iに書き込まれる可能性があったことです。その結果、mvコマンドが実行され、元のファイルが不完全な内容で上書きされてしまう(clobberされる)リスクがありました。

変更後の処理フロー(改善点):

sed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
rm _$i
  1. sed 1q $i | sed 's;^// ;;': 同様に、zsyscall*.goファイルの最初の行を処理します。
  2. | sh > _$i: shコマンドの出力を直接、_プレフィックスを持つ一時ファイル(_$i)にリダイレクトして書き込みます。ここでgofmtへのパイプがなくなっています。 これにより、shコマンド(実質的にmksyscall.plの出力)が一時ファイルに完全に書き込まれます。
  3. && gofmt < _$i > $i: 一時ファイル(_$i)への書き込みが成功した場合、その一時ファイルをgofmtの標準入力として渡し(< _$i)、gofmtの整形結果を直接元のファイル($i)にリダイレクトして書き込みます(> $i)。
    • このステップでgofmtが失敗した場合、gofmtコマンド自体が非ゼロの終了コードを返し、&&の連鎖がそこで止まるため、元のファイル$iは上書きされません。
    • また、gofmtが成功した場合のみ、整形された完全な内容が元のファイルに書き込まれることが保証されます。
  4. rm _$i: 処理が完了した後、一時ファイル_$iを削除します。

この変更により、gofmtの処理が一時ファイルへの書き込みと分離され、gofmtが失敗した場合でも元のファイルが安全に保たれるようになりました。一時ファイルにまず完全な内容を書き込み、その一時ファイルをgofmtで処理し、その結果を元のファイルに書き込むという多段階のアプローチにより、ファイル更新のアトミック性が向上しています。

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

変更はsrc/pkg/syscall/mkall.shファイル内の以下の行です。

--- a/src/pkg/syscall/mkall.sh
+++ b/src/pkg/syscall/mkall.sh
@@ -87,7 +87,8 @@ case "$1" in
 -syscalls)
 	for i in zsyscall*go
 	do
-\t\tsed 1q $i | sed 's;^// ;;' | sh | gofmt >_$i && mv _$i $i
+\t\tsed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
+\t\trm _$i
 	done
 	exit 0
 	;;

具体的には、sed ... | sh | gofmt >_$i && mv _$i $i の行が削除され、代わりに sed ... | sh > _$i && gofmt < _$i > $irm _$i の2行が追加されました。

コアとなるコードの解説

  • 変更前:

    sed 1q $i | sed 's;^// ;;' | sh | gofmt >_$i && mv _$i $i
    

    この行では、システムコール定義を処理し、その結果をgofmtにパイプして整形し、一時ファイル_$iに書き込み、成功したら元のファイル$iにリネームしていました。問題は、gofmtが失敗してもパイプライン全体が成功とみなされ、不完全な内容で元のファイルが上書きされる可能性があったことです。

  • 変更後:

    sed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
    rm _$i
    
    1. sed 1q $i | sed 's;^// ;;' | sh > _$i:
      • sed 1q $i | sed 's;^// ;;': zsyscall*.goファイル($i)の最初の行を読み込み、特定のコメント行を削除します。これは、システムコール定義を抽出する前処理です。
      • | sh: 前処理された内容をシェルスクリプトとして実行します。これにより、mksyscall.plによって生成されるGoコードの断片が標準出力に出力されます。
      • > _$i: shコマンドの標準出力を、_プレフィックスを持つ一時ファイル(例: _zsyscall_linux_amd64.go)にリダイレクトして書き込みます。この時点で、整形されていないが完全なGoコードが一時ファイルに保存されます。
    2. && gofmt < _$i > $i:
      • &&: 直前のコマンド(sh > _$i)が成功した場合にのみ、このコマンドが実行されます。
      • gofmt < _$i: 一時ファイル_$iの内容をgofmtの標準入力として渡します。
      • > $i: gofmtの整形結果を、元のファイル名$iにリダイレクトして書き込みます。このステップでgofmtが失敗した場合、元のファイルは上書きされません。
    3. rm _$i:
      • 前のコマンドがすべて成功した場合、作成された一時ファイル_$iを削除します。

この修正により、gofmtの実行とファイルへの書き込みがより安全な方法で行われるようになり、生成プロセス中のエラーによるソースファイルの破損が防止されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (syscallパッケージ、gofmtなど): https://go.dev/
  • Unix/Linux シェルのパイプとリダイレクトに関する一般的な情報源。
  • Gitのコミットと差分表示に関する一般的な情報源。
  • mksyscall.plの役割に関するGoプロジェクトのソースコードや関連ドキュメント。

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

このコミットは、Go言語のsyscallパッケージにおけるシステムコール生成スクリプトの堅牢性を向上させるための変更です。具体的には、システムコール定義ファイルからGoのソースコードを生成する際に、中間ファイルを使用することで、生成処理が失敗した場合に既存のソースファイルが破損(clobber)するのを防ぎます。

コミット

commit 4dc85d67cdaef157e0907c63cb276e448b6cbafe
Author: Han-Wen Nienhuys <hanwen@google.com>
Date:   Sun Jul 29 17:59:14 2012 -0400

    syscall: don't clobber source files if mksyscall.pl fails.
    
    R=golang-dev, minux.ma, r, rsc
    CC=golang-dev
    https://golang.org/cl/6353069

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

https://github.com/golang/go/commit/4dc85d67cdaef157e0907c63cb276e448b6cbafe

元コミット内容

このコミットは、syscallパッケージのmkall.shスクリプトにおける、システムコール定義ファイルからGoソースファイルを生成するロジックを修正しています。以前のバージョンでは、生成された内容を直接gofmtにパイプし、その結果を一時ファイルにリダイレクトした後、元のファイルに上書きしていました。このプロセス中に何らかのエラーが発生した場合、元のファイルが不完全な状態で上書きされ、破損する可能性がありました。

変更の背景

Go言語のsyscallパッケージは、オペレーティングシステム固有のシステムコールをGoプログラムから呼び出すためのインターフェースを提供します。これらのシステムコールは、OSのバージョンやアーキテクチャによって異なるため、Goプロジェクトではmksyscall.plのようなスクリプトを使用して、定義ファイルからGoのソースコードを自動生成しています。

この自動生成プロセスにおいて、mkall.shスクリプトがzsyscall*.goという形式のファイル(これらはシステムコール定義から生成されるGoソースファイルのテンプレートのようなもの)を処理していました。以前の実装では、生成されたコードを整形するためにgofmtコマンドに直接パイプしていましたが、もしgofmtが何らかの理由で失敗したり、パイプラインの途中でエラーが発生したりした場合、元のzsyscall*.goファイルが不完全な内容で上書きされてしまうリスクがありました。これは、ビルドプロセスや開発環境において、予期せぬファイル破損やビルドエラーを引き起こす可能性がありました。

このコミットの目的は、このような「clobber」(上書きによる破損)を防ぎ、システムコール生成プロセスの堅牢性を高めることです。

前提知識の解説

  • Go言語のsyscallパッケージ: Goプログラムがオペレーティングシステム(OS)のカーネル機能(システムコール)にアクセスするためのインターフェースを提供する標準ライブラリです。ファイル操作、ネットワーク通信、プロセス管理など、OSレベルの低レベルな操作を行う際に使用されます。OSごとにシステムコールの定義が異なるため、Goではこれらの定義を自動生成する仕組みを持っています。
  • mksyscall.pl: Goプロジェクト内で使用されるPerlスクリプトの一つで、システムコール定義ファイル(通常はコメント形式でGoソースファイル内に埋め込まれている)を解析し、対応するGoのシステムコール呼び出しコードを生成するために使われます。これは、Goのクロスプラットフォーム対応と、各OSのシステムコールAPIへの適応を容易にするための重要なツールです。mksyscall.plは、Goのソースファイル内に記述された//sys//sysnbといった特殊なコメント行を解析し、それに基づいてシステムコールを呼び出すためのGoコードを生成します。//sysはシステムコール実行中にゴルーチンが中断されることを示し、//sysnbは非ブロッキングなシステムコールでゴルーチンが中断されないことを示します。
  • mkall.sh: src/pkg/syscallディレクトリに存在するシェルスクリプトで、mksyscall.plなどのツールを呼び出して、syscallパッケージに必要なGoソースファイルを生成・更新する役割を担っています。このスクリプトは、Goのビルドプロセスの一部として実行されることがあります。
  • gofmt: Go言語の公式フォーマッタです。Goのソースコードを標準的なスタイルに自動的に整形します。Go開発においては、コードの一貫性を保つために非常に広く使われています。gofmtは、入力としてGoのソースコードを受け取り、整形されたコードを標準出力に出力します。
  • パイプ (|) とリダイレクト (>, >>): Unix/Linuxシェルにおける基本的なコマンド連携の仕組みです。
    • | (パイプ): あるコマンドの標準出力を、別のコマンドの標準入力に接続します。
    • > (リダイレクト): コマンドの標準出力をファイルに書き込みます。既存のファイルは上書きされます。
    • < (入力リダイレクト): ファイルの内容をコマンドの標準入力として渡します。
  • 一時ファイル: プログラムやスクリプトが一時的にデータを保存するために作成するファイルです。処理が完了した後に削除されることが一般的です。一時ファイルを使用することで、元のファイルを直接変更する前に、新しい内容が完全に生成され、検証されることを保証できます。これにより、処理の途中でエラーが発生した場合でも、元のファイルが破損するリスクを低減できます。

技術的詳細

このコミットの技術的な核心は、ファイルの上書き処理におけるアトミック性(不可分性)の向上にあります。

変更前の処理フロー(問題点):

sed 1q $i | sed 's;^// ;;' | sh | gofmt >_$i && mv _$i $i
  1. sed 1q $i | sed 's;^// ;;': zsyscall*.goファイル($i)の最初の行を読み込み、特定のコメント行を削除します。これは、mksyscall.plが解析するシステムコール定義の行を抽出する前処理です。
  2. | sh: その結果をシェルスクリプトとして実行します。このshの出力が、mksyscall.plによって生成されるGoコードの断片に相当します。
  3. | gofmt: shの出力をgofmtの標準入力にパイプし、整形します。
  4. >_$i: gofmtの整形結果を_プレフィックスを持つ一時ファイル(例: _zsyscall_linux_amd64.go)にリダイレクトして書き込みます。
  5. && mv _$i $i: 一時ファイルへの書き込みが成功した場合(&&)、その一時ファイルを元のファイル名($i)にリネーム(上書き)します。

このフローの問題点は、gofmtが失敗した場合や、gofmtへの入力が不正でエラーになった場合でも、gofmtコマンド自体は成功とみなされ、空の出力や不完全な出力が_$iに書き込まれる可能性があったことです。例えば、shの出力が不正なGoコードであった場合、gofmtはエラーを発生させますが、パイプラインの終了コードはgofmtの終了コードに依存するため、gofmtがエラーを返しても、>_$iへのリダイレクト自体は行われ、_$iが空になるか、不完全な内容になる可能性があります。その結果、mvコマンドが実行され、元のファイルが不完全な内容で上書きされてしまう(clobberされる)リスクがありました。

変更後の処理フロー(改善点):

sed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
rm _$i
  1. sed 1q $i | sed 's;^// ;;' | sh > _$i:
    • sed 1q $i | sed 's;^// ;;': 同様に、zsyscall*.goファイルの最初の行を処理します。
    • | sh: 前処理された内容をシェルスクリプトとして実行し、mksyscall.plによって生成されるGoコードの断片を標準出力に出力します。
    • > _$i: shコマンドの出力を直接、_プレフィックスを持つ一時ファイル(_$i)にリダイレクトして書き込みます。ここでgofmtへのパイプがなくなっています。 これにより、shコマンド(実質的にmksyscall.plの出力)が一時ファイルに完全に書き込まれます。この時点では、コードは整形されていませんが、mksyscall.plによって生成された完全な内容が一時ファイルに存在します。
  2. && gofmt < _$i > $i:
    • &&: 直前のコマンド(sh > _$i)が成功した場合にのみ、このコマンドが実行されます。これにより、一時ファイル_$iが正常に作成されたことが保証されます。
    • gofmt < _$i: 一時ファイル(_$i)の内容をgofmtの標準入力として渡します。
    • > $i: gofmtの整形結果を直接元のファイル($i)にリダイレクトして書き込みます。
    • このステップでgofmtが失敗した場合、gofmtコマンド自体が非ゼロの終了コードを返し、&&の連鎖がそこで止まるため、元のファイル$iは上書きされません。元のファイルは変更されずに残ります。
    • また、gofmtが成功した場合のみ、整形された完全な内容が元のファイルに書き込まれることが保証されます。
  3. rm _$i: 処理が完了した後、一時ファイル_$iを削除します。

この変更により、gofmtの処理が一時ファイルへの書き込みと分離され、gofmtが失敗した場合でも元のファイルが安全に保たれるようになりました。一時ファイルにまず完全な内容を書き込み、その一時ファイルをgofmtで処理し、その結果を元のファイルに書き込むという多段階のアプローチにより、ファイル更新のアトミック性が向上しています。

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

変更はsrc/pkg/syscall/mkall.shファイル内の以下の行です。

--- a/src/pkg/syscall/mkall.sh
+++ b/src/pkg/syscall/mkall.sh
@@ -87,7 +87,8 @@ case "$1" in
 -syscalls)
 	for i in zsyscall*go
 	do
-\t\tsed 1q $i | sed 's;^// ;;' | sh | gofmt >_$i && mv _$i $i
+\t\tsed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
+\t\trm _$i
 	done
 	exit 0
 	;;

具体的には、sed ... | sh | gofmt >_$i && mv _$i $i の行が削除され、代わりに sed ... | sh > _$i && gofmt < _$i > $irm _$i の2行が追加されました。

コアとなるコードの解説

  • 変更前:

    sed 1q $i | sed 's;^// ;;' | sh | gofmt >_$i && mv _$i $i
    

    この行では、システムコール定義を処理し、その結果をgofmtにパイプして整形し、一時ファイル_$iに書き込み、成功したら元のファイル$iにリネームしていました。問題は、gofmtが失敗してもパイプライン全体が成功とみなされ、不完全な内容で元のファイルが上書きされる可能性があったことです。

  • 変更後:

    sed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
    rm _$i
    
    1. sed 1q $i | sed 's;^// ;;' | sh > _$i:
      • sed 1q $i | sed 's;^// ;;': zsyscall*.goファイル($i)の最初の行を読み込み、特定のコメント行を削除します。これは、システムコール定義を抽出する前処理です。
      • | sh: 前処理された内容をシェルスクリプトとして実行します。これにより、mksyscall.plによって生成されるGoコードの断片が標準出力に出力されます。
      • > _$i: shコマンドの標準出力を、_プレフィックスを持つ一時ファイル(例: _zsyscall_linux_amd64.go)にリダイレクトして書き込みます。この時点で、整形されていないが完全なGoコードが一時ファイルに保存されます。
    2. && gofmt < _$i > $i:
      • &&: 直前のコマンド(sh > _$i)が成功した場合にのみ、このコマンドが実行されます。これにより、一時ファイル_$iが正常に作成されたことが保証されます。
      • gofmt < _$i: 一時ファイル_$iの内容をgofmtの標準入力として渡します。
      • > $i: gofmtの整形結果を、元のファイル名$iにリダイレクトして書き込みます。このステップでgofmtが失敗した場合、gofmtは非ゼロの終了コードを返し、&&の連鎖が中断されるため、元のファイルは上書きされません。
    3. rm _$i:
      • 前のコマンドがすべて成功した場合、作成された一時ファイル_$iを削除します。

この修正により、gofmtの実行とファイルへの書き込みがより安全な方法で行われるようになり、生成プロセス中のエラーによるソースファイルの破損が防止されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (syscallパッケージ、gofmtなど): https://go.dev/
  • Unix/Linux シェルのパイプとリダイレクトに関する一般的な情報源。
  • Gitのコミットと差分表示に関する一般的な情報源。
  • mksyscall.plの役割に関するGoプロジェクトのソースコードや関連ドキュメント。