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

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

このコミットは、Go言語の実験的なSSHパッケージ exp/ssh におけるセッション管理とコマンド実行のAPIを改善するものです。具体的には、リモートコマンド実行メソッド ExecRun にリネームし、os/exec パッケージとの一貫性を持たせています。さらに、リモートプロセスを非同期で開始する Start メソッドと、実行中のリモートプロセスにUNIXシグナルを送信する Signal メソッドが追加されました。これにより、SSHセッションを介したリモートプロセスのより柔軟な制御が可能になります。

コミット

commit c6691d1fb4e59ba620dd7f4fdb3ea24e8a0ab404
Author: Gustav Paul <gustav.paul@gmail.com>
Date:   Tue Nov 29 12:26:39 2011 -0500

    exp/ssh: Add Start(cmd string) and Signal(sig string) to Session. Rename Exec to Run.
    
    Exec() has been renamed to Run() in keeping with the os/exec API.
    
    Added func (*Session) Start(cmd string) which starts a remote process but unlike Run() doesn't wait for it to finish before returning.
    
    Run() has been refactored to use Start internally. Its really just a refactoring, no new code but some extra functionality was won.
    
    Also added func (*Session) Signal(sig signal) which sends a UNIX signal to a remote process. This is espcially useful in conjunction with Start() as the two allow you to start a remote process, monitor its stdout/stderr, and send it a TERM/HUP/etc signal when you want it to close.
    
    R=dave, rsc, agl, bradfitz, n13m3y3r, gustavo
    CC=golang-dev
    https://golang.org/cl/5437058
---
 src/pkg/exp/ssh/doc.go     |  4 +--
 src/pkg/exp/ssh/session.go | 64 ++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 58 insertions(+), 10 deletions(-)

diff --git a/src/pkg/exp/ssh/doc.go b/src/pkg/exp/ssh/doc.go
index 248b2fec4f..480f877191 100644
--- a/src/pkg/exp/ssh/doc.go
+++ b/src/pkg/exp/ssh/doc.go
@@ -92,9 +92,9 @@ Each ClientConn can support multiple interactive sessions, represented by a Sess
 	session, err := client.NewSession()
 
 Once a Session is created, you can execute a single command on the remote side 
-using the Exec method.
+using the Run method.
 
-\tif err := session.Exec(\"/usr/bin/whoami\"); err != nil {\n+\tif err := session.Run(\"/usr/bin/whoami\"); err != nil {\n \t\tpanic(\"Failed to exec: \" + err.String())\n \t}\n \treader := bufio.NewReader(session.Stdin)\ndiff --git a/src/pkg/exp/ssh/session.go b/src/pkg/exp/ssh/session.go
index cafa38cf50..dab0113f4b 100644
--- a/src/pkg/exp/ssh/session.go
+++ b/src/pkg/exp/ssh/session.go
@@ -15,6 +15,25 @@ import (
 	"io/ioutil"
 )
 
+type signal string
+
+// POSIX signals as listed in RFC 4254 Section 6.10.
+const (
+	SIGABRT signal = "ABRT"
+	SIGALRM signal = "ALRM"
+	SIGFPE  signal = "FPE"
+	SIGHUP  signal = "HUP"
+	SIGILL  signal = "ILL"
+	SIGINT  signal = "INT"
+	SIGKILL signal = "KILL"
+	SIGPIPE signal = "PIPE"
+	SIGQUIT signal = "QUIT"
+	SIGSEGV signal = "SEGV"
+	SIGTERM signal = "TERM"
+	SIGUSR1 signal = "USR1"
+	SIGUSR2 signal = "USR2"
+)
+
 // A Session represents a connection to a remote command or shell.
 type Session struct {
 	// Stdin specifies the remote process's standard input.
@@ -35,7 +54,7 @@ type Session struct {
 
 	*clientChan // the channel backing this session
 
-	started   bool // true once a Shell or Exec is invoked.
+	started   bool // true once a Shell or Run is invoked.
 	copyFuncs []func() error
 	errch     chan error // one send per copyFunc
 }
@@ -50,7 +69,7 @@ type setenvRequest struct {
 }
 
 // Setenv sets an environment variable that will be applied to any
-// command executed by Shell or Exec.
+// command executed by Shell or Run.
 func (s *Session) Setenv(name, value string) error {
 	req := setenvRequest{
 		PeersId:   s.peersId,
@@ -100,6 +119,26 @@ func (s *Session) RequestPty(term string, h, w int) error {\n \treturn s.waitForResponse()\n }\n \n+// RFC 4254 Section 6.9.\n+type signalMsg struct {\n+\tPeersId   uint32\n+\tRequest   string\n+\tWantReply bool\n+\tSignal    string\n+}\n+\n+// Signal sends the given signal to the remote process.\n+// sig is one of the SIG* constants.\n+func (s *Session) Signal(sig signal) error {\n+\treq := signalMsg{\n+\t\tPeersId:   s.peersId,\n+\t\tRequest:   \"signal\",\n+\t\tWantReply: false,\n+\t\tSignal:    string(sig),\n+\t}\n+\treturn s.writePacket(marshal(msgChannelRequest, req))\n+}\n+\n // RFC 4254 Section 6.5.\n type execMsg struct {\n \tPeersId   uint32\n@@ -108,10 +147,10 @@ type execMsg struct {\n \tCommand   string\n }\n \n-// Exec runs cmd on the remote host. Typically, the remote \n-// server passes cmd to the shell for interpretation. \n-// A Session only accepts one call to Exec or Shell.\n-func (s *Session) Exec(cmd string) error {\n+// Start runs cmd on the remote host. Typically, the remote\n+// server passes cmd to the shell for interpretation.\n+// A Session only accepts one call to Run, Start or Shell.\n+func (s *Session) Start(cmd string) error {\n \tif s.started {\n \t\treturn errors.New(\"ssh: session already started\")\n \t}\n@@ -127,14 +166,23 @@ func (s *Session) Exec(cmd string) error {\n \tif err := s.waitForResponse(); err != nil {\n \t\treturn fmt.Errorf(\"ssh: could not execute command %s: %v\", cmd, err)\n \t}\n-\tif err := s.start(); err != nil {\n+\treturn s.start()\n+}\n+\n+// Run runs cmd on the remote host and waits for it to terminate. \n+// Typically, the remote server passes cmd to the shell for \n+// interpretation. A Session only accepts one call to Run, \n+// Start or Shell.\n+func (s *Session) Run(cmd string) error {\n+\terr := s.Start(cmd)\n+\tif err != nil {\n \t\treturn err\n \t}\n \treturn s.Wait()\n }\n \n // Shell starts a login shell on the remote host. A Session only \n-// accepts one call to Exec or Shell.\n+// accepts one call to Run, Start or Shell.\n func (s *Session) Shell() error {\n \tif s.started {\n \t\treturn errors.New(\"ssh: session already started\")\n```

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

[https://github.com/golang/go/commit/c6691d1fb4e59ba620dd7f4fdb3ea24e8a0ab404](https://github.com/golang/go/commit/c6691d1fb4e59ba620dd7f4fdb3ea24e8a0ab404)

## 元コミット内容

`exp/ssh` パッケージの `Session` 型に `Start(cmd string)` と `Signal(sig string)` メソッドを追加し、`Exec` メソッドを `Run` にリネームしました。

`Exec()` は `os/exec` APIとの一貫性を保つために `Run()` にリネームされました。

`func (*Session) Start(cmd string)` はリモートプロセスを開始しますが、`Run()` とは異なり、プロセスが終了するのを待たずにすぐに戻ります。

`Run()` は内部的に `Start` を使用するようにリファクタリングされました。これは主にリファクタリングであり、新しいコードはほとんどありませんが、いくつかの追加機能が得られました。

また、`func (*Session) Signal(sig signal)` を追加しました。これはリモートプロセスにUNIXシグナルを送信します。これは特に `Start()` と組み合わせて使用すると便利です。この2つのメソッドにより、リモートプロセスを開始し、その標準出力/標準エラーを監視し、必要に応じて `TERM`/`HUP` などのシグナルを送信してプロセスを終了させることができます。

## 変更の背景

このコミットの主な背景は、Go言語の標準ライブラリにおけるAPIの一貫性と、SSHを介したリモートプロセス管理の機能強化です。

1.  **APIの一貫性**: 既存の `os/exec` パッケージは、ローカルプロセスの実行と管理のための標準的なAPIを提供しています。`exp/ssh` パッケージの `Session.Exec` メソッドは、リモートコマンドを実行する機能を持っていましたが、その命名が `os/exec` の `Run` メソッドと異なっていました。この不一致を解消し、Goの標準的な命名規則とパターンに合わせることで、開発者がSSHパッケージをより直感的に利用できるようにすることが目的でした。`Exec` を `Run` にリネームすることで、ローカルプロセスとリモートプロセスの実行が概念的に統一され、学習コストが削減されます。

2.  **リモートプロセス制御の強化**: 従来の `Exec` メソッドは、リモートコマンドを実行し、その完了を待つブロッキング操作でした。しかし、実際の運用では、リモートプロセスをバックグラウンドで起動したり、実行中のプロセスに対してシグナルを送って制御したりするニーズがあります。例えば、長時間実行されるサービスをSSH経由で起動し、その出力を監視しつつ、必要に応じて安全に停止させたい場合などです。
    *   `Start` メソッドの導入により、リモートプロセスを非同期で起動し、すぐに制御を呼び出し元に戻すことが可能になりました。これにより、Goプログラムはリモートプロセスの起動後も他のタスクを並行して実行できるようになります。
    *   `Signal` メソッドの導入は、`Start` と組み合わせて、実行中のリモートプロセスに対して `SIGTERM` (終了要求)、`SIGHUP` (設定再読み込み)、`SIGKILL` (強制終了) などのUNIXシグナルを送信する機能を提供します。これは、リモートプロセスのライフサイクル管理において非常に重要な機能であり、より堅牢なリモートオートメーションツールを構築するために不可欠です。

これらの変更は、`exp/ssh` パッケージがより成熟し、実用的なSSHクライアントライブラリとして機能するための重要なステップでした。

## 前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

1.  **SSH (Secure Shell)**:
    *   ネットワークを介してコンピュータを安全に操作するためのプロトコルです。主にリモートログインやコマンド実行、ファイル転送などに使用されます。
    *   クライアントとサーバー間で暗号化された通信チャネルを確立し、データの盗聴や改ざんを防ぎます。
    *   SSHセッションは、リモートホスト上でコマンドを実行したり、シェルを起動したりするための論理的な接続です。

2.  **Go言語の `os/exec` パッケージ**:
    *   Goの標準ライブラリの一部で、ローカルシステム上で外部コマンドを実行するための機能を提供します。
    *   `exec.Command` でコマンドオブジェクトを作成し、`Run()` メソッドでコマンドを実行して完了を待ちます。
    *   `Start()` メソッドでコマンドを非同期で開始し、`Wait()` メソッドでその完了を待つことができます。
    *   `Stdin`, `Stdout`, `Stderr` フィールドを通じて、実行中のプロセスの標準入出力にアクセスできます。

3.  **UNIXシグナル**:
    *   UNIX系オペレーティングシステムにおいて、プロセス間通信やプロセス制御のために使用されるソフトウェア割り込みの一種です。
    *   特定のイベント(例: Ctrl+Cによる割り込み、タイマーの期限切れ、子プロセスの終了)が発生した際に、カーネルがプロセスにシグナルを送信します。
    *   プロセスはシグナルを受信すると、デフォルトの動作を実行するか、シグナルハンドラと呼ばれる特定の関数を実行するように設定できます。
    *   一般的なシグナルとその意味:
        *   `SIGINT` (2): 割り込み。通常、Ctrl+Cで送信され、プロセスを終了させる。
        *   `SIGTERM` (15): 終了要求。プロセスにクリーンアップして終了するよう要求する。
        *   `SIGHUP` (1): 端末の切断。デーモンプロセスでは設定ファイルの再読み込みによく使われる。
        *   `SIGKILL` (9): 強制終了。プロセスはこれを無視したり捕捉したりできず、即座に終了させられる。
        *   `SIGUSR1`, `SIGUSR2`: ユーザー定義シグナル。アプリケーションが独自の目的で使用できる。

4.  **RFC 4254 (The Secure Shell (SSH) Connection Protocol)**:
    *   SSHプロトコルの接続層を定義するIETFの標準ドキュメントです。
    *   SSHセッション内でどのようにチャネルが確立され、コマンドが実行され、シグナルが送信されるかなど、詳細なプロトコル仕様が記述されています。
    *   特に、このコミットで参照されているセクション6.9は「Signals」に関するもので、SSHプロトコルがどのようにリモートプロセスへのシグナル送信をサポートしているかを規定しています。

これらの知識は、Goの `exp/ssh` パッケージがどのようにSSHプロトコルを実装し、リモートプロセスを制御しているかを理解する上で不可欠です。

## 技術的詳細

このコミットは、Goの `exp/ssh` パッケージ内の `session.go` ファイルと `doc.go` ファイルに焦点を当てています。

### `exp/ssh` パッケージの `Session` 構造体

`exp/ssh` パッケージの `Session` 構造体は、SSHクライアントがリモートホスト上で単一のコマンド実行またはシェルセッションを管理するための主要なエンティティです。この構造体は、リモートプロセスの標準入出力 (`Stdin`, `Stdout`, `Stderr`) をGoの `io.Reader` および `io.Writer` インターフェースとして提供し、Goプログラムがリモートプロセスと対話できるようにします。

### `Exec` から `Run` へのリネーム

*   **変更点**: `Session` 構造体の `Exec(cmd string) error` メソッドが `Run(cmd string) error` にリネームされました。
*   **理由**: Goの標準ライブラリ `os/exec` パッケージには、ローカルコマンドを実行し、その完了を待つ `Command.Run()` メソッドがあります。このリネームは、`exp/ssh` パッケージのAPIを `os/exec` パッケージのAPIと整合させることを目的としています。これにより、開発者はローカルコマンドとリモートコマンドの実行パターンをより一貫した方法で扱うことができます。

### `Start(cmd string)` メソッドの追加

*   **機能**: `func (s *Session) Start(cmd string) error` は、リモートホスト上で指定されたコマンド `cmd` を実行しますが、そのプロセスが終了するのを待ちません。コマンドが正常に開始されたらすぐに制御を呼び出し元に返します。
*   **内部動作**: このメソッドは、SSHプロトコルの `exec` リクエスト(RFC 4254 Section 6.5)をリモートSSHサーバーに送信します。このリクエストには実行するコマンドが含まれます。サーバーはコマンドを開始し、クライアントはコマンドの開始が成功したことを確認した後、すぐに戻ります。
*   **利点**: これにより、Goプログラムはリモートで長時間実行されるプロセスを起動し、そのプロセスがバックグラウンドで実行されている間に他のタスクを並行して処理できるようになります。例えば、リモートサーバー上でサービスを起動し、その起動ログを監視しつつ、別のSSHセッションで他の操作を行うといったことが可能になります。

### `Run` メソッドのリファクタリング

*   **変更点**: 新しい `Run(cmd string) error` メソッドは、内部的に `Start(cmd string)` を呼び出し、その後 `Session.Wait()` メソッドを呼び出してリモートプロセスの完了を待ちます。
*   **利点**: このリファクタリングにより、`Run` メソッドのロジックが簡素化され、`Start` と `Wait` というより基本的な操作の組み合わせとして表現されるようになりました。これはコードの再利用性を高め、将来的なメンテナンスを容易にします。機能的には、以前の `Exec` と同じく、コマンドの完了を待つブロッキング操作を提供します。

### `Signal(sig signal)` メソッドの追加

*   **機能**: `func (s *Session) Signal(sig signal) error` は、実行中のリモートプロセスにUNIXシグナルを送信します。`signal` 型は、`SIGTERM`, `SIGHUP`, `SIGINT` などのPOSIXシグナル定数を定義する新しい型です。
*   **内部動作**: このメソッドは、SSHプロトコルの `signal` リクエスト(RFC 4254 Section 6.9)をリモートSSHサーバーに送信します。このリクエストには、送信するシグナルの名前(例: "TERM")が含まれます。SSHサーバーは、そのシグナルを対応するリモートプロセスに転送します。
*   **利点**: `Start` メソッドと組み合わせることで、Goプログラムはリモートで起動したプロセスをより細かく制御できるようになります。例えば、`Start` でプロセスを起動し、必要に応じて `Signal(SIGTERM)` を送信してプロセスに優雅な終了を要求したり、`Signal(SIGHUP)` を送信して設定の再読み込みをトリガーしたりすることができます。これは、リモートのデーモンプロセスやサービスを管理する上で非常に強力な機能です。

### `signal` 型と定数

*   **追加**: `type signal string` という新しい型が定義され、`SIGABRT`, `SIGALRM`, `SIGFPE`, `SIGHUP`, `SIGILL`, `SIGINT`, `SIGKILL`, `SIGPIPE`, `SIGQUIT`, `SIGSEGV`, `SIGTERM`, `SIGUSR1`, `SIGUSR2` といったPOSIXシグナルに対応する定数が追加されました。これらの定数は、RFC 4254 Section 6.10にリストされているシグナル名に基づいています。
*   **目的**: これにより、`Signal` メソッドを呼び出す際に、文字列リテラルではなく型安全な定数を使用できるようになり、コードの可読性と堅牢性が向上します。

これらの変更は、`exp/ssh` パッケージがより強力で柔軟なリモートプロセス管理機能を提供するための基盤を築いています。

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

このコミットにおける主要なコード変更は、`src/pkg/exp/ssh/session.go` と `src/pkg/exp/ssh/doc.go` の2つのファイルに集中しています。

### `src/pkg/exp/ssh/doc.go`

*   `Exec` の言及が `Run` に変更されました。これは、APIのリネームをドキュメントに反映させるための修正です。

```diff
--- a/src/pkg/exp/ssh/doc.go
+++ b/src/pkg/exp/ssh/doc.go
@@ -92,9 +92,9 @@ Each ClientConn can support multiple interactive sessions, represented by a Sess
 	session, err := client.NewSession()
 
 Once a Session is created, you can execute a single command on the remote side 
-using the Exec method.
+using the Run method.
 
-\tif err := session.Exec(\"/usr/bin/whoami\"); err != nil {\n+\tif err := session.Run(\"/usr/bin/whoami\"); err != nil {\n \t\tpanic(\"Failed to exec: \" + err.String())\n \t}\n \treader := bufio.NewReader(session.Stdin)\n```

### `src/pkg/exp/ssh/session.go`

このファイルには、以下の重要な変更が含まれています。

1.  **`signal` 型と定数の追加**:
    *   `type signal string` が定義され、RFC 4254 Section 6.10 に基づくPOSIXシグナル名(例: `SIGTERM`, `SIGHUP`)が定数として追加されました。

    ```go
    type signal string

    // POSIX signals as listed in RFC 4254 Section 6.10.
    const (
        SIGABRT signal = "ABRT"
        SIGALRM signal = "ALRM"
        SIGFPE  signal = "FPE"
        SIGHUP  signal = "HUP"
        SIGILL  signal = "ILL"
        SIGINT  signal = "INT"
        SIGKILL signal = "KILL"
        SIGPIPE signal = "PIPE"
        SIGQUIT signal = "QUIT"
        SIGSEGV signal = "SEGV"
        SIGTERM signal = "TERM"
        SIGUSR1 signal = "USR1"
        SIGUSR2 signal = "USR2"
    )
    ```

2.  **`Session` 構造体の `started` フィールドのコメント更新**:
    *   `Exec` が `Run` にリネームされたことに伴い、コメントが更新されました。

    ```diff
    --- a/src/pkg/exp/ssh/session.go
    +++ b/src/pkg/exp/ssh/session.go
    @@ -35,7 +54,7 @@ type Session struct {
     
     	*clientChan // the channel backing this session
     
    -	started   bool // true once a Shell or Exec is invoked.
    +	started   bool // true once a Shell or Run is invoked.
     	copyFuncs []func() error
     	errch     chan error // one send per copyFunc
     }
    ```

3.  **`Setenv` メソッドのコメント更新**:
    *   同様に、`Exec` が `Run` にリネームされたことに伴い、コメントが更新されました。

    ```diff
    --- a/src/pkg/exp/ssh/session.go
    +++ b/src/pkg/exp/ssh/session.go
    @@ -50,7 +69,7 @@ type setenvRequest struct {
     }
     
     // Setenv sets an environment variable that will be applied to any
    -// command executed by Shell or Exec.
    +// command executed by Shell or Run.
     func (s *Session) Setenv(name, value string) error {
     	req := setenvRequest{
     		PeersId:   s.peersId,
    ```

4.  **`signalMsg` 構造体と `Signal` メソッドの追加**:
    *   SSHプロトコルの `signal` リクエストに対応する `signalMsg` 構造体が定義されました。
    *   `Signal(sig signal) error` メソッドが追加され、リモートプロセスにシグナルを送信する機能を提供します。

    ```go
    // RFC 4254 Section 6.9.
    type signalMsg struct {
        PeersId   uint32
        Request   string
        WantReply bool
        Signal    string
    }

    // Signal sends the given signal to the remote process.
    // sig is one of the SIG* constants.
    func (s *Session) Signal(sig signal) error {
        req := signalMsg{
            PeersId:   s.peersId,
            Request:   "signal",
            WantReply: false,
            Signal:    string(sig),
        }
        return s.writePacket(marshal(msgChannelRequest, req))
    }
    ```

5.  **`Exec` メソッドのリネームと `Start` メソッドへの変更**:
    *   既存の `Exec` メソッドが `Start` にリネームされ、そのコメントも更新されました。これにより、コマンドを非同期で開始する機能が提供されます。

    ```diff
    --- a/src/pkg/exp/ssh/session.go
    +++ b/src/pkg/exp/ssh/session.go
    @@ -108,10 +147,10 @@ type execMsg struct {
     	Command   string
     }
     
    -// Exec runs cmd on the remote host. Typically, the remote 
    -// server passes cmd to the shell for interpretation. 
    -// A Session only accepts one call to Exec or Shell.\n-func (s *Session) Exec(cmd string) error {
    +// Start runs cmd on the remote host. Typically, the remote
    +// server passes cmd to the shell for interpretation.
    +// A Session only accepts one call to Run, Start or Shell.
    +func (s *Session) Start(cmd string) error {
     	if s.started {
     		return errors.New("ssh: session already started")
     	}
    @@ -127,14 +166,23 @@ func (s *Session) Exec(cmd string) error {
     	if err := s.waitForResponse(); err != nil {\n     		return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)\n     	}\n    -\tif err := s.start(); err != nil {\n    +\treturn s.start()\n     	\treturn err\n     	}\n     	return s.Wait()\n     }
    ```

6.  **`Run` メソッドの追加とリファクタリング**:
    *   新しい `Run` メソッドが追加され、内部で `Start` を呼び出し、その後 `Wait` を呼び出すように実装されました。これにより、コマンドの完了を待つブロッキング実行が提供されます。

    ```go
    // Run runs cmd on the remote host and waits for it to terminate. 
    // Typically, the remote server passes cmd to the shell for 
    // interpretation. A Session only accepts one call to Run, 
    // Start or Shell.
    func (s *Session) Run(cmd string) error {
        err := s.Start(cmd)
        if err != nil {
            return err
        }
        return s.Wait()
    }
    ```

7.  **`Shell` メソッドのコメント更新**:
    *   `Exec` が `Run` にリネームされ、`Start` が追加されたことに伴い、コメントが更新されました。

    ```diff
    --- a/src/pkg/exp/ssh/session.go
    +++ b/src/pkg/exp/ssh/session.go
    @@ -142,7 +190,7 @@ func (s *Session) Run(cmd string) error {
     }
     
     // Shell starts a login shell on the remote host. A Session only 
    -// accepts one call to Exec or Shell.
    +// accepts one call to Run, Start or Shell.
     func (s *Session) Shell() error {
     	if s.started {
     		return errors.New("ssh: session already started")
    ```

これらの変更は、`exp/ssh` パッケージのAPIをより直感的で強力なものにするための重要なステップです。

## コアとなるコードの解説

このコミットのコアとなる変更は、`exp/ssh` パッケージの `Session` 型に新しい機能を追加し、既存の機能をリファクタリングすることにあります。

### 1. `signal` 型とPOSIXシグナル定数

```go
type signal string

// POSIX signals as listed in RFC 4254 Section 6.10.
const (
	SIGABRT signal = "ABRT"
	// ... (他のシグナル定数)
	SIGTERM signal = "TERM"
	// ...
)

これは、UNIXシグナルを表現するための新しい型 signal を定義しています。そして、RFC 4254で定義されている標準的なPOSIXシグナル名に対応する定数を宣言しています。これにより、Signal メソッドを呼び出す際に、マジック文字列ではなく、型安全な定数を使用できるようになり、コードの可読性とエラー耐性が向上します。

2. signalMsg 構造体と Signal メソッド

// RFC 4254 Section 6.9.
type signalMsg struct {
	PeersId   uint32
	Request   string
	WantReply bool
	Signal    string
}

// Signal sends the given signal to the remote process.
// sig is one of the SIG* constants.
func (s *Session) Signal(sig signal) error {
	req := signalMsg{
		PeersId:   s.peersId,
		Request:   "signal",
		WantReply: false,
		Signal:    string(sig),
	}
	return s.writePacket(marshal(msgChannelRequest, req))
}

signalMsg 構造体は、SSHプロトコルを介してリモートプロセスにシグナルを送信するためのリクエストメッセージの形式を定義しています。PeersId はセッションを識別するためのID、Request はリクエストの種類(ここでは "signal")、WantReply は応答を期待するかどうか、Signal は送信するシグナルの名前(例: "TERM")です。

Signal メソッドは、この signalMsg を構築し、s.writePacket を使用してSSHチャネル経由でリモートサーバーに送信します。これにより、Goプログラムからリモートで実行中のプロセスに対して、例えば SIGTERM を送信して優雅な終了を促すといった操作が可能になります。これは、リモートプロセスのライフサイクル管理において非常に重要な機能です。

3. Exec から Start へのリネームと Run のリファクタリング

// 旧 Exec メソッドが Start に変更
// Start runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start or Shell.
func (s *Session) Start(cmd string) error {
	if s.started {
		return errors.New("ssh: session already started")
	}
	s.started = true
	// ... (execMsg の構築と送信)
	return s.start() // コマンド開始後の処理
}

// Run runs cmd on the remote host and waits for it to terminate.
// Typically, the remote server passes cmd to the shell for
// interpretation. A Session only accepts one call to Run,
// Start or Shell.
func (s *Session) Run(cmd string) error {
	err := s.Start(cmd) // Start を呼び出してコマンドを非同期で開始
	if err != nil {
		return err
	}
	return s.Wait() // Wait を呼び出してコマンドの終了を待つ
}

以前の Exec メソッドは、コマンドを実行し、その完了を待つブロッキング操作でした。このコミットでは、その役割を2つのメソッドに分割しました。

  • Start(cmd string): このメソッドは、リモートコマンドを非同期で開始します。つまり、コマンドがリモートで起動されたことを確認したら、その完了を待たずにすぐに戻ります。これにより、Goプログラムはリモートプロセスの起動後も他の処理を続行できます。
  • Run(cmd string): このメソッドは、os/exec パッケージの Run メソッドとの一貫性を保つために導入されました。内部的には Start(cmd) を呼び出してコマンドを起動し、その後 s.Wait() を呼び出してリモートプロセスの終了を待ちます。これにより、以前の Exec と同じブロッキング実行のセマンティクスを提供しつつ、コードの内部構造をよりモジュール化しています。

このリファクタリングにより、開発者はリモートコマンドの実行をより柔軟に制御できるようになりました。非同期実行が必要な場合は Start を、完了を待つ場合は Run を選択できます。

これらの変更は、exp/ssh パッケージがより強力で、Goの他の標準ライブラリと一貫性のあるAPIを提供する上で不可欠なものです。

関連リンク

参考にした情報源リンク