[インデックス 12495] ファイルの概要
このコミットは、Go言語の標準ライブラリpath/filepath
パッケージにおけるWindows環境でのシンボリックリンク評価(EvalSymlinks
関数)の挙動を改善するものです。具体的には、Windowsのファイルシステムが持つ多様なパス表現(ショートネーム、ロングネーム、シンボリックリンク、ジャンクションポイントなど)に対応するため、Windows APIのGetLongPathNameW
関数を利用するように変更されています。これにより、EvalSymlinks
がWindows上でより正確な「実ファイル名」を取得できるようになります。
コミット
commit 0029b0d20744392260997441c1158dcb1b23a734
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Mar 8 10:00:25 2012 +1100
path/filepath: retrieve real file name in windows EvalSymlinks
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5756049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0029b0d20744392260997441c1158dcb1b23a734
元コミット内容
path/filepath: retrieve real file name in windows EvalSymlinks
このコミットは、Windows環境におけるpath/filepath
パッケージのEvalSymlinks
関数が、シンボリックリンクやその他のパス表現を評価する際に、より正確な実ファイル名(ロングパス名)を取得するように修正するものです。
変更の背景
Go言語のpath/filepath.EvalSymlinks
関数は、与えられたパスが指すシンボリックリンクを解決し、最終的な実パスを返すことを目的としています。しかし、Windowsのファイルシステムは、UNIX系システムとは異なるパス解決のメカニズムを持っています。特に、ショートファイル名(8.3形式)や、シンボリックリンク、ジャンクションポイント、マウントポイントなど、様々な種類の「再解析ポイント」が存在します。
従来のEvalSymlinks
の実装では、Windows環境においてこれらの複雑なパス表現を適切に解決できていませんでした。特に、シンボリックリンクが指す先の「実ファイル名」を取得する際に、Windowsが内部的に使用するロングパス名(完全なファイル名)ではなく、ショートパス名や不完全なパスが返される可能性がありました。これは、ファイル操作の正確性や、パスの正規化において問題を引き起こす可能性がありました。
このコミットの背景には、Windows環境でのEvalSymlinks
の信頼性と正確性を向上させ、GoプログラムがWindowsファイルシステムとより適切に連携できるようにするという目的があります。
前提知識の解説
シンボリックリンク (Symbolic Link)
シンボリックリンク(またはソフトリンク)は、ファイルシステム上の別のファイルやディレクトリへの参照(ポインタ)を含む特殊なファイルです。シンボリックリンクを操作すると、通常はそれが指す元のファイルやディレクトリが操作されます。UNIX系システムでは広く使われていますが、WindowsでもWindows 2000以降、NTFSファイルシステムでサポートされています。
Windowsのパス表現
Windowsのファイルシステムは、UNIX系システムとは異なり、パスの表現にいくつかの特徴があります。
- ドライブレター:
C:\
,D:\
のようにドライブレターで始まる絶対パス。 - UNCパス:
\\Server\Share\Path
のようにネットワーク上のリソースを指すパス。 - ショートファイル名 (8.3形式): DOS時代からの互換性のために存在する、ファイル名が8文字、拡張子が3文字に制限された形式。例:
PROGRA~1
はProgram Files
のショートファイル名。 - ロングファイル名: 通常の、スペースや特殊文字を含むファイル名。
- ジャンクションポイント (Junction Point): NTFSファイルシステムにおけるディレクトリへのシンボリックリンクに似た機能。ボリューム内の別のディレクトリを指します。
- ディレクトリシンボリックリンク: Windows Vista以降で導入された、ディレクトリへのシンボリックリンク。
- ファイルシンボリックリンク: Windows Vista以降で導入された、ファイルへのシンボリックリンク。
これらの多様なパス表現を正確に解決し、最終的な実パス(ロングパス名)を取得することは、Windowsプログラミングにおいて重要です。
GetLongPathNameW
Windows API
GetLongPathNameW
は、Windows APIの一つで、指定されたパスのロングパス形式を取得するために使用されます。この関数は、ショートパス名、シンボリックリンク、ジャンクションポイントなどを解決し、そのパスが指す実際のファイルまたはディレクトリの完全なロングパス名を返します。W
サフィックスは、ワイド文字(UTF-16)を使用するバージョンであることを示します。
技術的詳細
このコミットの主要な技術的変更点は、path/filepath.EvalSymlinks
関数のWindows固有の処理を、Windows APIのGetLongPathNameW
を利用するように変更した点です。
-
プラットフォーム固有の実装の分離:
- 従来の
path/filepath/path.go
にあったEvalSymlinks
関数内のWindows固有の処理が削除されました。 - 代わりに、
evalSymlinks
という内部関数が導入され、この関数がプラットフォーム固有の挙動をカプセル化するように設計されました。 src/pkg/path/filepath/symlink.go
が新しく作成され、UNIX系システム(!windows
ビルドタグ)向けのevalSymlinks
の実装が格納されました。この実装は、従来のEvalSymlinks
のUNIX系システム向けのロジックをそのまま引き継いでいます。src/pkg/path/filepath/symlink_windows.go
が新しく作成され、Windows向けのevalSymlinks
の実装が格納されました。このファイルはwindows
ビルドタグによってWindowsでのみコンパイルされます。
- 従来の
-
Windowsでの
GetLongPathNameW
の利用:symlink_windows.go
内のevalSymlinks
関数は、syscall.GetLongPathName
を呼び出すことで、入力パスのロングパス名を取得します。GetLongPathName
は、UTF-16エンコーディングのパスを受け取り、UTF-16エンコーディングのロングパス名を返します。Goのsyscall
パッケージは、これらのUTF-16文字列とGoの文字列(UTF-8)間の変換を処理します。GetLongPathName
は、バッファのサイズが足りない場合に、必要なバッファサイズを返すため、そのケースをハンドリングしてバッファを再割り当てし、再度呼び出すロジックが含まれています。
-
syscall
パッケージの拡張:src/pkg/syscall/syscall_windows.go
、src/pkg/syscall/zsyscall_windows_386.go
、src/pkg/syscall/zsyscall_windows_amd64.go
が変更され、GetLongPathNameW
Windows APIをGoのsyscall
パッケージから呼び出せるように、その定義と実装が追加されました。これにより、Goのコードから直接このAPIを利用できるようになります。
-
依存関係の更新:
src/pkg/go/build/deps_test.go
が更新され、path/filepath
パッケージがsyscall
パッケージに依存するようになったことが反映されています。これは、Windows固有のevalSymlinks
実装がsyscall
パッケージを利用するためです。
この変更により、Windows環境でのEvalSymlinks
は、ショートパス名やシンボリックリンク、ジャンクションポイントなど、Windowsがサポートするあらゆるパス表現を正確に解決し、その実ファイル名(ロングパス名)を返すことができるようになりました。
コアとなるコードの変更箇所
src/pkg/path/filepath/path.go
EvalSymlinks
関数からWindows固有のロジックが削除され、プラットフォーム固有のevalSymlinks
関数を呼び出すように変更されました。
--- a/src/pkg/path/filepath/path.go
+++ b/src/pkg/path/filepath/path.go
@@ -191,64 +189,7 @@ func Ext(path string) string {
// If path is relative the result will be relative to the current directory,
// unless one of the components is an absolute symbolic link.
func EvalSymlinks(path string) (string, error) {
- if runtime.GOOS == "windows" {
- // Symlinks are not supported under windows.
- _, err := os.Lstat(path)
- if err != nil {
- return "", err
- }
- return Clean(path), nil
- }
- const maxIter = 255
- originalPath := path
- // consume path by taking each frontmost path element,
- // expanding it if it's a symlink, and appending it to b
- var b bytes.Buffer
- for n := 0; path != ""; n++ {
- if n > maxIter {
- return "", errors.New("EvalSymlinks: too many links in " + originalPath)
- }
-
- // find next path component, p
- i := strings.IndexRune(path, Separator)
- var p string
- if i == -1 {
- p, path = path, ""
- } else {
- p, path = path[:i], path[i+1:]
- }
-
- if p == "" {
- if b.Len() == 0 {
- // must be absolute path
- b.WriteRune(Separator)
- }
- continue
- }
-
- fi, err := os.Lstat(b.String() + p)
- if err != nil {
- return "", err
- }
- if fi.Mode()&os.ModeSymlink == 0 {
- b.WriteString(p)
- if path != "" {
- b.WriteRune(Separator)
- }
- continue
- }
-
- // it's a symlink, put it at the front of path
- dest, err := os.Readlink(b.String() + p)
- if err != nil {
- return "", err
- }
- if IsAbs(dest) {
- b.Reset()
- }
- path = dest + string(Separator) + path
- }
- return Clean(b.String()), nil
+ return evalSymlinks(path)
}
src/pkg/path/filepath/symlink_windows.go
(新規ファイル)
Windows環境でのevalSymlinks
の実装。syscall.GetLongPathName
を利用してロングパス名を取得します。
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package filepath
import (
"syscall"
)
func evalSymlinks(path string) (string, error) {
p := syscall.StringToUTF16(path)
b := p // GetLongPathName says we can reuse buffer
n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
if n > uint32(len(b)) {
b = make([]uint16, n)
n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
}
b = b[:n]
return Clean(syscall.UTF16ToString(b)), nil
}
src/pkg/syscall/syscall_windows.go
GetLongPathName
関数の宣言が追加されました。
--- a/src/pkg/syscall/syscall_windows.go
+++ b/src/pkg/syscall/syscall_windows.go
@@ -174,6 +174,7 @@ func NewCallback(fn interface{}) uintptr
//sys SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error)
//sys FlushFileBuffers(handle Handle) (err error)
//sys GetFullPathName(path *uint16, buflen uint32, buf *uint16, fname **uint16) (n uint32, err error) = kernel32.GetFullPathNameW
+//sys GetLongPathName(path *uint16, buf *uint16, buflen uint32) (n uint32, err error) = kernel32.GetLongPathNameW
//sys CreateFileMapping(fhandle Handle, sa *SecurityAttributes, prot uint32, maxSizeHigh uint32, maxSizeLow uint32, name *uint16) (handle Handle, err error) = kernel32.CreateFileMappingW
//sys MapViewOfFile(handle Handle, access uint32, offsetHigh uint32, offsetLow uint32, length uintptr) (addr uintptr, err error)
//sys UnmapViewOfFile(addr uintptr) (err error)
src/pkg/syscall/zsyscall_windows_386.go
および src/pkg/syscall/zsyscall_windows_amd64.go
GetLongPathName
関数の実装が追加されました。これは、Windows APIのGetLongPathNameW
を呼び出すためのGoのラッパーです。
--- a/src/pkg/syscall/zsyscall_windows_386.go
+++ b/src/pkg/syscall/zsyscall_windows_386.go
@@ -890,6 +891,19 @@ func GetFullPathName(path *uint16, buflen uint32, buf *uint16, fname **uint16) (
return
}
+func GetLongPathName(path *uint16, buf *uint16, buflen uint32) (n uint32, err error) {
+ r0, _, e1 := Syscall(procGetLongPathNameW.Addr(), 3, uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(buf)), uintptr(buflen))
+ n = uint32(r0)
+ if n == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = EINVAL
+ }
+ }
+ return
+}
+
func CreateFileMapping(fhandle Handle, sa *SecurityAttributes, prot uint32, maxSizeHigh uint32, maxSizeLow uint32, name *uint16) (handle Handle, err error) {
r0, _, e1 := Syscall6(procCreateFileMappingW.Addr(), 6, uintptr(fhandle), uintptr(unsafe.Pointer(sa)), uintptr(prot), uintptr(maxSizeHigh), uintptr(maxSizeLow), uintptr(unsafe.Pointer(name)))
handle = Handle(r0)
コアとなるコードの解説
このコミットの核心は、Windowsにおけるパス解決の複雑性に対応するため、Goのpath/filepath.EvalSymlinks
関数がWindows APIのGetLongPathNameW
を利用するように変更された点です。
-
path/filepath/path.go
の変更:EvalSymlinks
関数は、もはやプラットフォーム固有のロジックを直接含んでいません。これは、Goの標準ライブラリにおける一般的な設計パターンであり、プラットフォーム固有のコードを分離することで、コードの可読性と保守性を向上させます。return evalSymlinks(path)
というシンプルな呼び出しに置き換えられたことで、EvalSymlinks
は、コンパイル時に選択されたプラットフォーム固有のevalSymlinks
実装に処理を委譲するようになりました。
-
symlink_windows.go
の新規追加とevalSymlinks
の実装:- このファイルは、
// +build windows
タグによってWindows環境でのみコンパイルされます。 evalSymlinks
関数は、入力パスpath
をsyscall.StringToUTF16
でUTF-16エンコーディングに変換します。Windows APIは通常UTF-16を使用するため、これは必須のステップです。syscall.GetLongPathName
を呼び出し、変換されたパスを渡します。この関数は、ショートパス名、シンボリックリンク、ジャンクションポイントなどを解決し、そのパスが指す実際のファイルまたはディレクトリの完全なロングパス名をUTF-16形式でb
バッファに書き込みます。GetLongPathName
は、必要なバッファサイズが現在のバッファサイズを超える場合、その必要なサイズを返します。この場合、コードは新しい、より大きなバッファをmake([]uint16, n)
で作成し、再度GetLongPathName
を呼び出して、完全なロングパス名を取得します。これは、Windows APIを安全に利用するための一般的なパターンです。- 最後に、取得したUTF-16形式のロングパス名を
syscall.UTF16ToString
でGoの文字列(UTF-8)に変換し、filepath.Clean
でパスを正規化して返します。Clean
関数は、余分なスラッシュの削除や、.
、..
の解決などを行います。
- このファイルは、
-
syscall
パッケージの拡張:syscall
パッケージは、Goプログラムがオペレーティングシステムの低レベルな機能(システムコール)にアクセスするためのインターフェースを提供します。syscall_windows.go
では、GetLongPathName
というGoの関数が、Windows APIのGetLongPathNameW
に対応するように宣言されています。//sys
ディレクティブは、Goのツールチェーンがこの宣言に基づいて、対応するシステムコールを自動的に生成することを示します。zsyscall_windows_386.go
とzsyscall_windows_amd64.go
は、それぞれ32ビットおよび64ビットWindowsアーキテクチャ向けの自動生成されたシステムコールラッパーファイルです。このコミットでは、GetLongPathName
関数がprocGetLongPathNameW
(kernel32.dll
からロードされるGetLongPathNameW
関数のアドレス)を呼び出すように実装が追加されています。これにより、Goのコードから直接Windows APIを呼び出すことが可能になります。
この一連の変更により、Goのpath/filepath.EvalSymlinks
は、Windows環境において、より堅牢で正確なパス解決能力を獲得しました。
関連リンク
- Go言語の
path/filepath
パッケージ: https://pkg.go.dev/path/filepath - Go言語の
syscall
パッケージ: https://pkg.go.dev/syscall - Windows API
GetLongPathNameW
(Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getlongpathname
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/5756049 (コミットメッセージに記載されているCLリンク)
- Microsoft Learn -
GetLongPathNameW
documentation: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getlongpathname - Goのビルドタグに関するドキュメント: https://go.dev/cmd/go/#hdr-Build_constraints
- Goの
syscall
パッケージの利用方法に関する一般的な情報 (Goのドキュメントやブログ記事など)