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

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

このコミットは、Go言語の公式ドキュメントツールである godoc コマンドから、ソースコードリポジトリの同期機能を削除するものです。具体的には、godoc が定期的に外部コマンドを実行してソースコードを更新する機能が廃止されました。これにより、godoc のコードベースが簡素化され、特定の同期メカニズムへの依存がなくなりました。

コミット

commit d46438c3dadc5ce4903a2a2244b5bde84e4357cc
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Mar 12 15:57:38 2012 -0700

    cmd/godoc: remove sync code
    
    Fixes #3273
    
    R=gri
    CC=golang-dev
    https://golang.org/cl/5795065

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

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

元コミット内容

cmd/godoc: remove sync code Fixes #3273

このコミットは、godoc コマンドから同期コードを削除します。これはIssue #3273を修正するものです。

変更の背景

この変更の背景には、godoc の設計思想と運用上の課題がありました。

godoc は、Go言語のソースコードからドキュメントを生成し、Webサーバーとして提供するツールです。初期の godoc には、-sync および -sync_minutes というフラグが存在し、これらを設定することで、指定されたコマンド(通常は git pullhg pull など)を定期的に実行し、godoc が参照するソースコードリポジトリを自動的に最新の状態に保つ機能がありました。

しかし、この自動同期機能はいくつかの問題を引き起こしていました。

  1. 複雑性の増加: godoc 自体が外部コマンドの実行、その結果の解析、エラーハンドリング、指数関数的なバックオフといった同期ロジックを持つことで、コードベースが不必要に複雑になっていました。
  2. セキュリティリスク: 任意のコマンドを実行できる機能は、設定ミスや悪意のある利用によってセキュリティ上の脆弱性につながる可能性がありました。
  3. 運用上の問題: 同期コマンドの失敗や、同期中に godoc が参照するファイルシステムの状態が一時的に不整合になる可能性がありました。また、同期処理が godoc プロセスのパフォーマンスに影響を与えることも考えられます。
  4. 役割の分離: godoc の主要な役割はドキュメントの提供であり、ソースコードの同期は別のツールや運用プロセス(例: cron ジョブ、CI/CDパイプライン)に任せるべきであるという考え方が強まりました。

特に、コミットメッセージにある Fixes #3273 は、この同期機能に関連する具体的な問題を示唆しています。Issue #3273は「cmd/godoc: sync command should not be run as root」というタイトルで、godoc がroot権限で実行されている場合に、同期コマンドもroot権限で実行されてしまうというセキュリティ上の懸念が指摘されていました。この問題は、同期機能を完全に削除することで根本的に解決されます。

これらの理由から、godoc のコア機能に集中し、よりシンプルで堅牢な設計にするために、同期機能が削除されることになりました。

前提知識の解説

  • godoc: Go言語のソースコードからドキュメントを抽出し、Webブラウザで閲覧可能な形式で提供するツール。Goの標準ライブラリやサードパーティパッケージのドキュメントをローカルで参照する際に利用されます。
  • Go言語のコマンドラインツール (cmd/): Go言語のプロジェクトでは、cmd/ ディレクトリ以下に実行可能なコマンドラインツールが配置されるのが一般的です。cmd/godocgodoc コマンドの実装を指します。
  • flag パッケージ: Go言語の標準ライブラリで、コマンドライン引数を解析するために使用されます。-sync-sync_minutes といったフラグは、このパッケージによって定義されていました。
  • os.Pipe() / os.StartProcess() / p.Wait(): Go言語で外部プロセスを実行し、その標準入出力と通信するための関数群です。同期機能では、これらの関数を使って外部の同期コマンド(例: git pull)を実行していました。
  • http.HandlerFunc / http.DefaultServeMux: Go言語の標準ライブラリ net/http パッケージで、HTTPサーバーを構築するための基本的な要素です。godoc はWebサーバーとして動作するため、これらの機能を利用していました。
  • Issue Tracker (GitHub Issues): ソフトウェア開発プロジェクトで、バグ報告、機能要望、タスク管理などを行うためのシステム。Fixes #3273 は、このコミットがGitHubのIssue #3273を解決したことを意味します。

技術的詳細

このコミットは、godoc の自動同期機能に関連するコードを完全に削除することで、以下の技術的な変更を加えています。

  1. コマンドラインフラグの削除:
    • src/cmd/godoc/doc.go から、-sync-sync_minutes というコマンドラインフラグに関する説明が削除されました。これらのフラグは、同期コマンドとその実行間隔を設定するために使用されていました。
  2. 同期ロジックの削除:
    • src/cmd/godoc/main.go から、exec 関数と dosync 関数が完全に削除されました。
      • exec 関数は、指定された外部コマンドを新しいプロセスとして実行し、その出力をキャプチャする役割を担っていました。
      • dosync 関数は、exec 関数を呼び出して同期コマンドを実行し、その終了ステータスに基づいて godoc のインデックスを更新したり、同期間隔を調整(成功時は通常に戻し、失敗時は指数関数的にバックオフ)したりする主要な同期ロジックを含んでいました。
    • main.gomain 関数内から、dosync 関数をHTTPハンドラとして登録する部分 (http.Handle("/debug/sync", http.HandlerFunc(dosync))) と、定期的に同期を実行するためのゴルーチン (go func() { ... }) の起動ロジックが削除されました。
  3. 関連する変数の削除:
    • src/cmd/godoc/main.go から、同期機能に関連するグローバル変数(syncCmd, syncMin, syncDelay)が削除されました。
    • src/cmd/godoc/godoc.gofsTree 変数に関するコメントが「updated with each sync」から「updated with each sync (but sync code is removed now)」に変更され、同期機能が削除されたことが明示されています。
  4. インポートの削除:
    • src/cmd/godoc/main.go から、同期機能で必要とされていた time パッケージのインポートが削除されました。

これらの変更により、godoc は外部コマンドによる自動同期の責任から解放され、起動時に一度インデックスを作成するシンプルな動作に戻りました。ソースコードの更新は、godoc の外部で別途管理されることが前提となります。

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

このコミットにおける主要な変更は、以下の3つのファイルに集中しています。

  1. src/cmd/godoc/doc.go:

    • -sync-sync_minutes フラグに関する説明が削除されました。
    • インデックスの更新に関する説明から、同期コマンドの終了ステータスに基づくインデックス更新ロジック(成功時の更新、失敗時のバックオフなど)が削除され、「The index is created at startup.」というシンプルな記述に変更されました。
    --- a/src/cmd/godoc/doc.go
    +++ b/src/cmd/godoc/doc.go
    @@ -77,12 +77,6 @@ The flags are:
     		HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
     	-server=addr
     		webserver address for command line searches
    -	-sync="command"
    -		if this and -sync_minutes are set, run the argument as a
    -		command every sync_minutes; it is intended to update the
    -		repository holding the source files.
    -	-sync_minutes=0
    -		sync interval in minutes; sync is disabled if <= 0
     	-templates=""
     		directory containing alternate template files; if set,
     		the directory may provide alternative template files
    @@ -110,15 +104,7 @@ as follows:
     	/public/x          -> public/x
     
     When godoc runs as a web server and -index is set, a search index is maintained.
    -The index is created at startup and is automatically updated every time the
    --sync command terminates with exit status 0, indicating that files have changed.
    -
    -If the sync exit status is 1, godoc assumes that it succeeded without errors
    -but that no files changed; the index is not updated in this case.
    -
    -In all other cases, sync is assumed to have failed and godoc backs off running
    -sync exponentially (up to 1 day). As soon as sync succeeds again (exit status 0
    -or 1), the normal sync rhythm is re-established.
    +The index is created at startup.
     
     The index contains both identifier and full text search information (searchable
     via regular expressions). The maximum number of full text search results shown
    
  2. src/cmd/godoc/godoc.go:

    • fsTree 変数のコメントが更新され、同期コードが削除されたことが明記されました。
    --- a/src/cmd/godoc/godoc.go
    +++ b/src/cmd/godoc/godoc.go
    @@ -72,7 +72,7 @@ var (
     	indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
     
      	// file system information
    -	fsTree      RWValue // *Directory tree of packages, updated with each sync
    +	fsTree      RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
     	fsModified  RWValue // timestamp of last call to invalidateIndex
     	docMetadata RWValue // mapping from paths to *Metadata
    
  3. src/cmd/godoc/main.go:

    • time パッケージのインポートが削除されました。
    • syncCmd, syncMin, syncDelay といった同期関連のフラグ定義が削除されました。
    • exec 関数と dosync 関数が完全に削除されました。これらは外部コマンドの実行と同期ロジックをカプセル化していました。
    • main 関数内から、/debug/sync エンドポイントの登録と、定期的な同期を実行するゴルーチンの起動ロジックが削除されました。
    --- a/src/cmd/godoc/main.go
    +++ b/src/cmd/godoc/main.go
    @@ -45,7 +45,6 @@ import (
     	"regexp"
     	"runtime"
     	"strings"
    -	"time"
     )
     
     const defaultAddr = ":6060" // default webserver address
    @@ -58,11 +57,6 @@ var (
      	// file-based index
      	writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files")
     
    -	// periodic sync
    -	syncCmd   = flag.String("sync", "", "sync command; disabled if empty")
    -	syncMin   = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0")
    -	syncDelay delayTime // actual sync interval in minutes; usually syncDelay == syncMin, but syncDelay may back off exponentially
    -
      	// network
      	httpAddr   = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')")
      	serverAddr = flag.String("String("server", "", "webserver address for command line searches")
    @@ -82,75 +76,6 @@ func serveError(w http.ResponseWriter, r *http.Request, relpath string, err erro
      	servePage(w, "File "+relpath, "", "", contents)
      }
      
    -func exec(rw http.ResponseWriter, args []string) (status int) {
    -	r, w, err := os.Pipe()
    -	if err != nil {
    -		log.Printf("os.Pipe(): %v", err)
    -		return 2
    -	}
    -
    -	bin := args[0]
    -	fds := []*os.File{nil, w, w}
    -	if *verbose {
    -		log.Printf("executing %v", args)
    -	}
    -	p, err := os.StartProcess(bin, args, &os.ProcAttr{Files: fds, Dir: *goroot})
    -	defer r.Close()
    -	w.Close()
    -	if err != nil {
    -		log.Printf("os.StartProcess(%q): %v", bin, err)
    -		return 2
    -	}
    -
    -	var buf bytes.Buffer
    -	io.Copy(&buf, r)
    -	wait, err := p.Wait()
    -	if err != nil {
    -		os.Stderr.Write(buf.Bytes())
    -		log.Printf("os.Wait(%d, 0): %v", p.Pid, err)
    -		return 2
    -	}
    -	if !wait.Success() {
    -		os.Stderr.Write(buf.Bytes())
    -		log.Printf("executing %v failed", args)
    -		status = 1 // See comment in default case in dosync.
    -		return
    -	}
    -
    -	if *verbose {
    -		os.Stderr.Write(buf.Bytes())
    -	}
    -	if rw != nil {
    -		rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
    -		rw.Write(buf.Bytes())
    -	}
    -
    -	return
    -}
    -
    -func dosync(w http.ResponseWriter, r *http.Request) {
    -	args := []string{"/bin/sh", "-c", *syncCmd}
    -	switch exec(w, args) {
    -	case 0:
    -		// sync succeeded and some files have changed;
    -		// update package tree.
    -		// TODO(gri): The directory tree may be temporarily out-of-sync.
    -		//            Consider keeping separate time stamps so the web-
    -		//            page can indicate this discrepancy.
    -		initFSTree()
    -		fallthrough
    -	case 1:
    -		// sync failed because no files changed;
    -		// don't change the package tree
    -		syncDelay.set(time.Duration(*syncMin) * time.Minute) //  revert to regular sync schedule
    -	default:
    -		// TODO(r): this cannot happen now, since Wait has a boolean exit condition,
    -		// not an integer.
    -		// sync failed because of an error - back off exponentially, but try at least once a day
    -		syncDelay.backoff(24 * time.Hour)
    -	}
    -}
    -
      func usage() {
      	fmt.Fprintf(os.Stderr,
      		"usage: godoc package [name ...]\n"+
    @@ -348,30 +273,11 @@ func main() {
      		}
      
      		registerPublicHandlers(http.DefaultServeMux)
    -		if *syncCmd != "" {
    -			http.Handle("/debug/sync", http.HandlerFunc(dosync))
    -		}
      
      		// Initialize default directory tree with corresponding timestamp.
      		// (Do it in a goroutine so that launch is quick.)
      		go initFSTree()
      
    -		// Start sync goroutine, if enabled.
    -		if *syncCmd != "" && *syncMin > 0 {
    -			syncDelay.set(*syncMin) // initial sync delay
    -			go func() {
    -				for {
    -					dosync(nil, nil)
    -					delay, _ := syncDelay.get()
    -					dt := delay.(time.Duration)
    -					if *verbose {
    -						log.Printf("next sync in %s", dt)
    -					}
    -					time.Sleep(dt)
    -				}
    -			}()
    -		}
    -
      		// Immediately update metadata.
      		updateMetadata()
      		// Periodically refresh metadata.
    

コアとなるコードの解説

このコミットの核心は、godoc が外部のソースコードリポジトリを自動的に同期する機能を完全に排除した点にあります。

  • src/cmd/godoc/main.go からの exec および dosync 関数の削除:

    • exec 関数は、os.StartProcess を用いてシェルコマンドを実行し、その標準出力をキャプチャする汎用的なヘルパー関数でした。これは dosync 関数から呼び出され、実際の同期コマンド(例: git pull)を実行するために使われていました。
    • dosync 関数は、同期処理のメインロジックを担っていました。この関数は exec を呼び出して同期コマンドを実行し、その結果(終了ステータス)に応じて godoc の内部状態(パッケージツリーの更新)を調整していました。また、同期が失敗した場合には、指数関数的にバックオフして再試行間隔を延ばすロジックも含まれていました。
    • これらの関数が削除されたことで、godoc はもはや外部コマンドを実行して自身が参照するソースコードを更新する能力を持たなくなりました。
  • main 関数からの同期ゴルーチンとHTTPハンドラの削除:

    • 以前は、main 関数内で -sync-sync_minutes フラグが設定されている場合、dosync 関数を定期的に実行するゴルーチンが起動されていました。これにより、godoc はバックグラウンドで自動的にソースコードを同期し続けていました。
    • また、/debug/sync というHTTPエンドポイントが提供されており、これにアクセスすることで手動で同期をトリガーすることが可能でした。
    • これらの起動ロジックとハンドラが削除されたことで、godoc は起動時に一度インデックスを作成するのみとなり、その後のソースコードの変更は godoc の再起動、または外部からのファイルシステム更新によって反映されることになります。
  • src/cmd/godoc/doc.go および src/cmd/godoc/godoc.go の変更:

    • doc.go から同期関連のフラグ説明と、インデックス更新ロジックの詳細な説明が削除されたことで、ユーザーインターフェースとドキュメントから同期機能の存在が完全に消えました。
    • godoc.go のコメント修正は、コードベースの現状を正確に反映させるためのものです。

これらの変更は、godoc の役割を「ドキュメントの提供」に特化させ、ソースコードの管理・同期は godoc の外部で行うという明確な分離を示しています。これにより、godoc 自体のコードベースはよりシンプルで保守しやすくなり、セキュリティ上の懸念も解消されました。

関連リンク

  • Go Issue #3273: https://github.com/golang/go/issues/3273
    • このコミットが修正した具体的なIssue。godoc の同期コマンドがroot権限で実行される問題について議論されています。
  • Gerrit Change-Id 5795065: https://golang.org/cl/5795065
    • このコミットの元のGerritレビューページ。当時の議論や変更の経緯をより詳細に確認できます。

参考にした情報源リンク