[インデックス 14254] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるLinux固有の実行(exec_linux
)処理において、制御端末(controlling tty)を変更する機能を追加するものです。具体的には、SysProcAttr
構造体に新しいフィールドCtty
を導入し、TIOCSCTTY
ioctlシステムコールが特定のファイルディスクリプタに対して実行できるように修正することで、プロセスが任意のファイルディスクリプタを制御端末として設定できるようにします。これにより、less
のようなアプリケーションが標準入力以外のファイルディスクリプタを制御端末として利用できるようになります。
コミット
- コミットハッシュ:
3494010f7dc1705d6317c82bfe458f61cf2bca66
- 作者: Peter Waller peter.waller@gmail.com
- コミット日時: 2012年10月30日 火曜日 17:36:18 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3494010f7dc1705d6317c82bfe458f61cf2bca66
元コミット内容
syscall/exec_linux: enable changing controlling tty
As discussed in the following thread:
https://groups.google.com/forum/?fromgroups=#!topic/golang-dev/emeJffxWhVo
This is required to enable applications such as `less` to use something
other than stdin as the controlling terminal.
R=dave, iant
CC=bradfitz, golang-dev
https://golang.org/cl/6785057
変更の背景
この変更の主な背景は、Go言語で作成されたアプリケーションが、より高度な端末操作を必要とする外部プログラム(例: less
)を適切に実行できるようにすることです。
従来のGoのsyscall
パッケージでは、新しいプロセスが制御端末を設定する際、デフォルトでファイルディスクリプタ0(標準入力)を制御端末として設定するようになっていました。しかし、less
のようなページャーや、vi
/emacs
のようなテキストエディタ、あるいはシェルスクリプトなど、一部の対話型アプリケーションは、標準入力以外の特定のファイルディスクリプタ(例えば、擬似端末(pty)のマスター側から渡されたファイルディスクリプタ)を自身の制御端末として設定する必要がある場合があります。
もしアプリケーションが標準入力以外のファイルディスクリプタを制御端末として利用しようとした場合、Goの既存のAPIではその指定ができませんでした。これにより、Goで書かれたプログラムからこれらの外部アプリケーションを起動する際に、端末の制御が正しく行われず、期待通りの動作をしないという問題が発生していました。
この問題は、Goの開発者コミュニティのメーリングリスト(golang-dev
)で議論され、その結果として、制御端末として設定するファイルディスクリプタを明示的に指定できる機能の必要性が認識されました。このコミットは、その議論と必要性に応える形で実装されました。
前提知識の解説
このコミットを理解するためには、以下の概念を把握しておく必要があります。
-
TTY (Teletypewriter) / 制御端末 (Controlling Terminal):
- Unix/Linuxシステムにおける「端末」は、ユーザーがシステムと対話するためのインターフェースです。物理的な端末(シリアルポート接続など)から、仮想コンソール(Ctrl+Alt+F1などで切り替えるもの)、そしてSSH接続などで使われる擬似端末(Pseudo-Terminal, PTY)まで多岐にわたります。
- 「制御端末」とは、プロセスグループが関連付けられている端末のことです。プロセスグループのリーダープロセスが制御端末を開くと、その端末が制御端末となります。制御端末は、その端末からの入力(キーボード入力など)をプロセスグループに渡し、プロセスグループからの出力(
stdout
,stderr
)を受け取って表示します。また、シグナル(例: Ctrl+CによるSIGINT
、Ctrl+ZによるSIGTSTP
)も制御端末を通じてプロセスグループに送られます。 - 各セッション(後述)は最大で一つの制御端末を持つことができます。
-
セッション (Session) とプロセスグループ (Process Group):
- プロセスグループ: 1つ以上のプロセスからなる集合で、共通のプロセスグループID (PGID) を持ちます。プロセスグループは、シグナル(例:
SIGINT
)の配送単位として機能します。例えば、シェルでパイプライン(cmd1 | cmd2
)を実行すると、cmd1
とcmd2
は同じプロセスグループに属し、Ctrl+Cを押すと両方にシグナルが送られます。 - セッション: 1つ以上のプロセスグループからなる集合で、共通のセッションID (SID) を持ちます。セッションは、通常、ログインシェルが起動されるときに作成され、そのセッション内のすべてのプロセスグループは同じ制御端末を共有します。
setsid()
システムコールは、新しいセッションを作成し、呼び出し元のプロセスをそのセッションのリーダーにし、新しいプロセスグループのリーダーにもします。このとき、呼び出し元のプロセスは制御端末を持たない状態になります。
- プロセスグループ: 1つ以上のプロセスからなる集合で、共通のプロセスグループID (PGID) を持ちます。プロセスグループは、シグナル(例:
-
ioctl
システムコールとTIOCSCTTY
:ioctl
(Input/Output Control) は、デバイス固有の操作を行うための汎用的なシステムコールです。ファイルディスクリプタに関連付けられたデバイスに対して、様々な制御コマンドを送ることができます。TIOCSCTTY
(Terminal IO Control Set Controlling TTY) は、ioctl
コマンドの一つで、指定されたファイルディスクリプタを呼び出し元プロセスの制御端末として設定するために使用されます。この操作は、通常、新しいセッションのリーダープロセスによって行われます。この操作が成功すると、そのファイルディスクリプタが指す端末が、そのセッションの制御端末となります。
-
Go言語の
syscall
パッケージ:- Go言語の
syscall
パッケージは、オペレーティングシステム(OS)の低レベルなシステムコールへのインターフェースを提供します。これにより、Goプログラムから直接OSの機能(ファイル操作、プロセス管理、ネットワーク通信など)を呼び出すことができます。 RawSyscall
は、syscall
パッケージ内で提供される関数の一つで、OSのシステムコールを直接呼び出すための低レベルなメカニズムです。通常、3つの引数(システムコール番号、引数1、引数2)を取り、3つの戻り値(戻り値1、戻り値2、エラーコード)を返します。
- Go言語の
-
SysProcAttr
構造体:- Go言語の
os/exec
パッケージで外部コマンドを実行する際に、プロセスの属性(例: 環境変数、作業ディレクトリ、権限など)を細かく制御するために使用される構造体です。OS固有の属性を設定するためのフィールドが含まれています。このコミットでは、Linux固有のSysProcAttr
構造体が変更されています。
- Go言語の
技術的詳細
このコミットの技術的な核心は、Go言語のsyscall
パッケージが、新しいプロセスを生成する際に、そのプロセスの制御端末をより柔軟に設定できるようにした点にあります。
変更前は、SysProcAttr
構造体のSetctty
フィールドがtrue
に設定されている場合、forkAndExecInChild
関数内で無条件にファイルディスクリプタ0(標準入力)に対してTIOCSCTTY
ioctlシステムコールが実行されていました。これは、多くの場合で問題ありませんでしたが、特定のシナリオ(例: less
が標準入力以外のファイルディスクリプタを制御端末として期待する場合)では不十分でした。
このコミットでは、以下の2つの主要な変更が導入されました。
-
SysProcAttr
構造体へのCtty
フィールドの追加:src/pkg/syscall/exec_linux.go
内のSysProcAttr
構造体に、Ctty int
という新しいフィールドが追加されました。このフィールドは、制御端末として設定したいファイルディスクリプタの番号を保持します。Setctty
フィールドのコメントも「Set controlling terminal to fd 0」から「Set controlling terminal to fd Ctty (only meaningful if Setsid is set)」に変更され、Ctty
フィールドとの関連性およびSetsid
(新しいセッションを作成する)との組み合わせが重要であることが明示されました。これは、制御端末を設定する操作は、通常、セッションリーダーによって行われるためです。
-
forkAndExecInChild
関数におけるTIOCSCTTY
呼び出しの変更:forkAndExecInChild
関数は、子プロセスをフォークし、指定された実行可能ファイルをその子プロセスで実行するGoランタイムの内部関数です。- 変更前は、
sys.Setctty
がtrue
の場合、RawSyscall(SYS_IOCTL, 0, uintptr(TIOCSCTTY), 0)
が呼び出されていました。これは、ファイルディスクリプタ0を制御端末に設定することを意味します。 - 変更後、この行は
if sys.Setctty && sys.Ctty >= 0 { _, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0) }
に変更されました。sys.Setctty && sys.Ctty >= 0
という条件が追加され、Setctty
がtrue
であり、かつCtty
フィールドが有効なファイルディスクリプタ(0以上)である場合にのみ、TIOCSCTTY
が実行されるようになりました。RawSyscall
の第2引数(TIOCSCTTY
を適用するファイルディスクリプタ)が、ハードコードされた0
からuintptr(sys.Ctty)
に変更されました。これにより、SysProcAttr.Ctty
で指定されたファイルディスクリプタが制御端末として設定されるようになります。
これらの変更により、Goプログラムはos/exec.Cmd
のSysProcAttr
を設定する際に、Setctty
をtrue
にし、かつCtty
に適切なファイルディスクリプタ番号(例: 擬似端末のマスター側から取得したファイルディスクリプタ)を指定することで、子プロセスがそのファイルディスクリプタを制御端末として利用できるようになります。これは、端末エミュレータやシェル、あるいはless
のような対話型プログラムをGoから起動する際に、より正確な端末制御を可能にする重要な機能強化です。
コアとなるコードの変更箇所
--- a/src/pkg/syscall/exec_linux.go
+++ b/src/pkg/syscall/exec_linux.go
@@ -16,8 +16,9 @@ type SysProcAttr struct {
Ptrace bool // Enable tracing.
Setsid bool // Create session.
Setpgid bool // Set process group ID to new pid (SYSV setpgrp)
- Setctty bool // Set controlling terminal to fd 0
+ Setctty bool // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
Noctty bool // Detach fd 0 from controlling terminal
+ Ctty int // Controlling TTY fd (Linux only)
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
}
@@ -206,9 +207,9 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
}\n \t}\n \n-\t// Make fd 0 the tty\n-\tif sys.Setctty {\n-\t\t_, _, err1 = RawSyscall(SYS_IOCTL, 0, uintptr(TIOCSCTTY), 0)\n+\t// Set the controlling TTY to Ctty\n+\tif sys.Setctty && sys.Ctty >= 0 {\n+\t\t_, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)\n \t\tif err1 != 0 {\n \t\t\tgoto childerror\n \t\t}\n```
## コアとなるコードの解説
### `SysProcAttr`構造体の変更
```go
type SysProcAttr struct {
// ... 既存のフィールド ...
Setctty bool // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
Noctty bool // Detach fd 0 from controlling terminal
Ctty int // Controlling TTY fd (Linux only)
// ... その他のフィールド ...
}
-
Setctty
フィールドのコメント変更:- 変更前:
Setctty bool // Set controlling terminal to fd 0
- 変更後:
Setctty bool // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
- このコメント変更は、
Setctty
がもはやファイルディスクリプタ0に固定されず、新しく追加されるCtty
フィールドの値を使用することを示しています。また、「Setsid
が設定されている場合にのみ意味がある」という注意書きが追加されました。これは、制御端末を設定する操作は、通常、新しいセッションのリーダープロセス(Setsid
によって作成される)によって行われるべきであるというUnix/Linuxの慣習に基づいています。
- 変更前:
-
Ctty
フィールドの追加:Ctty int // Controlling TTY fd (Linux only)
- この新しいフィールドは、制御端末として設定したいファイルディスクリプタの番号を整数で指定するために導入されました。これにより、開発者は標準入力(fd 0)以外の任意のファイルディスクリプタを制御端末として指定できるようになります。
Linux only
と明記されているのは、この機能がLinuxカーネルの特定のioctl
コマンドに依存するためです。
forkAndExecInChild
関数の変更
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *SysProcAttr, pipe *[2]int) (pid int, err error) {
// ... 既存のコード ...
// Set the controlling TTY to Ctty
if sys.Setctty && sys.Ctty >= 0 {
_, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)
if err1 != 0 {
goto childerror
}
}
// ... 既存のコード ...
}
-
条件分岐の変更:
- 変更前:
if sys.Setctty { ... }
- 変更後:
if sys.Setctty && sys.Ctty >= 0 { ... }
sys.Ctty >= 0
という条件が追加されました。これは、Ctty
フィールドが有効なファイルディスクリプタ番号(非負の値)である場合にのみ、制御端末の設定を試みることを保証します。これにより、無効なファイルディスクリプタ番号が指定された場合の不必要なシステムコール呼び出しやエラーを避けることができます。
- 変更前:
-
RawSyscall
の引数変更:- 変更前:
RawSyscall(SYS_IOCTL, 0, uintptr(TIOCSCTTY), 0)
- 変更後:
RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)
RawSyscall
の第2引数(TIOCSCTTY
コマンドを適用するファイルディスクリプタ)が、ハードコードされた0
からuintptr(sys.Ctty)
に変更されました。これにより、SysProcAttr.Ctty
で指定されたファイルディスクリプタが、新しく生成される子プロセスの制御端末として設定されるようになります。
- 変更前:
これらの変更により、Go言語のos/exec
パッケージを通じて外部プロセスを起動する際に、より柔軟かつ正確な制御端末の割り当てが可能となり、less
のような特定の端末動作を要求するアプリケーションとの連携が改善されました。
関連リンク
- Go言語開発メーリングリストでの議論:
- Go Code Review (CL):
参考にした情報源リンク
- 本解説は、提供されたコミット情報、Go言語の
syscall
パッケージおよびos/exec
パッケージに関する一般的な知識、Unix/Linuxのプロセス管理(TTY, セッション, プロセスグループ)に関する一般的な知識に基づいて作成されています。