[インデックス 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
パッケージのクロスプラットフォームなインターフェースに統合したことです。
-
User
構造体の変更:src/pkg/os/user/user.go
において、User
構造体のUid
とGid
フィールドの型が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.」と明記されています。
-
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.TranslateAccountName
やsyscall.NetUserGetInfo
を試行します。これは、ドメイン環境とローカル環境の両方に対応するためです。
- ユーザーのフルネームを取得するために
-
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の型も定義されています。
-
ビルドスクリプトの更新:
src/buildscript/windows_386.sh
およびsrc/buildscript/windows_amd64.sh
が更新され、syscall
パッケージのコンパイル時にsecurity_windows.go
が含まれるようになりました。src/pkg/syscall/Makefile
とsrc/pkg/syscall/mkall.sh
も同様に更新され、security_windows.go
がビルドプロセスに組み込まれるように変更されています。src/pkg/os/user/Makefile
も更新され、Windowsビルド時にlookup_windows.go
が含まれ、CGOが有効でないUnix系システムではlookup_stubs.go
が含まれるように条件付きコンパイルが設定されました。
-
テストの更新:
src/pkg/os/user/user_test.go
が更新され、Windows環境でもCurrent()
,Lookup()
,LookupId()
が正しく動作するかを検証するテストが追加されました。- 特に、
Uid
とGid
が文字列型になったことに対応し、テストでの比較ロジックも変更されています。WindowsではGid
とHomeDir
の取得が困難な場合があるため、テストでこれらのフィールドの比較をスキップするロジックも含まれています(BUG(brainman)
コメントで示唆されているように、当時の実装ではGid
とHomeDir
が完全に取得できないケースがあったため)。
このコミットにより、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
構造体のUid
とGid
フィールドへの値設定がstrconv.Itoa
を使って文字列に変換されるようになった。src/pkg/os/user/lookup_windows.go
: 新規追加ファイル。Windows環境向けのユーザー情報ルックアップ実装。Current()
,Lookup()
,LookupId()
関数がWindows API (syscall
パッケージ経由) を利用して実装されている。src/pkg/os/user/user.go
:User
構造体のUid
とGid
フィールドの型がint
からstring
に変更された。implemented
変数の初期値がfalse
からtrue
に変更され、lookup_stubs.go
のinit
関数でfalse
に上書きされるようになった。src/pkg/os/user/user_test.go
:os/user
パッケージのテストファイル。Windows環境でのテストを有効化し、Current()
,Lookup()
,LookupId()
のテストロジックが追加・修正された。User
構造体のUid
とGid
が文字列になったことに対応し、比較ロジックも変更された。src/pkg/syscall/Makefile
:GOFILES_windows
にsecurity_windows.go
が追加された。src/pkg/syscall/mkall.sh
:syscall_goos
変数にsecurity_windows.go
が追加され、mksyscall_windows.pl
がsecurity_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
}
Uid
と Gid
が int
から string
に変更された点が最も重要です。これにより、Unix系システムの数値UID/GIDとWindowsの文字列SIDの両方を同じ構造体で表現できるようになり、クロスプラットフォームな os/user
パッケージのインターフェースが維持されます。
関連リンク
- Go Issue 1789: https://github.com/golang/go/issues/1789
- Go CL 5543069: https://golang.org/cl/5543069
参考にした情報源リンク
- Microsoft Docs - Security Identifiers (SIDs): https://learn.microsoft.com/en-us/windows/win32/secauthz/security-identifiers
- Microsoft Docs - Access Tokens: https://learn.microsoft.com/en-us/windows/win32/secauthz/access-tokens
- Microsoft Docs - User Profiles: https://learn.microsoft.com/en-us/windows/win32/shell/user-profiles
- Microsoft Docs -
OpenProcessToken
function: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken - Microsoft Docs -
GetTokenInformation
function: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-gettokeninformation - Microsoft Docs -
GetUserProfileDirectory
function: https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw - Microsoft Docs -
LookupAccountSid
function: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountsidw - Microsoft Docs -
LookupAccountName
function: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew - Microsoft Docs -
ConvertSidToStringSid
function: https://learn.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertsidtostringsid - Microsoft Docs -
ConvertStringSidToSid
function: https://learn.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertstringsidtostringsid - Microsoft Docs -
TranslateName
function: https://learn.microsoft.com/en-us/windows/win32/api/secext/nf-secext-translatenamew - Microsoft Docs -
NetUserGetInfo
function: https://learn.microsoft.com/en-us/windows/win32/api/lmuse/nf-lmuse-netusergetinfo - Go Wiki -
syscall
package: https://go.dev/wiki/WindowsSyscall - Go Source Code -
mksyscall_windows.pl
: https://github.com/golang/go/blob/master/src/syscall/mksyscall_windows.pl