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

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

このコミットは、Go言語のsyscallパッケージにおいて、NetBSDシステムコールに対するnametomib()関数の実装を追加するものです。nametomib()は、人間が読める形式のシステム制御情報(sysctl)の名前(例: "kern.ostype")を、システムが内部的に使用する整数配列(MIB: Management Information Base)に変換する役割を担います。この機能は、NetBSDのCTL_QUERYノード発見メカニズムを利用して実現されています。

コミット

commit 495a9dc2b3ac76004e1324ca38761efad848ad96
Author: Joel Sing <jsing@google.com>
Date:   Wed May 23 01:33:48 2012 +1000

    syscall: implement nametomib() on netbsd
    
    Implement nametomib() on NetBSD using the CTL_QUERY node discovery
    mechanism.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6211071

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

https://github.com/golang/go/commit/495a9dc2b3ac76004e1324ca38761efad848ad96

元コミット内容

syscall: implement nametomib() on netbsd

Implement nametomib() on NetBSD using the CTL_QUERY node discovery
mechanism.

変更の背景

Go言語のsyscallパッケージは、オペレーティングシステムが提供する低レベルのシステムコールへのインターフェースを提供します。これにより、GoプログラムからOS固有の機能にアクセスできるようになります。nametomib()関数は、sysctlインターフェースを通じてシステム情報を取得する際に不可欠な機能です。

NetBSDでは、sysctl変数は階層的な名前空間で管理されており、例えばカーネルのOSタイプはkern.ostypeのように表現されます。しかし、sysctlシステムコール自体は、これらの名前を直接受け取るのではなく、対応する整数値の配列(MIB)を必要とします。他のUnix系OS(例: FreeBSD)では、sysctlbyname()のような関数がこの名前からMIBへの変換をOS側で提供していますが、NetBSDには直接的な同等の関数がありませんでした。

このコミットの背景には、GoのsyscallパッケージがNetBSD上でsysctlをより完全にサポートするために、この名前からMIBへの変換ロジックをGo側で実装する必要があったという経緯があります。特に、NetBSDが提供するCTL_QUERYという特殊なsysctlノードを利用して、名前空間を動的に探索し、対応するMIBを構築するアプローチが採用されました。

前提知識の解説

Sysctl (System Control)

Sysctlは、Unix系オペレーティングシステムにおいて、カーネルの実行時パラメータを照会したり設定したりするためのメカニズムです。これにより、システム管理者は、カーネルの動作を動的に調整したり、システムの状態に関する情報を取得したりできます。

Sysctl変数は通常、ドットで区切られた階層的な名前(例: kern.maxfiles, net.inet.ip.forwarding)で識別されます。これらの名前は、内部的には整数値の配列(MIB: Management Information Base)にマッピングされます。例えば、kern.ostype{1, 1}のようなMIBに変換されるかもしれません。

NetBSDのSysctlとCTL_QUERY

NetBSDのsysctlシステムは、他のBSD系OSと多くの共通点がありますが、特定の機能において独自の実装を持っています。このコミットで重要なのは、NetBSDが提供するCTL_QUERYという特殊なsysctlノードです。

CTL_QUERYは、特定のMIBパスの下にある利用可能なsysctlノードの情報を動的に取得するために使用されます。これは、名前からMIBへの変換を行う際に、名前空間を探索するために利用できる強力なメカニズムです。具体的には、あるMIBパスにCTL_QUERYを追加してsysctlシステムコールを呼び出すと、そのパスの直下にあるノードのメタデータ(名前、番号、型など)のリストが返されます。この情報を使って、名前の各コンポーネントに対応するMIB番号を段階的に解決していくことができます。

MIB (Management Information Base)

MIBは、システムやネットワークデバイスの管理情報を階層的に構造化したデータベースのようなものです。sysctlの文脈では、カーネルパラメータやシステム状態を表す整数値の配列を指します。各整数は、階層内の特定のノード(カテゴリや具体的なパラメータ)を識別します。

unsafe.Pointer_C_int

Go言語のunsafeパッケージは、型安全性をバイパスしてメモリを直接操作するための機能を提供します。unsafe.Pointerは、任意の型のポインタを表現でき、異なる型のポインタ間で変換を行うことができます。これは、C言語の構造体やシステムコールインターフェースとGoのデータ構造をマッピングする際にしばしば使用されます。

_C_intは、GoのsyscallパッケージでC言語のint型に対応するために定義される型です。システムコールは通常、C言語のデータ型を期待するため、Goの型をCの型に正確にマッピングする必要があります。

技術的詳細

このコミットの主要な技術的詳細は、NetBSDのCTL_QUERYメカニズムを利用してnametomib()を実装する点にあります。

  1. sysctlNodes関数の導入:

    • この新しい関数は、与えられたMIBパスの直下にあるsysctlノードのリストを取得します。
    • 内部的には、与えられたmib配列にCTL_QUERYを付加し、そのMIBを使ってsysctlシステムコールを呼び出します。
    • 最初の呼び出しでは、返されるデータのサイズ(olen)を取得し、そのサイズに基づいてSysctlnode構造体のスライスを割り当てます。
    • 2回目の呼び出しで、実際にノードのデータを取得します。
    • Sysctlnode構造体は、各sysctlノードのメタデータ(名前、番号、フラグなど)を保持します。
  2. nametomib関数の実装:

    • 入力されたsysctl名(例: "kern.ostype")をドットで分割し、各コンポーネント("kern", "ostype")を抽出します。
    • 空のMIB配列から開始し、名前の各コンポーネントを順に解決していきます。
    • 各コンポーネントについて、現在のMIBパス(最初は空、その後は解決済みの部分)にCTL_QUERYを付加してsysctlNodesを呼び出し、そのパスの直下にあるノードのリストを取得します。
    • 取得したノードリストをイテレートし、現在のコンポーネント名と一致するノードを探します。
    • 一致するノードが見つかった場合、そのノードの番号(node.Num)をMIB配列に追加します。
    • このプロセスを名前のすべてのコンポーネントに対して繰り返すことで、最終的なMIB配列を構築します。
    • もし途中でコンポーネントが見つからなかった場合、EINVAL(無効な引数)エラーを返します。
  3. mkerrors.shの変更:

    • mkerrors.shスクリプトは、Goのsyscallパッケージで使用される定数や構造体を、Cヘッダーファイルから自動生成するために使われます。
    • このコミットでは、CTL_MAXNAME, CTL_NET, CTL_QUERY, SYSCTL_VERSといった新しい定数がGoのコードに正しく取り込まれるように、スクリプトのパターンマッチングが更新されています。
    • また、schedppqconst intから#defineに変更されています。これは、Cヘッダーファイルでの定義方法に合わせた変更で、Goのバインディング生成に影響します。
  4. types_netbsd.goの変更:

    • sys/sysctl.hヘッダーがインクルードされるようになりました。これにより、sysctl関連のC構造体や定数がGoのコードから利用可能になります。
    • Sysctlnode型がC.struct_sysctlnodeとして定義されました。これは、C言語のstruct sysctlnodeに対応するGoの構造体です。
  5. zerrors_netbsd_*.goztypes_netbsd_*.goの変更:

    • これらはmkerrors.shスクリプトによって自動生成されるファイルです。
    • zerrors_netbsd_386.gozerrors_netbsd_amd64.goには、CTL_QUERYSYSCTL_VERSIONSYSCTL_VERS_0SYSCTL_VERS_1SYSCTL_VERS_MASKといった新しい定数が追加されています。
    • ztypes_netbsd_386.goztypes_netbsd_amd64.goには、Sysctlnode構造体のGoでの定義が追加されています。この構造体は、Flags, Num, Nameなどのフィールドを持ち、sysctlノードのメタデータを表現します。

この実装により、GoプログラムはNetBSD上でsysctlの名前ベースのクエリを効率的に実行できるようになり、より柔軟なシステム情報の取得が可能になります。

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

このコミットのコアとなる変更は、src/pkg/syscall/syscall_netbsd.goにおけるnametomib()関数の実装と、それに付随するsysctlNodes()関数の追加です。

// src/pkg/syscall/syscall_netbsd.go

// sysctlNodes retrieves a list of sysctl nodes below the given MIB.
// It uses the CTL_QUERY mechanism to discover nodes.
func sysctlNodes(mib []_C_int) (nodes []Sysctlnode, err error) {
	var olen uintptr

	// Get a list of all sysctl nodes below the given MIB by performing
	// a sysctl for the given MIB with CTL_QUERY appended.
	mib = append(mib, CTL_QUERY)
	qnode := Sysctlnode{Flags: SYSCTL_VERS_1}
	qp := (*byte)(unsafe.Pointer(&qnode))
	sz := unsafe.Sizeof(qnode)
	if err = sysctl(mib, nil, &olen, qp, sz); err != nil {
		return nil, err
	}

	// Now that we know the size, get the actual nodes.
	nodes = make([]Sysctlnode, olen/sz)
	np := (*byte)(unsafe.Pointer(&nodes[0]))
	if err = sysctl(mib, np, &olen, qp, sz); err != nil {
		return nil, err
	}

	return nodes, nil
}

// nametomib converts a sysctl name (e.g., "kern.ostype") to its MIB array.
func nametomib(name string) (mib []_C_int, err error) {
	// Split name into components.
	var parts []string
	last := 0
	for i := 0; i < len(name); i++ {
		if name[i] == '.' {
			parts = append(parts, name[last:i])
			last = i + 1
		}
	}
	parts = append(parts, name[last:])

	// Discover the nodes and construct the MIB OID.
	for partno, part := range parts {
		nodes, err := sysctlNodes(mib) // Get nodes for the current MIB path
		if err != nil {
			return nil, err
		}
		for _, node := range nodes {
			n := make([]byte, 0)
			for i := range node.Name {
				if node.Name[i] != 0 { // Node names are null-terminated C strings
					n = append(n, byte(node.Name[i]))
				}
			}
			if string(n) == part { // Match component name
				mib = append(mib, _C_int(node.Num)) // Add node number to MIB
				break
			}
		}
		if len(mib) != partno+1 { // If component not found
			return nil, EINVAL
		}
	}

	return mib, nil
}

コアとなるコードの解説

sysctlNodes(mib []_C_int) (nodes []Sysctlnode, err error)

この関数は、与えられたmib(Management Information Base)パスの下にあるsysctlノードのリストを取得します。

  1. mib = append(mib, CTL_QUERY): 既存のmibパスの最後にCTL_QUERYという特別な定数を追加します。NetBSDのsysctlシステムでは、このCTL_QUERYをパスの最後に指定することで、そのパスの直下にあるノードのメタデータ(名前、番号など)を問い合わせることができます。
  2. qnode := Sysctlnode{Flags: SYSCTL_VERS_1}: Sysctlnode構造体のインスタンスを作成し、FlagsフィールドにSYSCTL_VERS_1を設定します。これは、問い合わせるsysctlノードのバージョンを指定するためのものです。
  3. qp := (*byte)(unsafe.Pointer(&qnode)): qnodeのアドレスをunsafe.Pointerを介して*byte型にキャストします。これは、sysctlシステムコールがバイトポインタを期待するためです。
  4. sz := unsafe.Sizeof(qnode): Sysctlnode構造体のサイズを取得します。
  5. if err = sysctl(mib, nil, &olen, qp, sz); err != nil: 最初のsysctl呼び出しを行います。
    • mib: CTL_QUERYが追加されたMIBパス。
    • nil: データを格納するバッファ。ここでは、返されるデータのサイズを知るためにnilを渡します。
    • &olen: 返されるデータのサイズが格納されるポインタ。
    • qp: 問い合わせるノードの情報を渡すためのポインタ(ここではqnode)。
    • sz: qnodeのサイズ。
    • この呼び出しにより、olenに、指定されたMIBパスの下にあるすべてのノードのメタデータを格納するために必要なバイト数が設定されます。
  6. nodes = make([]Sysctlnode, olen/sz): olenszを使って、取得するSysctlnodeの数に合わせたスライスをnodesとして作成します。
  7. np := (*byte)(unsafe.Pointer(&nodes[0])): nodesスライスの最初の要素のアドレスを*byte型にキャストします。
  8. if err = sysctl(mib, np, &olen, qp, sz); err != nil: 2回目のsysctl呼び出しを行います。
    • 今回は、npnodesスライスのバッファを渡し、実際のノードデータを取得します。
    • これにより、nodesスライスに、指定されたMIBパスの直下にあるすべてのsysctlノードのメタデータが格納されます。

nametomib(name string) (mib []_C_int, err error)

この関数は、人間が読めるsysctl名(例: "kern.ostype")を、システムが使用するMIB配列に変換します。

  1. parts := []string: 入力されたnameをドット(.)で分割し、各コンポーネント(例: "kern", "ostype")をpartsスライスに格納します。
  2. for partno, part := range parts: 分割された各コンポーネントについてループします。
  3. nodes, err := sysctlNodes(mib): 現在までに解決されたMIBパス(最初は空、その後は部分的に解決されたMIB)を使ってsysctlNodesを呼び出し、そのパスの直下にあるノードのリストを取得します。
  4. for _, node := range nodes: 取得した各nodeについてループします。
  5. n := make([]byte, 0) ... if string(n) == part: node.NameはC言語のヌル終端文字列として格納されているため、それをGoの文字列に変換し、現在のpart(コンポーネント名)と比較します。
  6. mib = append(mib, _C_int(node.Num)): もし名前が一致するノードが見つかった場合、そのノードの番号(node.Num)を現在のmib配列に追加します。これにより、MIBパスが1段階深くなります。
  7. break: 一致するノードが見つかったら、現在のコンポーネントの探索を終了し、次のコンポーネントに移ります。
  8. if len(mib) != partno+1: 各ループの終わりに、mibの長さが現在のpartno(0から始まるインデックス)に1を加えたものと等しいかを確認します。これは、現在のコンポーネントに対応するノードがmibに追加されたことを意味します。もし長さが一致しない場合(つまり、現在のコンポーネントが見つからなかった場合)、EINVALエラーを返します。
  9. すべてのコンポーネントが正常に解決されると、最終的なmib配列が返されます。

この二つの関数が連携することで、GoプログラムはNetBSDのsysctl名前空間を動的に探索し、指定された名前のsysctl変数に対応するMIBを正確に取得できるようになります。

関連リンク

参考にした情報源リンク

  • NetBSDのsysctl(3)マニュアルページ: CTL_QUERYの動作とsysctlnode構造体に関する詳細な情報を提供しています。
  • Go言語のsyscallパッケージのドキュメントとソースコード: GoがどのようにOSのシステムコールをラップしているか、特に異なるOS間での実装の違いを理解する上で参照しました。
  • BSD系OSのsysctlに関する一般的なドキュメントや記事: nametomibのような機能の必要性や、名前からMIBへの変換の一般的なアプローチを理解するのに役立ちました。

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

このコミットは、Go言語のsyscallパッケージにおいて、NetBSDシステムコールに対するnametomib()関数の実装を追加するものです。nametomib()は、人間が読める形式のシステム制御情報(sysctl)の名前(例: "kern.ostype")を、システムが内部的に使用する整数配列(MIB: Management Information Base)に変換する役割を担います。この機能は、NetBSDのCTL_QUERYノード発見メカニズムを利用して実現されています。

コミット

commit 495a9dc2b3ac76004e1324ca38761efad848ad96
Author: Joel Sing <jsing@google.com>
Date:   Wed May 23 01:33:48 2012 +1000

    syscall: implement nametomib() on netbsd
    
    Implement nametomib() on NetBSD using the CTL_QUERY node discovery
    mechanism.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6211071

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

https://github.com/golang.org/go/commit/495a9dc2b3ac76004e1324ca38761efad848ad96

元コミット内容

syscall: implement nametomib() on netbsd

Implement nametomib() on NetBSD using the CTL_QUERY node discovery
mechanism.

変更の背景

Go言語のsyscallパッケージは、オペレーティングシステムが提供する低レベルのシステムコールへのインターフェースを提供します。これにより、GoプログラムからOS固有の機能にアクセスできるようになります。nametomib()関数は、sysctlインターフェースを通じてシステム情報を取得する際に不可欠な機能です。

NetBSDでは、sysctl変数は階層的な名前空間で管理されており、例えばカーネルのOSタイプはkern.ostypeのように表現されます。しかし、sysctlシステムコール自体は、これらの名前を直接受け取るのではなく、対応する整数値の配列(MIB)を必要とします。他のUnix系OS(例: FreeBSD)では、sysctlbyname()のような関数がこの名前からMIBへの変換をOS側で提供していますが、NetBSDには直接的な同等の関数がありませんでした。

このコミットの背景には、GoのsyscallパッケージがNetBSD上でsysctlをより完全にサポートするために、この名前からMIBへの変換ロジックをGo側で実装する必要があったという経緯があります。特に、NetBSDが提供するCTL_QUERYという特殊なsysctlノードを利用して、名前空間を動的に探索し、対応するMIBを構築するアプローチが採用されました。

前提知識の解説

Sysctl (System Control)

Sysctlは、Unix系オペレーティングシステムにおいて、カーネルの実行時パラメータを照会したり設定したりするためのメカニズムです。これにより、システム管理者は、カーネルの動作を動的に調整したり、システムの状態に関する情報を取得したりできます。

Sysctl変数は通常、ドットで区切られた階層的な名前(例: kern.maxfiles, net.inet.ip.forwarding)で識別されます。これらの名前は、内部的には整数値の配列(MIB: Management Information Base)にマッピングされます。各整数は、階層内の特定のノード(カテゴリや具体的なパラメータ)を識別します。

NetBSDのSysctlとCTL_QUERY

NetBSDのsysctlシステムは、他のBSD系OSと多くの共通点がありますが、特定の機能において独自の実装を持っています。このコミットで重要なのは、NetBSDが提供するCTL_QUERYという特殊なsysctlノードです。

CTL_QUERYは、特定のMIBパスの下にある利用可能なsysctlノードの情報を動的に取得するために使用されます。これは、名前からMIBへの変換を行う際に、名前空間を探索するために利用できる強力なメカニズムです。具体的には、あるMIBパスにCTL_QUERYを追加してsysctlシステムコールを呼び出すと、そのパスの直下にあるノードのメタデータ(名前、番号、型など)のリストが返されます。この情報を使って、名前の各コンポーネントに対応するMIB番号を段階的に解決していくことができます。CTL_QUERYは通常、<sys/sysctl.h>-2として定義されるプリプロセッサマクロです。

MIB (Management Information Base)

MIBは、システムやネットワークデバイスの管理情報を階層的に構造化したデータベースのようなものです。sysctlの文脈では、カーネルパラメータやシステム状態を表す整数値の配列を指します。各整数は、階層内の特定のノード(カテゴリや具体的なパラメータ)を識別します。

unsafe.Pointer_C_int

Go言語のunsafeパッケージは、型安全性をバイパスしてメモリを直接操作するための機能を提供します。unsafe.Pointerは、任意の型のポインタを表現でき、異なる型のポインタ間で変換を行うことができます。これは、C言語の構造体やシステムコールインターフェースとGoのデータ構造をマッピングする際にしばしば使用されます。

_C_intは、GoのsyscallパッケージでC言語のint型に対応するために定義される型です。システムコールは通常、C言語のデータ型を期待するため、Goの型をCの型に正確にマッピングする必要があります。

技術的詳細

このコミットの主要な技術的詳細は、NetBSDのCTL_QUERYメカニズムを利用してnametomib()を実装する点にあります。

  1. sysctlNodes関数の導入:

    • この新しい関数は、与えられたMIBパスの直下にあるsysctlノードのリストを取得します。
    • 内部的には、与えられたmib配列にCTL_QUERYを付加し、そのMIBを使ってsysctlシステムコールを呼び出します。
    • 最初の呼び出しでは、返されるデータのサイズ(olen)を取得し、そのサイズに基づいてSysctlnode構造体のスライスを割り当てます。
    • 2回目の呼び出しで、実際にノードのデータを取得します。
    • Sysctlnode構造体は、各sysctlノードのメタデータ(名前、番号、フラグなど)を保持します。CTL_QUERYや他の動的な操作を使用する際には、プログラムのバージョンを示すためにSYSCTL_VERSIONを明示的に指定することが重要です。
  2. nametomib関数の実装:

    • 入力されたsysctl名(例: "kern.ostype")をドットで分割し、各コンポーネント("kern", "ostype")を抽出します。
    • 空のMIB配列から開始し、名前の各コンポーネントを順に解決していきます。
    • 各コンポーネントについて、現在のMIBパス(最初は空、その後は解決済みの部分)にCTL_QUERYを付加してsysctlNodesを呼び出し、そのパスの直下にあるノードのリストを取得します。
    • 取得したノードリストをイテレートし、現在のコンポーネント名と一致するノードを探します。
    • 一致するノードが見つかった場合、そのノードの番号(node.Num)をMIB配列に追加します。
    • このプロセスを名前のすべてのコンポーネントに対して繰り返すことで、最終的なMIB配列を構築します。
    • もし途中でコンポーネントが見つからなかった場合、EINVAL(無効な引数)エラーを返します。
  3. mkerrors.shの変更:

    • mkerrors.shスクリプトは、Goのsyscallパッケージで使用される定数や構造体を、Cヘッダーファイルから自動生成するために使われます。
    • このコミットでは、CTL_MAXNAME, CTL_NET, CTL_QUERY, SYSCTL_VERSといった新しい定数がGoのコードに正しく取り込まれるように、スクリプトのパターンマッチングが更新されています。
    • また、schedppqconst intから#defineに変更されています。これは、Cヘッダーファイルでの定義方法に合わせた変更で、Goのバインディング生成に影響します。
  4. types_netbsd.goの変更:

    • sys/sysctl.hヘッダーがインクルードされるようになりました。これにより、sysctl関連のC構造体や定数がGoのコードから利用可能になります。
    • Sysctlnode型がC.struct_sysctlnodeとして定義されました。これは、C言語のstruct sysctlnodeに対応するGoの構造体です。
  5. zerrors_netbsd_*.goztypes_netbsd_*.goの変更:

    • これらはmkerrors.shスクリプトによって自動生成されるファイルです。
    • zerrors_netbsd_386.gozerrors_netbsd_amd64.goには、CTL_QUERYSYSCTL_VERSIONSYSCTL_VERS_0SYSCTL_VERS_1SYSCTL_VERS_MASKといった新しい定数が追加されています。
    • ztypes_netbsd_386.goztypes_netbsd_amd64.goには、Sysctlnode構造体のGoでの定義が追加されています。この構造体は、Flags, Num, Nameなどのフィールドを持ち、sysctlノードのメタデータを表現します。

この実装により、GoプログラムはNetBSD上でsysctlの名前ベースのクエリを効率的に実行できるようになり、より柔軟なシステム情報の取得が可能になります。

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

このコミットのコアとなる変更は、src/pkg/syscall/syscall_netbsd.goにおけるnametomib()関数の実装と、それに付随するsysctlNodes()関数の追加です。

// src/pkg/syscall/syscall_netbsd.go

// sysctlNodes retrieves a list of sysctl nodes below the given MIB.
// It uses the CTL_QUERY mechanism to discover nodes.
func sysctlNodes(mib []_C_int) (nodes []Sysctlnode, err error) {
	var olen uintptr

	// Get a list of all sysctl nodes below the given MIB by performing
	// a sysctl for the given MIB with CTL_QUERY appended.
	mib = append(mib, CTL_QUERY)
	qnode := Sysctlnode{Flags: SYSCTL_VERS_1}
	qp := (*byte)(unsafe.Pointer(&qnode))
	sz := unsafe.Sizeof(qnode)
	if err = sysctl(mib, nil, &olen, qp, sz); err != nil {
		return nil, err
	}

	// Now that we know the size, get the actual nodes.
	nodes = make([]Sysctlnode, olen/sz)
	np := (*byte)(unsafe.Pointer(&nodes[0]))
	if err = sysctl(mib, np, &olen, qp, sz); err != nil {
		return nil, err
	}

	return nodes, nil
}

// nametomib converts a sysctl name (e.g., "kern.ostype") to its MIB array.
func nametomib(name string) (mib []_C_int, err error) {
	// Split name into components.
	var parts []string
	last := 0
	for i := 0; i < len(name); i++ {
		if name[i] == '.' {
			parts = append(parts, name[last:i])
			last = i + 1
		}
	}
	parts = append(parts, name[last:])

	// Discover the nodes and construct the MIB OID.
	for partno, part := range parts {
		nodes, err := sysctlNodes(mib) // Get nodes for the current MIB path
		if err != nil {
			return nil, err
		}
		for _, node := range nodes {
			n := make([]byte, 0)
			for i := range node.Name {
				if node.Name[i] != 0 { // Node names are null-terminated C strings
					n = append(n, byte(node.Name[i]))
				}
			}
			if string(n) == part { // Match component name
				mib = append(mib, _C_int(node.Num)) // Add node number to MIB
				break
			}
		}
		if len(mib) != partno+1 { // If component not found
			return nil, EINVAL
		}
	}

	return mib, nil
}

コアとなるコードの解説

sysctlNodes(mib []_C_int) (nodes []Sysctlnode, err error)

この関数は、与えられたmib(Management Information Base)パスの下にあるsysctlノードのリストを取得します。

  1. mib = append(mib, CTL_QUERY): 既存のmibパスの最後にCTL_QUERYという特別な定数を追加します。NetBSDのsysctlシステムでは、このCTL_QUERYをパスの最後に指定することで、そのパスの直下にあるノードのメタデータ(名前、番号など)を問い合わせることができます。
  2. qnode := Sysctlnode{Flags: SYSCTL_VERS_1}: Sysctlnode構造体のインスタンスを作成し、FlagsフィールドにSYSCTL_VERS_1を設定します。これは、問い合わせるsysctlノードのバージョンを指定するためのものです。
  3. qp := (*byte)(unsafe.Pointer(&qnode)): qnodeのアドレスをunsafe.Pointerを介して*byte型にキャストします。これは、sysctlシステムコールがバイトポインタを期待するためです。
  4. sz := unsafe.Sizeof(qnode): Sysctlnode構造体のサイズを取得します。
  5. if err = sysctl(mib, nil, &olen, qp, sz); err != nil: 最初のsysctl呼び出しを行います。
    • mib: CTL_QUERYが追加されたMIBパス。
    • nil: データを格納するバッファ。ここでは、返されるデータのサイズを知るためにnilを渡します。
    • &olen: 返されるデータのサイズが格納されるポインタ。
    • qp: 問い合わせるノードの情報を渡すためのポインタ(ここではqnode)。
    • sz: qnodeのサイズ。
    • この呼び出しにより、olenに、指定されたMIBパスの下にあるすべてのノードのメタデータを格納するために必要なバイト数が設定されます。
  6. nodes = make([]Sysctlnode, olen/sz): olenszを使って、取得するSysctlnodeの数に合わせたスライスをnodesとして作成します。
  7. np := (*byte)(unsafe.Pointer(&nodes[0])): nodesスライスの最初の要素のアドレスを*byte型にキャストします。
  8. if err = sysctl(mib, np, &olen, qp, sz); err != nil: 2回目のsysctl呼び出しを行います。
    • 今回は、npnodesスライスのバッファを渡し、実際のノードデータを取得します。
    • これにより、nodesスライスに、指定されたMIBパスの直下にあるすべてのsysctlノードのメタデータが格納されます。

nametomib(name string) (mib []_C_int, err error)

この関数は、人間が読めるsysctl名(例: "kern.ostype")を、システムが使用するMIB配列に変換します。

  1. parts := []string: 入力されたnameをドット(.)で分割し、各コンポーネント(例: "kern", "ostype")をpartsスライスに格納します。
  2. for partno, part := range parts: 分割された各コンポーネントについてループします。
  3. nodes, err := sysctlNodes(mib): 現在までに解決されたMIBパス(最初は空、その後は部分的に解決されたMIB)を使ってsysctlNodesを呼び出し、そのパスの直下にあるノードのリストを取得します。
  4. for _, node := range nodes: 取得した各nodeについてループします。
  5. n := make([]byte, 0) ... if string(n) == part: node.NameはC言語のヌル終端文字列として格納されているため、それをGoの文字列に変換し、現在のpart(コンポーネント名)と比較します。
  6. mib = append(mib, _C_int(node.Num)): もし名前が一致するノードが見つかった場合、そのノードの番号(node.Num)を現在のmib配列に追加します。これにより、MIBパスが1段階深くなります。
  7. break: 一致するノードが見つかったら、現在のコンポーネントの探索を終了し、次のコンポーネントに移ります。
  8. if len(mib) != partno+1: 各ループの終わりに、mibの長さが現在のpartno(0から始まるインデックス)に1を加えたものと等しいかを確認します。これは、現在のコンポーネントに対応するノードがmibに追加されたことを意味します。もし長さが一致しない場合(つまり、現在のコンポーネントが見つからなかった場合)、EINVALエラーを返します。
  9. すべてのコンポーネントが正常に解決されると、最終的なmib配列が返されます。

この二つの関数が連携することで、GoプログラムはNetBSDのsysctl名前空間を動的に探索し、指定された名前のsysctl変数に対応するMIBを正確に取得できるようになります。

関連リンク

参考にした情報源リンク

  • NetBSDのsysctl(3)マニュアルページ: CTL_QUERYの動作とsysctlnode構造体に関する詳細な情報を提供しています。
  • Go言語のsyscallパッケージのドキュメントとソースコード: GoがどのようにOSのシステムコールをラップしているか、特に異なるOS間での実装の違いを理解する上で参照しました。
  • BSD系OSのsysctlに関する一般的なドキュメントや記事: nametomibのような機能の必要性や、名前からMIBへの変換の一般的なアプローチを理解するのに役立ちました。