きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

Go言語で、ファイルの「所有者」を拾えるか? You、DLL呼び出しちゃいなよ

発端

java.nio.file.attributeで「所有者」情報が取れる。
PowerShellでも取れる。
では、Go言語はどうだ?

調査

たとえば、C++でそれを行う場合、こういうことをする。
Finding the Owner of a File Object in C++ (Windows)


ここで登場する「GetSecurityInfo関数」はGo言語で使われているか?
調べた結果、使われていない。

大抵のdll呼び出しは、"src/syscall/zsyscall_windows.go"を読むと分かる。

「GetSecurityInfo関数」は、「advapi32.dll」に含まれています。
GetSecurityInfo function (Windows)


Go言語は、内部で「advapi32.dll」を読み込んではいるものの、「GetSecurityInfo関数」を呼び出せるようにはなっていません。
今見ているソースはGo1.8です。

挑戦

ネジがゆるい私は、「よし、DLL呼び出してみよう」と、泥船に足を突っ込んだ。

Go内部では、ロードしているDLLですが、変数が公開されていません。
しかたない、自分でロードすんぜ。

var (
    modadvapi32          = syscall.NewLazyDLL("advapi32.dll")
    procGetSecurityInfo  = modadvapi32.NewProc("GetSecurityInfo")
)

はい、ただのもろパクリです。
あ、でもsysdll.Add()を挟むのは除外しました、なぜならばinternalはコンパイルエラーになるからです。

NewLazyDLLやNewProcについては、「src/syscall/dll_windows.go」に書かれています。

Procには呼び出し用のポインタレシーバが付いてます。

func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) 

とりあえず、引数を全部uintptrで渡せってことか。

1つ目の引数 HANDLE

handleには、ファイルハンドルを渡すことになる。
os.OpenFileして、fileからFdを得ると、それはuintptrなので、そのまま渡してみる。

2つ目の引数 SE_OBJECT_TYPE

今回は、SE_FILE_OBJECTを渡したいわけです。
SE_OBJECT_TYPE enumeration (Windows)

enumで先頭が0初期化してますから、2つ目の定数SE_FILE_OBJECTは、1ってことですね。
これをuintptrで渡すとか、まどろっこしいなぁ。

3つ目の引数 SECURITY_INFORMATION

今回はOWNER_SECURITY_INFORMATIONを渡したいので、おそらく1です
DWORDとあるので、uint32にしときます。

4つ目の引数 ppsidOwner 型はPSID

これが受け取りたい。ぜひ受け取りたい。
でもPSIDって何?
どうやら、可変長な構造であるSIDをポイントしているポインターっぽい。

var psidOwner = make([]byte, uint32(50))
sid := (unsafe.Pointer(&psidOwner))
ppsidOwner := uintptr(unsafe.Pointer(sid))

ちょっと変数名をどうするか、ちぐはぐな感じになったが、こんな感じで書いてみた。
これは、「src/syscall/security_windows.go」にあるfunc LookupSIDを参考にしている。

残りの引数

どうせオプションだし、渡さないことにする。

ダメだった点

1をあらわしている引数をuintptrにして渡してるのがどうもダメっぽかったので修正。
最終的にこの形にした。1をベタ書きです。。。ひどい。なんだコレ。

  r1, r2, lastErr := procGetSecurityInfo.Call(handle, 1, 1, ppsidOwner)


なんとか結果が返ってきた。
ここからバッファ渡してるやつをポイントしなおす。

    ownerSID := (*syscall.SID)(unsafe.Pointer(&psidOwner[0]))
    str, convertErr := ownerSID.String()
    if convertErr != nil {

    } else {
        fmt.Printf("string SID = %s\n", str)
    }

    account, domain, accountType, lookUpErr := ownerSID.LookupAccount("")
    if lookUpErr != nil {

    } else {
        fmt.Printf("account = %s\n", account)
        fmt.Printf("domain = %s\n", domain)
        fmt.Printf("accountType = %d\n", accountType)
    }

ownerSID.String()すると「S-X-X-XX-XXXXXXXX」みたいな形式のSIDが取れてます。
ownerSID.LookupAccount("")するとアカウントがとれました。
このあたりのことは「src/syscall/security_windows.go」を見てください。


こまごました点は考慮していませんが、DLL呼び出せちゃいました。
.