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

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

このコミットは、Go言語の標準ライブラリ os/user パッケージにWindowsプラットフォーム向けのユーザー情報ルックアップ機能を追加するものです。これにより、GoアプリケーションがWindows環境で現在のユーザー情報や、指定したユーザー名・ユーザーID(SID)に対応するユーザー情報を取得できるようになります。

コミット

  • コミットハッシュ: dcbc77d2cfb37985f1ccc712a7626945a6b1f5b2
  • Author: Alex Brainman alex.brainman@gmail.com
  • Date: Mon Jan 30 22:59:10 2012 +1100

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

https://github.com/golang/go/commit/dcbc77d2cfb37985f1ccc712a7626945a6b1f5b2

元コミット内容

    os/user: windows implementation
    
    pjmlp gets credit for initial version.
    
    Fixes #1789.
    
    R=paulo.jpinto, bradfitz, remyoudompheng, rsc
    CC=golang-dev
    https://golang.org/cl/5543069

変更の背景

Go言語の os/user パッケージは、ユーザーアカウント情報を取得するためのクロスプラットフォームなインターフェースを提供することを目的としています。このコミット以前は、os/user パッケージは主にUnix系システム(Linux, FreeBSD, Darwinなど)での実装が中心であり、Windows環境では Current(), Lookup(), LookupId() といった主要な関数が「未実装」のエラーを返していました。

この状況は、Go言語で開発されたアプリケーションがWindows上でユーザー情報を必要とする場合に大きな障壁となっていました。特に、ユーザーのホームディレクトリの取得や、特定のユーザーの存在確認といった基本的な操作ができないことは、実用的なアプリケーション開発において不便でした。

コミットメッセージにある Fixes #1789 は、GoのIssueトラッカーにおける「Issue 1789: os/user: implement for windows」を指しています。このIssueは、Windows環境での os/user パッケージの実装が求められていることを示しており、このコミットはその要望に応える形で、Windows固有のAPIを利用してユーザー情報取得機能を提供することを目的としています。これにより、GoアプリケーションのWindows環境での互換性と機能性が向上しました。

前提知識の解説

Go言語の os/user パッケージ

os/user パッケージは、現在のシステムユーザーに関する情報を取得するためのGo言語の標準ライブラリです。主な機能として、以下のものがあります。

  • Current(): 現在実行中のプロセスのユーザー情報を取得します。
  • Lookup(username string): 指定されたユーザー名に対応するユーザー情報を取得します。
  • LookupId(uid string): 指定されたユーザーIDに対応するユーザー情報を取得します。

これらの関数は、User という構造体を返します。この構造体には、ユーザーID (Uid)、プライマリグループID (Gid)、ユーザー名 (Username)、フルネーム (Name)、ホームディレクトリ (HomeDir) などの情報が含まれます。

Windowsにおけるユーザー管理の概念

Unix系システムではユーザーID (UID) やグループID (GID) が整数値で管理されるのに対し、Windowsではユーザーやグループはセキュリティ識別子 (Security Identifier, SID) と呼ばれる可変長の構造体で一意に識別されます。SIDは S-1-5-21-3623811015-3361044348-30300820-1001 のような形式の文字列で表現されます。

また、Windowsではユーザーの認証情報や権限はアクセストークン (Access Token) に格納されます。プロセスが実行される際には、そのプロセスに関連付けられたアクセストークンが使用され、これによりプロセスがアクセスできるリソースや実行できる操作が決定されます。アクセストークンには、ユーザーのSID、所属するグループのSID、特権などが含まれます。

ユーザープロファイルは、ユーザー固有のドキュメント、設定、アプリケーションデータなどを格納するディレクトリです。Windowsでは、ユーザーのホームディレクトリに相当する概念としてユーザープロファイルディレクトリが存在します。

Go言語の syscall パッケージとWindows API

Go言語は、低レベルなシステムコールやOS固有のAPIにアクセスするために syscall パッケージを提供しています。Windowsの場合、このパッケージを通じてWin32 APIを呼び出すことができます。

Win32 APIは、Windowsオペレーティングシステムのコア機能を提供するC言語ベースの関数群です。ユーザー情報の取得には、以下のようなAPIが利用されます。

  • OpenProcessToken: プロセスに関連付けられたアクセストークンを開きます。
  • GetTokenInformation: アクセストークンからユーザーやグループのSIDなどの情報を取得します。
  • LookupAccountSid: SIDからアカウント名とドメイン名を取得します。
  • LookupAccountName: アカウント名からSIDを取得します。
  • ConvertSidToStringSid / ConvertStringSidToSid: SIDと文字列形式のSIDを相互変換します。
  • GetUserProfileDirectory: アクセストークンからユーザープロファイルディレクトリのパスを取得します。
  • TranslateName: アカウント名を異なる形式(例: SAM互換名から表示名)に変換します。
  • NetUserGetInfo: ネットワークユーザーアカウントに関する情報を取得します。

Goの syscall パッケージでは、これらのWin32 APIをGoの関数としてラップし、Goのデータ型とCのデータ型間の変換を処理します。mksyscall_windows.pl のようなスクリプトは、GoのソースコードからWin32 APIのラッパー関数を自動生成するために使用されます。

技術的詳細

このコミットの主要な技術的変更点は、Windows固有のユーザー情報取得ロジックを src/pkg/os/user/lookup_windows.go に実装し、それを os/user パッケージのクロスプラットフォームなインターフェースに統合したことです。

  1. User 構造体の変更:

    • src/pkg/os/user/user.go において、User 構造体の UidGid フィールドの型が int から string に変更されました。
    • これは、Unix系システムではUID/GIDが整数であるのに対し、WindowsではSIDが文字列形式であるため、両プラットフォームに対応するために string 型に統一されたためです。コメントにも「On posix systems Uid and Gid contain a decimal number representing uid and gid. On windows Uid and Gid contain security identifier (SID) in a string format.」と明記されています。
  2. Windows固有のルックアップ実装 (lookup_windows.go):

    • Current():
      • syscall.OpenCurrentProcessToken() を呼び出して現在のプロセスのアクセストークンを取得します。
      • 取得したトークンから GetTokenUser() でユーザーのSID (syscall.SID) を、GetTokenPrimaryGroup() でプライマリグループのSIDを取得します。
      • GetUserProfileDirectory() でユーザーのホームディレクトリ(プロファイルディレクトリ)を取得します。
      • これらの情報(ユーザーSID、グループSID、ホームディレクトリ)を基に newUser ヘルパー関数を呼び出し、User 構造体を構築します。
    • Lookup(username string):
      • syscall.LookupSID("", username) を使用して、指定されたユーザー名に対応するSIDを取得します。
      • 取得したSIDを基に newUserFromSid ヘルパー関数を呼び出し、User 構造体を構築します。
    • LookupId(uid string):
      • syscall.StringToSid(uid) を使用して、文字列形式のSIDを syscall.SID オブジェクトに変換します。
      • 変換したSIDを基に newUserFromSid ヘルパー関数を呼び出し、User 構造体を構築します。
    • lookupFullName ヘルパー関数:
      • ユーザーのフルネームを取得するために syscall.TranslateAccountNamesyscall.NetUserGetInfo を試行します。これは、ドメイン環境とローカル環境の両方に対応するためです。
  3. WindowsセキュリティAPIの追加 (security_windows.go):

    • src/pkg/syscall/security_windows.go が新規追加され、Windowsのセキュリティ関連API(TranslateName, NetUserGetInfo, LookupAccountSid, LookupAccountName, ConvertSidToStringSid, ConvertStringSidToSid, OpenProcessToken, GetTokenInformation, GetUserProfileDirectory など)のGoラッパーが定義されました。
    • これらの関数は、syscall.Syscall を用いて実際のWin32 APIを呼び出します。エラーハンドリングやバッファサイズの動的な調整(ERROR_INSUFFICIENT_BUFFER の場合のリトライ)も実装されています。
    • SID 型や Token 型など、Windowsセキュリティオブジェクトに対応するGoの型も定義されています。
  4. ビルドスクリプトの更新:

    • src/buildscript/windows_386.sh および src/buildscript/windows_amd64.sh が更新され、syscall パッケージのコンパイル時に security_windows.go が含まれるようになりました。
    • src/pkg/syscall/Makefilesrc/pkg/syscall/mkall.sh も同様に更新され、security_windows.go がビルドプロセスに組み込まれるように変更されています。
    • src/pkg/os/user/Makefile も更新され、Windowsビルド時に lookup_windows.go が含まれ、CGOが有効でないUnix系システムでは lookup_stubs.go が含まれるように条件付きコンパイルが設定されました。
  5. テストの更新:

    • src/pkg/os/user/user_test.go が更新され、Windows環境でも Current(), Lookup(), LookupId() が正しく動作するかを検証するテストが追加されました。
    • 特に、UidGid が文字列型になったことに対応し、テストでの比較ロジックも変更されています。Windowsでは GidHomeDir の取得が困難な場合があるため、テストでこれらのフィールドの比較をスキップするロジックも含まれています(BUG(brainman) コメントで示唆されているように、当時の実装では GidHomeDir が完全に取得できないケースがあったため)。

このコミットにより、Go言語はWindows環境でのユーザー情報取得において、よりネイティブな機能を提供できるようになり、クロスプラットフォーム開発の利便性が向上しました。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  • src/pkg/os/user/lookup_stubs.go: Windows以外のCGO無効環境向けのスタブ実装。!cgo,windows ビルドタグにより、CGOが無効かつWindowsではない環境でコンパイルされる。Current, Lookup, LookupId が未実装エラーを返すように変更。
  • src/pkg/os/user/lookup_unix.go: Unix系システム向けのユーザー情報ルックアップ実装。Current() 関数が追加され、LookupId の引数が int から string に変更され、内部で strconv.Atoi を使用して変換するようになった。User 構造体の UidGid フィールドへの値設定が strconv.Itoa を使って文字列に変換されるようになった。
  • src/pkg/os/user/lookup_windows.go: 新規追加ファイル。Windows環境向けのユーザー情報ルックアップ実装。Current(), Lookup(), LookupId() 関数がWindows API (syscall パッケージ経由) を利用して実装されている。
  • src/pkg/os/user/user.go: User 構造体の UidGid フィールドの型が int から string に変更された。implemented 変数の初期値が false から true に変更され、lookup_stubs.goinit 関数で false に上書きされるようになった。
  • src/pkg/os/user/user_test.go: os/user パッケージのテストファイル。Windows環境でのテストを有効化し、Current(), Lookup(), LookupId() のテストロジックが追加・修正された。User 構造体の UidGid が文字列になったことに対応し、比較ロジックも変更された。
  • src/pkg/syscall/Makefile: GOFILES_windowssecurity_windows.go が追加された。
  • src/pkg/syscall/mkall.sh: syscall_goos 変数に security_windows.go が追加され、mksyscall_windows.plsecurity_windows.go を処理するように変更された。
  • src/pkg/syscall/security_windows.go: 新規追加ファイル。Windowsのセキュリティ関連APIのGoラッパー関数と関連する定数、構造体が定義されている。
  • src/pkg/syscall/zsyscall_windows_386.go, src/pkg/syscall/zsyscall_windows_amd64.go: mksyscall_windows.pl によって自動生成されるファイル。security_windows.go で定義されたAPIに対応するGoの関数呼び出しが追加され、関連するDLL (secur32.dll, netapi32.dll, userenv.dll) のプロシージャが登録された。
  • src/pkg/syscall/ztypes_windows.go: Windows固有の型定義ファイル。STANDARD_RIGHTS_READ の重複定義が削除され、FILE_NOTIFY_CHANGE_*HKEY_* などの定数に // do not reorder コメントが追加された。

コアとなるコードの解説

src/pkg/os/user/lookup_windows.go (新規追加)

このファイルは、Windows環境でユーザー情報を取得するためのGoコードの核心部分です。

// Current returns the current user.
func Current() (*User, error) {
	t, e := syscall.OpenCurrentProcessToken() // 現在のプロセスのアクセストークンを開く
	if e != nil {
		return nil, e
	}
	defer t.Close() // 関数終了時にトークンを閉じる

	u, e := t.GetTokenUser() // トークンからユーザーのSIDを取得
	if e != nil {
		return nil, e
	}
	pg, e := t.GetTokenPrimaryGroup() // トークンからプライマリグループのSIDを取得
	if e != nil {
		return nil, e
	}
	gid, e := pg.PrimaryGroup.String() // グループSIDを文字列に変換
	if e != nil {
		return nil, e
	}
	dir, e := t.GetUserProfileDirectory() // ユーザープロファイルディレクトリを取得
	if e != nil {
		return nil, e
	}
	return newUser(u.User.Sid, gid, dir) // 取得した情報でUser構造体を生成
}

// Lookup looks up a user by username.
func Lookup(username string) (*User, error) {
	// ユーザー名からSIDを取得
	sid, _, t, e := syscall.LookupSID("", username)
	if e != nil {
		return nil, e
	}
	if t != syscall.SidTypeUser { // SIDがユーザータイプであることを確認
		return nil, fmt.Errorf("user: should be user account type, not %d", t)
	}
	return newUserFromSid(sid) // SIDからUser構造体を生成
}

// LookupId looks up a user by userid.
func LookupId(uid string) (*User, error) {
	// 文字列形式のSIDをsyscall.SIDオブジェクトに変換
	sid, e := syscall.StringToSid(uid)
	if e != nil {
		return nil, e
	}
	return newUserFromSid(sid) // SIDからUser構造体を生成
}

これらの関数は、Windowsの低レベルAPIを syscall パッケージ経由で呼び出し、ユーザーのSID、グループSID、ホームディレクトリなどの情報を取得し、それを os/user.User 構造体にマッピングしています。特に Current() は、現在実行中のプロセスのセキュリティコンテキストを利用して情報を取得する典型的なパターンを示しています。

src/pkg/syscall/security_windows.go (新規追加)

このファイルは、Windowsのセキュリティ関連APIをGoから呼び出すためのラッパー関数群を提供します。

// TranslateAccountName converts a directory service
// object name from one format to another.
func TranslateAccountName(username string, from, to uint32, initSize int) (string, error) {
	// secur32.dll の TranslateNameW API を呼び出す
	// バッファが足りない場合はリトライするロジックを含む
}

// UserInfo10 struct represents information level 10 for NetUserGetInfo.
type UserInfo10 struct {
	Name       *uint16
	Comment    *uint16
	UsrComment *uint16
	FullName   *uint16
}

//sys	NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) = netapi32.NetUserGetInfo
//sys	NetApiBufferFree(buf *byte) (neterr error) = netapi32.NetApiBufferFree

// SID struct represents a Security Identifier.
type SID struct{}

// StringToSid converts a string-format security identifier
// sid into a valid, functional sid.
func StringToSid(s string) (*SID, error) {
	// advapi32.dll の ConvertStringSidToSidW API を呼び出す
}

// LookupSID retrieves a security identifier sid for the account
// and the name of the domain on which the account was found.
func LookupSID(system, account string) (sid *SID, domain string, accType uint32, err error) {
	// advapi32.dll の LookupAccountNameW API を呼び出す
	// バッファが足りない場合はリトライするロジックを含む
}

// Token struct represents an access token.
type Token Handle

// OpenCurrentProcessToken opens the access token
// associated with current process.
func OpenCurrentProcessToken() (Token, error) {
	// kernel32.dll の GetCurrentProcess と advapi32.dll の OpenProcessToken API を呼び出す
}

// GetTokenUser retrieves access token t user account information.
func (t Token) GetTokenUser() (*Tokenuser, error) {
	// advapi32.dll の GetTokenInformation API を呼び出す (TokenUser クラス)
}

// GetUserProfileDirectory retrieves path to the
// root directory of the access token t user's profile.
func (t Token) GetUserProfileDirectory() (string, error) {
	// userenv.dll の GetUserProfileDirectoryW API を呼び出す
	// バッファが足りない場合はリトライするロジックを含む
}

このファイルは、Goの syscall パッケージの慣例に従い、//sys コメントを使ってWin32 APIのシグネチャを定義し、mksyscall_windows.pl スクリプトによって実際のGoラッパー関数が zsyscall_windows_*.go ファイルに自動生成されるようにしています。これにより、Goコードから直接Win32 APIを呼び出すことが可能になります。

src/pkg/os/user/user.go (User構造体の変更)

type User struct {
	Uid      string // user id
	Gid      string // primary group id
	Username string
	Name     string
	HomeDir  string
}

UidGidint から string に変更された点が最も重要です。これにより、Unix系システムの数値UID/GIDとWindowsの文字列SIDの両方を同じ構造体で表現できるようになり、クロスプラットフォームな os/user パッケージのインターフェースが維持されます。

関連リンク

参考にした情報源リンク