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

[インデックス 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)で議論され、その結果として、制御端末として設定するファイルディスクリプタを明示的に指定できる機能の必要性が認識されました。このコミットは、その議論と必要性に応える形で実装されました。

前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

  1. TTY (Teletypewriter) / 制御端末 (Controlling Terminal):

    • Unix/Linuxシステムにおける「端末」は、ユーザーがシステムと対話するためのインターフェースです。物理的な端末(シリアルポート接続など)から、仮想コンソール(Ctrl+Alt+F1などで切り替えるもの)、そしてSSH接続などで使われる擬似端末(Pseudo-Terminal, PTY)まで多岐にわたります。
    • 「制御端末」とは、プロセスグループが関連付けられている端末のことです。プロセスグループのリーダープロセスが制御端末を開くと、その端末が制御端末となります。制御端末は、その端末からの入力(キーボード入力など)をプロセスグループに渡し、プロセスグループからの出力(stdout, stderr)を受け取って表示します。また、シグナル(例: Ctrl+CによるSIGINT、Ctrl+ZによるSIGTSTP)も制御端末を通じてプロセスグループに送られます。
    • 各セッション(後述)は最大で一つの制御端末を持つことができます。
  2. セッション (Session) とプロセスグループ (Process Group):

    • プロセスグループ: 1つ以上のプロセスからなる集合で、共通のプロセスグループID (PGID) を持ちます。プロセスグループは、シグナル(例: SIGINT)の配送単位として機能します。例えば、シェルでパイプライン(cmd1 | cmd2)を実行すると、cmd1cmd2は同じプロセスグループに属し、Ctrl+Cを押すと両方にシグナルが送られます。
    • セッション: 1つ以上のプロセスグループからなる集合で、共通のセッションID (SID) を持ちます。セッションは、通常、ログインシェルが起動されるときに作成され、そのセッション内のすべてのプロセスグループは同じ制御端末を共有します。setsid()システムコールは、新しいセッションを作成し、呼び出し元のプロセスをそのセッションのリーダーにし、新しいプロセスグループのリーダーにもします。このとき、呼び出し元のプロセスは制御端末を持たない状態になります。
  3. ioctlシステムコールとTIOCSCTTY:

    • ioctl (Input/Output Control) は、デバイス固有の操作を行うための汎用的なシステムコールです。ファイルディスクリプタに関連付けられたデバイスに対して、様々な制御コマンドを送ることができます。
    • TIOCSCTTY (Terminal IO Control Set Controlling TTY) は、ioctlコマンドの一つで、指定されたファイルディスクリプタを呼び出し元プロセスの制御端末として設定するために使用されます。この操作は、通常、新しいセッションのリーダープロセスによって行われます。この操作が成功すると、そのファイルディスクリプタが指す端末が、そのセッションの制御端末となります。
  4. Go言語のsyscallパッケージ:

    • Go言語のsyscallパッケージは、オペレーティングシステム(OS)の低レベルなシステムコールへのインターフェースを提供します。これにより、Goプログラムから直接OSの機能(ファイル操作、プロセス管理、ネットワーク通信など)を呼び出すことができます。
    • RawSyscallは、syscallパッケージ内で提供される関数の一つで、OSのシステムコールを直接呼び出すための低レベルなメカニズムです。通常、3つの引数(システムコール番号、引数1、引数2)を取り、3つの戻り値(戻り値1、戻り値2、エラーコード)を返します。
  5. SysProcAttr構造体:

    • Go言語のos/execパッケージで外部コマンドを実行する際に、プロセスの属性(例: 環境変数、作業ディレクトリ、権限など)を細かく制御するために使用される構造体です。OS固有の属性を設定するためのフィールドが含まれています。このコミットでは、Linux固有のSysProcAttr構造体が変更されています。

技術的詳細

このコミットの技術的な核心は、Go言語のsyscallパッケージが、新しいプロセスを生成する際に、そのプロセスの制御端末をより柔軟に設定できるようにした点にあります。

変更前は、SysProcAttr構造体のSetcttyフィールドがtrueに設定されている場合、forkAndExecInChild関数内で無条件にファイルディスクリプタ0(標準入力)に対してTIOCSCTTY ioctlシステムコールが実行されていました。これは、多くの場合で問題ありませんでしたが、特定のシナリオ(例: lessが標準入力以外のファイルディスクリプタを制御端末として期待する場合)では不十分でした。

このコミットでは、以下の2つの主要な変更が導入されました。

  1. 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(新しいセッションを作成する)との組み合わせが重要であることが明示されました。これは、制御端末を設定する操作は、通常、セッションリーダーによって行われるためです。
  2. forkAndExecInChild関数におけるTIOCSCTTY呼び出しの変更:

    • forkAndExecInChild関数は、子プロセスをフォークし、指定された実行可能ファイルをその子プロセスで実行するGoランタイムの内部関数です。
    • 変更前は、sys.Setcttytrueの場合、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という条件が追加され、Setcttytrueであり、かつCttyフィールドが有効なファイルディスクリプタ(0以上)である場合にのみ、TIOCSCTTYが実行されるようになりました。
      • RawSyscallの第2引数(TIOCSCTTYを適用するファイルディスクリプタ)が、ハードコードされた0からuintptr(sys.Ctty)に変更されました。これにより、SysProcAttr.Cttyで指定されたファイルディスクリプタが制御端末として設定されるようになります。

これらの変更により、Goプログラムはos/exec.CmdSysProcAttrを設定する際に、Setcttytrueにし、かつ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言語のsyscallパッケージおよびos/execパッケージに関する一般的な知識、Unix/Linuxのプロセス管理(TTY, セッション, プロセスグループ)に関する一般的な知識に基づいて作成されています。