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

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

このコミットは、Go言語の os/user パッケージにおけるWindows環境でのユーザー情報ルックアップに関するバグ修正です。具体的には、ドメインユーザーがドメインから切断された状態にある場合に、ユーザーのフルネームを取得する際に発生するエラーをハンドリングし、その際にユーザー名をフルネームとして代替使用するように変更しています。これにより、特定の条件下でユーザー情報取得が失敗する問題(Issue 4113)が解決されます。

コミット

commit 5f7f9062dbebd68978b449e241e66873320186ca
Author: Shivakumar GN <shivakumar.gn@gmail.com>
Date:   Wed Oct 3 10:33:09 2012 +1000

    os/user : use username as fullname if all else fails (on windows)
    
    Fixes #4113.
    
    R=golang-dev, alex.brainman
    CC=golang-dev
    https://golang.org/cl/6545054

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

https://github.com/golang/go/commit/5f7f9062dbebd68978b449e241e66873320186ca

元コミット内容

commit 5f7f9062dbebd68978b449e241e66873320186ca
Author: Shivakumar GN <shivakumar.gn@gmail.com>
Date:   Wed Oct 3 10:33:09 2012 +1000

    os/user : use username as fullname if all else fails (on windows)
    
    Fixes #4113.
    
    R=golang-dev, alex.brainman
    CC=golang-dev
    https://golang.org/cl/6545054
---
 src/pkg/os/user/lookup_windows.go | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n
diff --git a/src/pkg/os/user/lookup_windows.go b/src/pkg/os/user/lookup_windows.go
index 90fe0662e6..3626a4e9f0 100644
--- a/src/pkg/os/user/lookup_windows.go
+++ b/src/pkg/os/user/lookup_windows.go
@@ -27,7 +27,9 @@ func lookupFullName(domain, username, domainAndUser string) (string, error) {
 		var p *byte
 		e = syscall.NetUserGetInfo(d, u, 10, &p)
 		if e != nil {
-			return "", e
+			// path executed when a domain user is disconnected from the domain
+			// pretend username is fullname
+			return username, nil
 		}
 		defer syscall.NetApiBufferFree(p)
 		i := (*syscall.UserInfo10)(unsafe.Pointer(p))\n

変更の背景

このコミットは、Go言語の os/user パッケージがWindows環境でユーザーのフルネームを取得する際に発生する特定のバグを修正するために行われました。このバグは、Go issue trackerの Issue 4113 で報告されていました。

報告された問題は、Windows環境において、ドメインユーザーがドメインコントローラーから切断された状態(例えば、ノートPCが社内ネットワークから持ち出された状態)で os/user パッケージの Lookup 関数(または内部的に呼び出される関連関数)を使用すると、ユーザーのフルネームを取得する syscall.NetUserGetInfo 関数がエラーを返すというものでした。このエラーが発生すると、os/user パッケージはユーザー情報を適切に取得できず、アプリケーションが予期せぬ動作をしたり、クラッシュしたりする可能性がありました。

このコミットの目的は、このような特定のシナリオで NetUserGetInfo がエラーを返した場合に、エラーをそのまま返すのではなく、ユーザー名をフルネームとして代替的に使用することで、より堅牢なユーザー情報取得メカニズムを提供することです。これにより、ドメインから切断された状態のドメインユーザーに対しても、少なくともユーザー名という形で情報を提供できるようになり、アプリケーションの安定性が向上します。

前提知識の解説

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

  • Go言語の os/user パッケージ: os/user パッケージは、現在のシステムユーザーに関する情報を取得するためのGo言語の標準ライブラリです。ユーザー名、UID/GID、ホームディレクトリ、フルネームなどの情報を提供します。OSに依存する実装を持っており、Windows、Linux、macOSなど、各OSのAPIを内部的に呼び出して情報を取得します。

  • Windows API NetUserGetInfo: NetUserGetInfo は、WindowsのNet Services APIの一部であり、特定のユーザーアカウントに関する情報を取得するために使用されます。この関数は、ユーザー名、パスワード、権限、フルネームなど、さまざまなレベル(情報レベル)のユーザー情報を取得できます。このコミットでは、情報レベル 10 を使用しており、これは USER_INFO_10 構造体に対応し、ユーザーのフルネーム(usri10_full_name)などの基本情報を含みます。

  • Windows API NetApiBufferFree: NetApiBufferFree は、Net Services APIによって割り当てられたメモリを解放するために使用される関数です。NetUserGetInfo のような関数は、情報を格納するために内部的にメモリを割り当て、そのポインタを呼び出し元に返します。このメモリは、使用後に NetApiBufferFree を呼び出して明示的に解放する必要があります。これはメモリリークを防ぐために重要です。

  • syscall パッケージ: Go言語の syscall パッケージは、低レベルのオペレーティングシステムプリミティブへのアクセスを提供します。これには、システムコール、プロセス管理、ファイルシステム操作、ネットワーク操作などが含まれます。Windows固有のAPI(NetUserGetInfo など)をGoから呼び出す際には、この syscall パッケージを介して行われます。

  • unsafe.Pointer: Go言語の unsafe パッケージは、Goの型安全性をバイパスする操作を可能にします。unsafe.Pointer は、任意の型のポインタを保持できる特殊なポインタ型です。C言語の void* に似ています。このコミットでは、syscall.NetUserGetInfo から返された生バイトポインタを syscall.UserInfo10 構造体へのポインタに変換するために使用されています。これは、Goの型システムでは直接許可されていない操作であり、低レベルのシステムプログラミングで必要となる場合があります。

  • ドメインユーザーとドメインからの切断: WindowsのActive Directoryドメイン環境では、ユーザーアカウントはドメインコントローラーによって管理されます。ドメインユーザーは、ドメイン内のどのコンピューターからでもログインできます。しかし、ノートPCなどがドメインネットワークから切断された状態(例えば、自宅や外出先)にある場合、ドメインコントローラーとの通信ができないため、一部のドメイン関連の操作(ユーザー情報の詳細な取得など)が失敗することがあります。このコミットは、まさにこの「ドメインから切断された状態のドメインユーザー」が原因で発生する問題に対処しています。

技術的詳細

このコミットの技術的な核心は、Windows API NetUserGetInfo のエラーハンドリングの改善にあります。

src/pkg/os/user/lookup_windows.go ファイルの lookupFullName 関数は、指定されたユーザーのフルネームを取得する役割を担っています。この関数は内部で syscall.NetUserGetInfo を呼び出しています。

元のコードでは、syscall.NetUserGetInfo の呼び出しがエラー e を返した場合、そのエラーをそのまま呼び出し元に返していました。

		e = syscall.NetUserGetInfo(d, u, 10, &p)
		if e != nil {
			return "", e // エラーをそのまま返す
		}

しかし、Issue 4113で報告されたように、ドメインユーザーがドメインから切断された状態にある場合、NetUserGetInfo はエラーを返します。このエラーは、ユーザーが存在しないことを意味するものではなく、単にドメインコントローラーにアクセスできないために詳細な情報を取得できないことを意味します。このような状況でエラーを返してしまうと、os/user パッケージの利用者はユーザー情報を取得できなくなってしまいます。

このコミットでは、この特定のエラーケースを考慮し、エラーが発生した場合のフォールバックメカニズムを導入しています。

		e = syscall.NetUserGetInfo(d, u, 10, &p)
		if e != nil {
			// path executed when a domain user is disconnected from the domain
			// pretend username is fullname
			return username, nil // エラーが発生しても、ユーザー名をフルネームとして返し、エラーは返さない
		}

変更後のコードでは、NetUserGetInfo がエラーを返した場合でも、空文字列とエラーを返す代わりに、引数として渡された username をフルネームとして返し、エラーとしては nil(エラーなし)を返します。

この変更の意図は、以下のコメントに明確に示されています。 // path executed when a domain user is disconnected from the domain // pretend username is fullname

これは、「ドメインユーザーがドメインから切断された場合に実行されるパスであり、ユーザー名をフルネームとして扱う」という設計判断を示しています。これにより、完全なフルネームが取得できない場合でも、少なくともユーザー名という形で有効な情報を返すことで、os/user パッケージの堅牢性と使いやすさを向上させています。

この変更は、Windowsのユーザー管理の複雑さと、ネットワーク接続の状態がAPIの挙動に与える影響を考慮した、実用的な解決策と言えます。

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

--- a/src/pkg/os/user/lookup_windows.go
+++ b/src/pkg/os/user/lookup_windows.go
@@ -27,7 +27,9 @@ func lookupFullName(domain, username, domainAndUser string) (string, error) {
 		var p *byte
 		e = syscall.NetUserGetInfo(d, u, 10, &p)
 		if e != nil {
-			return "", e
+			// path executed when a domain user is disconnected from the domain
+			// pretend username is fullname
+			return username, nil
 		}
 		defer syscall.NetApiBufferFree(p)
 		i := (*syscall.UserInfo10)(unsafe.Pointer(p))\n

コアとなるコードの解説

変更は src/pkg/os/user/lookup_windows.go ファイル内の lookupFullName 関数に集中しています。

この関数は、Windows環境でユーザーのフルネームを取得するために使用されます。 引数:

  • domain: ユーザーが属するドメイン名。
  • username: ユーザー名。
  • domainAndUser: ドメインとユーザー名を組み合わせた文字列(例: DOMAIN\username)。

関数内部では、以下の処理が行われます。

  1. syscall.NetUserGetInfo(d, u, 10, &p) を呼び出して、ユーザー情報を取得します。

    • d: ドメイン名(domain)。
    • u: ユーザー名(username)。
    • 10: 取得する情報レベル。これは USER_INFO_10 構造体に対応し、ユーザーのフルネームなどの基本情報を含みます。
    • &p: 取得したユーザー情報が格納されるメモリへのポインタ。
  2. NetUserGetInfo の呼び出し結果として返されるエラー e をチェックします。

    • 変更前:

      if e != nil {
          return "", e
      }
      

      NetUserGetInfo がエラーを返した場合 (e != nil)、関数は空文字列と取得したエラー e をそのまま返していました。これは、ユーザー情報の取得に失敗したことを意味します。

    • 変更後:

      if e != nil {
          // path executed when a domain user is disconnected from the domain
          // pretend username is fullname
          return username, nil
      }
      

      NetUserGetInfo がエラーを返した場合でも、関数はエラーを返さず、代わりに username 引数の値をフルネームとして返し、エラーとしては nil を返します。 この変更により、ドメインユーザーがドメインから切断されているなどの理由で NetUserGetInfo が失敗した場合でも、os/user パッケージは少なくともユーザー名を提供できるようになり、アプリケーションがクラッシュしたり、不完全な情報を返したりするのを防ぎます。

  3. defer syscall.NetApiBufferFree(p): NetUserGetInfo によって割り当てられたメモリ p を、関数の終了時に必ず解放するように defer ステートメントで NetApiBufferFree を呼び出しています。これはメモリリークを防ぐための重要な処理であり、変更前後で変わりません。

  4. i := (*syscall.UserInfo10)(unsafe.Pointer(p)): 取得した生バイトポインタ psyscall.UserInfo10 構造体へのポインタに型変換しています。

  5. return syscall.UTF16ToString(i.Usri10_full_name), nil: UserInfo10 構造体から Usri10_full_name フィールド(UTF-16エンコードされたフルネーム)を取得し、syscall.UTF16ToString を使用してGoの文字列に変換して返します。これはエラーが発生しなかった場合の通常の成功パスです。

このコミットの主要な変更点は、NetUserGetInfo がエラーを返した場合の挙動を、エラーをそのまま返すのではなく、ユーザー名を代替のフルネームとして返すように変更した点にあります。これにより、特定の条件下でのユーザー情報取得の失敗を許容し、より堅牢な動作を実現しています。

関連リンク

参考にした情報源リンク