-
Notifications
You must be signed in to change notification settings - Fork 182
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
fs.ResolvePath
to resolve symbolic links (#275)
* Add `fs.ResolvePath` to resolve symbolic links `filepath.EvalSymlinks` does not work well on Windows, and can enter infinite loops in certain situations and error out. Use Win32 API GetFinalPathNameByHandle to handle path resolution. Implementation based off on: containerd/containerd#5411 Signed-off-by: Hamza El-Saawy <[email protected]> * PR: types, documentation Signed-off-by: Hamza El-Saawy <[email protected]> * remove unneded constant groups Signed-off-by: Hamza El-Saawy <[email protected]> * Attempt normalized path first Update logic to try querying for normalized path initially, then use opened path if access is denied. Signed-off-by: Hamza El-Saawy <[email protected]> --------- Signed-off-by: Hamza El-Saawy <[email protected]>
- Loading branch information
Showing
12 changed files
with
650 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// This package contains Win32 filesystem functionality. | ||
package fs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
//go:build windows | ||
|
||
package fs | ||
|
||
import ( | ||
"golang.org/x/sys/windows" | ||
|
||
"github.com/Microsoft/go-winio/internal/stringbuffer" | ||
) | ||
|
||
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go | ||
|
||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew | ||
//sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *syscall.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW | ||
|
||
const NullHandle windows.Handle = 0 | ||
|
||
// AccessMask defines standard, specific, and generic rights. | ||
// | ||
// Bitmask: | ||
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 | ||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 | ||
// +---------------+---------------+-------------------------------+ | ||
// |G|G|G|G|Resvd|A| StandardRights| SpecificRights | | ||
// |R|W|E|A| |S| | | | ||
// +-+-------------+---------------+-------------------------------+ | ||
// | ||
// GR Generic Read | ||
// GW Generic Write | ||
// GE Generic Exectue | ||
// GA Generic All | ||
// Resvd Reserved | ||
// AS Access Security System | ||
// | ||
// https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask | ||
// | ||
// https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights | ||
// | ||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants | ||
type AccessMask = windows.ACCESS_MASK | ||
|
||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. | ||
const ( | ||
// Not actually any. | ||
// | ||
// For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device" | ||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters | ||
FILE_ANY_ACCESS AccessMask = 0 | ||
|
||
// Specific Object Access | ||
// from ntioapi.h | ||
|
||
FILE_READ_DATA AccessMask = (0x0001) // file & pipe | ||
FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory | ||
|
||
FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe | ||
FILE_ADD_FILE AccessMask = (0x0002) // directory | ||
|
||
FILE_APPEND_DATA AccessMask = (0x0004) // file | ||
FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory | ||
FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe | ||
|
||
FILE_READ_EA AccessMask = (0x0008) // file & directory | ||
FILE_READ_PROPERTIES AccessMask = FILE_READ_EA | ||
|
||
FILE_WRITE_EA AccessMask = (0x0010) // file & directory | ||
FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA | ||
|
||
FILE_EXECUTE AccessMask = (0x0020) // file | ||
FILE_TRAVERSE AccessMask = (0x0020) // directory | ||
|
||
FILE_DELETE_CHILD AccessMask = (0x0040) // directory | ||
|
||
FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all | ||
|
||
FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all | ||
|
||
FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) | ||
FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE) | ||
FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE) | ||
FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE) | ||
|
||
SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF | ||
|
||
// Standard Access | ||
// from ntseapi.h | ||
|
||
DELETE AccessMask = 0x0001_0000 | ||
READ_CONTROL AccessMask = 0x0002_0000 | ||
WRITE_DAC AccessMask = 0x0004_0000 | ||
WRITE_OWNER AccessMask = 0x0008_0000 | ||
SYNCHRONIZE AccessMask = 0x0010_0000 | ||
|
||
STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000 | ||
|
||
STANDARD_RIGHTS_READ AccessMask = READ_CONTROL | ||
STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL | ||
STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL | ||
|
||
STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000 | ||
) | ||
|
||
type FileShareMode uint32 | ||
|
||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. | ||
const ( | ||
FILE_SHARE_NONE FileShareMode = 0x00 | ||
FILE_SHARE_READ FileShareMode = 0x01 | ||
FILE_SHARE_WRITE FileShareMode = 0x02 | ||
FILE_SHARE_DELETE FileShareMode = 0x04 | ||
FILE_SHARE_VALID_FLAGS FileShareMode = 0x07 | ||
) | ||
|
||
type FileCreationDisposition uint32 | ||
|
||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. | ||
const ( | ||
// from winbase.h | ||
|
||
CREATE_NEW FileCreationDisposition = 0x01 | ||
CREATE_ALWAYS FileCreationDisposition = 0x02 | ||
OPEN_EXISTING FileCreationDisposition = 0x03 | ||
OPEN_ALWAYS FileCreationDisposition = 0x04 | ||
TRUNCATE_EXISTING FileCreationDisposition = 0x05 | ||
) | ||
|
||
// CreateFile and co. take flags or attributes together as one parameter. | ||
// Define alias until we can use generics to allow both | ||
|
||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants | ||
type FileFlagOrAttribute uint32 | ||
|
||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. | ||
const ( // from winnt.h | ||
FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000 | ||
FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000 | ||
FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000 | ||
FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000 | ||
FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000 | ||
FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000 | ||
FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000 | ||
FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000 | ||
FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000 | ||
FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000 | ||
FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000 | ||
) | ||
|
||
type FileSQSFlag = FileFlagOrAttribute | ||
|
||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. | ||
const ( // from winbase.h | ||
SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16) | ||
SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16) | ||
SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16) | ||
SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16) | ||
|
||
SECURITY_SQOS_PRESENT FileSQSFlag = 0x00100000 | ||
SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F0000 | ||
) | ||
|
||
// GetFinalPathNameByHandle flags | ||
// | ||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters | ||
type GetFinalPathFlag uint32 | ||
|
||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API. | ||
const ( | ||
GetFinalPathDefaultFlag GetFinalPathFlag = 0x0 | ||
|
||
FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0 | ||
FILE_NAME_OPENED GetFinalPathFlag = 0x8 | ||
|
||
VOLUME_NAME_DOS GetFinalPathFlag = 0x0 | ||
VOLUME_NAME_GUID GetFinalPathFlag = 0x1 | ||
VOLUME_NAME_NT GetFinalPathFlag = 0x2 | ||
VOLUME_NAME_NONE GetFinalPathFlag = 0x4 | ||
) | ||
|
||
// getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle | ||
// with the given handle and flags. It transparently takes care of creating a buffer of the | ||
// correct size for the call. | ||
// | ||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew | ||
func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) { | ||
b := stringbuffer.NewWString() | ||
//TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n? | ||
for { | ||
n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags)) | ||
if err != nil { | ||
return "", err | ||
} | ||
// If the buffer wasn't large enough, n will be the total size needed (including null terminator). | ||
// Resize and try again. | ||
if n > b.Cap() { | ||
b.ResizeTo(n) | ||
continue | ||
} | ||
// If the buffer is large enough, n will be the size not including the null terminator. | ||
// Convert to a Go string and return. | ||
return b.String(), nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
//go:build windows | ||
|
||
package fs | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"golang.org/x/sys/windows" | ||
) | ||
|
||
func Test_GetFinalPathNameByHandle(t *testing.T) { | ||
d := t.TempDir() | ||
// open f via a relative path | ||
name := t.Name() + ".txt" | ||
fullPath := filepath.Join(d, name) | ||
|
||
w, err := os.Getwd() | ||
if err != nil { | ||
t.Fatalf("could not get working directory: %v", err) | ||
} | ||
if err := os.Chdir(d); err != nil { | ||
t.Fatalf("could not chdir to %s: %v", d, err) | ||
} | ||
defer os.Chdir(w) //nolint:errcheck | ||
|
||
f, err := os.Create(name) | ||
if err != nil { | ||
t.Fatalf("could not open %s: %v", fullPath, err) | ||
} | ||
defer f.Close() | ||
|
||
path, err := GetFinalPathNameByHandle(windows.Handle(f.Fd()), GetFinalPathDefaultFlag) | ||
if err != nil { | ||
t.Fatalf("could not get final path for %s: %v", fullPath, err) | ||
} | ||
if strings.EqualFold(fullPath, path) { | ||
t.Fatalf("expected %s, got %s", fullPath, path) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package fs | ||
|
||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level | ||
type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32` | ||
|
||
// Impersonation levels | ||
const ( | ||
SecurityAnonymous SecurityImpersonationLevel = 0 | ||
SecurityIdentification SecurityImpersonationLevel = 1 | ||
SecurityImpersonation SecurityImpersonationLevel = 2 | ||
SecurityDelegation SecurityImpersonationLevel = 3 | ||
) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.