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

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

このコミットは、Go言語のツールチェインの一部である cmd/api コマンドに関連する変更です。src/cmd/api/run.go ファイルは、go api コマンドがAPIチェックを実行する際に使用するGoワークスペース(GOPATH)の準備ロジックを含んでいます。具体的には、一時的なGOPATHを生成し、必要なGoツールをチェックアウトする処理を担っています。

コミット

commit b34ec90e1945af8ed22bf96254deb4c637c0bfbc
Author: Robert Daniel Kortschak <dan.kortschak@adelaide.edu.au>
Date:   Wed Sep 11 10:50:56 2013 +1000

    cmd/api: make api check directory per-user
    
    Fixes #6353.
    
    R=golang-dev, bradfitz, alex.brainman
    CC=golang-dev
    https://golang.org/cl/13652043

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

https://github.com/golang/go/commit/b34ec90e1945af8ed22bf96254deb4c637c0bfbc

元コミット内容

cmd/api: make api check directory per-user

このコミットは、go api コマンドがAPIチェックを実行する際に使用する一時ディレクトリを、ユーザーごとに分離するように変更します。これにより、複数のユーザーが同じシステムで go api コマンドを実行した際に発生する可能性のある競合や問題が解決されます。

Fixes #6353.

この変更は、GoのIssue #6353を修正します。

変更の背景

Goの cmd/api ツールは、GoのAPIが後方互換性を維持しているかを確認するために使用されます。このツールは、一時的なGOPATH環境を作成し、その中にGoのソースコードをチェックアウトしてAPIチェックを実行します。

変更前の実装では、この一時的なGOPATHディレクトリが os.TempDir() によって返されるシステム共通の一時ディレクトリ内に、固定の名前(gopath-api)で作成されていました。この設計には、以下のような問題がありました。

  1. 複数ユーザー環境での競合: 複数のユーザーが同じシステム上で同時に go api コマンドを実行した場合、全員が同じ一時ディレクトリを使用しようとします。これにより、ファイルロックの競合、ファイルの破損、予期せぬ動作、または一方のユーザーの操作が他方のユーザーの操作に影響を与えるといった問題が発生する可能性がありました。
  2. パーミッションの問題: あるユーザーが作成した一時ディレクトリやファイルに対して、別のユーザーが書き込み権限を持たない場合、エラーが発生する可能性がありました。
  3. クリーンアップの問題: 共有ディレクトリであるため、どのユーザーがクリーンアップを担当すべきか、またはクリーンアップが適切に行われるかどうかが不明確になることがありました。

このコミットは、これらの問題を解決するために、一時GOPATHディレクトリのパスに現在のユーザー名を含めることで、ユーザーごとに独立したディレクトリが作成されるようにします。

前提知識の解説

  • GOPATH: Go言語のワークスペースの概念です。Goのソースコード、コンパイルされたパッケージ、実行可能ファイルが配置されるディレクトリ構造を定義します。go getgo install などのコマンドは、GOPATHに基づいて動作します。
  • os.TempDir(): Go標準ライブラリの os パッケージに含まれる関数で、オペレーティングシステムが一時ファイルやディレクトリを保存するために推奨するデフォルトのディレクトリのパスを返します。例えば、Linuxでは /tmp、macOSでは /var/folders/...、Windowsでは C:\Users\<username>\AppData\Local\Temp などが一般的です。
  • os/user パッケージ: Go標準ライブラリの os/user パッケージは、現在のユーザーや指定されたユーザーに関する情報を取得するための機能を提供します。user.Current() 関数は、現在のユーザーの情報を表す *User 構造体を返します。この構造体には、ユーザー名 (Username) やユーザーID (Uid) などの情報が含まれます。
  • filepath.Join(): Go標準ライブラリの path/filepath パッケージに含まれる関数で、引数として与えられたパス要素を結合して、プラットフォーム固有のパス区切り文字(例: / または \)を使用してクリーンなパスを構築します。
  • log.Fatalf(): Go標準ライブラリの log パッケージに含まれる関数で、フォーマットされたエラーメッセージを出力し、プログラムを終了させます。

技術的詳細

このコミットの主要な目的は、cmd/api ツールが使用する一時的なGOPATHディレクトリをユーザーごとに分離することです。これを実現するために、以下のステップが導入されました。

  1. 現在のユーザー情報の取得: os/user パッケージの user.Current() 関数を使用して、現在 go api コマンドを実行しているユーザーの情報を取得します。これにより、ユーザー名 (u.Username) にアクセスできるようになります。
  2. ユーザー名を含む一時ディレクトリパスの生成: 取得したユーザー名を一時GOPATHディレクトリのパスに組み込みます。具体的には、os.TempDir() が返す一時ディレクトリのパスに、gopath-api- というプレフィックスと、クリーンアップされたユーザー名を結合し、さらに goToolsVersion を追加します。
    • gopath := filepath.Join(os.TempDir(), "gopath-api-"+cleanUsername(u.Username), goToolsVersion)
  3. ユーザー名のクリーンアップ: cleanUsername という新しいヘルパー関数が導入されました。これは、ユーザー名に含まれる可能性のある特殊文字(\/:)をアンダースコア(_)に置換します。これは、ファイルシステムパスとして無効な文字や、パスの解釈に混乱を招く可能性のある文字を排除し、安全なディレクトリ名を作成するためです。例えば、Windowsのドメインユーザー名(例: DOMAIN\username)や、Unix系システムでパス区切り文字を含むユーザー名に対応するためと考えられます。

この変更により、各ユーザーは自分専用の一時GOPATHディレクトリを持つことになり、複数ユーザー環境での競合やパーミッションの問題が解消されます。

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

src/cmd/api/run.go ファイルにおいて、prepGoPath 関数と新しいヘルパー関数 cleanUsername が変更・追加されました。

--- a/src/cmd/api/run.go
+++ b/src/cmd/api/run.go
@@ -19,6 +19,7 @@ import (
  	"net/http"
  	"os"
  	"os/exec"
+	"os/user"
  	"path/filepath"
  	"strconv"
  	"strings"
@@ -99,8 +100,13 @@ func forceAPICheck() bool {
  func prepGoPath() string {
  	const tempBase = "go.tools.TMP"
  
+	u, err := user.Current()
+	if err != nil {
+		log.Fatalf("Error getting current user: %v", err)
+	}
+
  	// The GOPATH we'll return
-	gopath := filepath.Join(os.TempDir(), "gopath-api", goToolsVersion)
+	gopath := filepath.Join(os.TempDir(), "gopath-api-"+cleanUsername(u.Username), goToolsVersion)
  
  	// cloneDir is where we run "hg clone".
  	cloneDir := filepath.Join(gopath, "src", "code.google.com", "p")
@@ -140,6 +146,18 @@ func prepGoPath() string {
  	return gopath
  }
  
+func cleanUsername(n string) string {
+	b := make([]rune, len(n))
+	for i, r := range n {
+		if r == '\\' || r == '/' || r == ':' {
+			b[i] = '_'
+		} else {
+			b[i] = r
+		}
+	}
+	return string(b)
+}
+
 func goToolsCheckoutGood(dir string) bool {
  	if _, err := os.Stat(dir); err != nil {
  		return false

コアとなるコードの解説

  1. import "os/user" の追加:

    • os/user パッケージは、現在のシステムユーザーの情報を取得するために必要となるため、インポートリストに追加されました。
  2. prepGoPath 関数内の変更:

    • u, err := user.Current():
      • user.Current() 関数を呼び出して、現在実行中のユーザーの情報を取得します。この関数は *user.User 型のポインタとエラーを返します。
    • if err != nil { log.Fatalf("Error getting current user: %v", err) }:
      • user.Current() の呼び出しでエラーが発生した場合(例: ユーザー情報が取得できない場合)、log.Fatalf を使用してエラーメッセージを出力し、プログラムを異常終了させます。これは、ユーザー情報が一時ディレクトリのパス生成に不可欠であるため、致命的なエラーとして扱われます。
    • gopath := filepath.Join(os.TempDir(), "gopath-api-"+cleanUsername(u.Username), goToolsVersion):
      • 変更前のコードでは gopath := filepath.Join(os.TempDir(), "gopath-api", goToolsVersion) でした。
      • この行が、一時GOPATHディレクトリのパスを生成する核心部分です。
      • os.TempDir() はシステムの一時ディレクトリのパスを返します。
      • "gopath-api-" + cleanUsername(u.Username) の部分が新しく追加されました。cleanUsername(u.Username) は、現在のユーザー名 (u.Username) を安全な形式に変換した文字列を返します。これにより、生成されるパスは os.TempDir()/gopath-api-username_cleaned/goToolsVersion のようになり、ユーザーごとに一意のディレクトリが確保されます。
      • goToolsVersion は、Goツールのバージョンを示す定数で、異なるバージョンのツールが共存できるようにするためのものです。
  3. cleanUsername 関数の追加:

    • func cleanUsername(n string) string:
      • この関数は、入力された文字列 n(ユーザー名)を受け取り、クリーンアップされた文字列を返します。
    • b := make([]rune, len(n)):
      • 結果の文字列を構築するための rune スライスを、入力文字列と同じ長さで初期化します。rune を使用することで、マルチバイト文字(UTF-8)も正しく処理できます。
    • for i, r := range n:
      • 入力文字列 nrune 単位でイテレートします。i はインデックス、r は現在の rune です。
    • if r == '\\' || r == '/' || r == ':':
      • 現在の rune がバックスラッシュ (\)、スラッシュ (/)、またはコロン (:) のいずれかであるかをチェックします。これらの文字は、ファイルシステムパスの区切り文字や特殊な意味を持つ文字として扱われる可能性があるため、安全のために置換されます。
    • b[i] = '_' または b[i] = r:
      • もし r が上記の特殊文字のいずれかであれば、b[i] にアンダースコア (_) を代入します。
      • そうでなければ、b[i] に元の rune r をそのまま代入します。
    • return string(b):
      • 構築された rune スライス b を文字列に変換して返します。

この変更により、go api コマンドは、各ユーザーの環境で独立して動作し、一時ファイルの競合やパーミッションの問題を回避できるようになりました。

関連リンク

参考にした情報源リンク

(注: コミットメッセージに記載されている Fixes #6353 のIssueは、現在のGoのIssueトラッカーでは直接見つけることができませんでした。これは、古いIssueトラッカーからの移行や、Issue番号の再割り当てなど、時間の経過による変更が原因である可能性があります。)