syncthing/lib/fs/basicfs_lstat_windows.go
Jakob Borg 8dc826b234
build: use Go 1.23, require minimum 1.22 (#9651)
🥳

---------

Co-authored-by: Ross Smith II <ross@smithii.com>
2024-08-19 20:26:08 +02:00

105 lines
3.1 KiB
Go

// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
import (
"fmt"
"go/version"
"os"
"runtime"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func readReparseTag(path string) (uint32, error) {
namep, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, fmt.Errorf("syscall.UTF16PtrFromString failed with: %s", err)
}
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
if err != nil {
return 0, fmt.Errorf("syscall.CreateFile failed with: %s", err)
}
defer syscall.CloseHandle(h)
//https://docs.microsoft.com/windows/win32/api/winbase/ns-winbase-file_attribute_tag_info
const fileAttributeTagInfo = 9
type FILE_ATTRIBUTE_TAG_INFO struct {
FileAttributes uint32
ReparseTag uint32
}
var ti FILE_ATTRIBUTE_TAG_INFO
err = windows.GetFileInformationByHandleEx(windows.Handle(h), fileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
// It appears calling GetFileInformationByHandleEx with
// FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
// ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
// instance to indicate no symlinks are possible.
ti.ReparseTag = 0
} else {
return 0, fmt.Errorf("windows.GetFileInformationByHandleEx failed with: %s", err)
}
}
return ti.ReparseTag, nil
}
func isDirectoryJunction(reparseTag uint32) bool {
return reparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
}
type dirJunctFileInfo struct {
os.FileInfo
}
func (fi *dirJunctFileInfo) Mode() os.FileMode {
// Simulate a directory and not a symlink; also set the execute
// bits so the directory can be traversed Unix-side.
return fi.FileInfo.Mode() ^ junctionPointModeMask | os.ModeDir | 0111
}
func (fi *dirJunctFileInfo) IsDir() bool {
return true
}
var junctionPointModeMask os.FileMode
func init() {
junctionPointModeMask = os.ModeSymlink
if version.Compare(runtime.Version(), "go1.23") >= 0 {
// if GODEBUG=winsymlink=0, then we are in g1.22 mode so let's check for
// os.ModeSymlink as well, as it can't hurt.
junctionPointModeMask |= os.ModeIrregular
}
}
func (f *BasicFilesystem) underlyingLstat(name string) (os.FileInfo, error) {
var fi, err = os.Lstat(name)
// There are cases where files are tagged as symlink, but they end up being
// something else. Make sure we properly handle those types.
if err == nil {
// NTFS directory junctions can be treated as ordinary directories,
// see https://forum.syncthing.net/t/option-to-follow-directory-junctions-symbolic-links/14750
if fi.Mode()&junctionPointModeMask != 0 && f.junctionsAsDirs {
if reparseTag, reparseErr := readReparseTag(name); reparseErr == nil && isDirectoryJunction(reparseTag) {
return &dirJunctFileInfo{fi}, nil
}
}
}
return fi, err
}