github.com/hattya/nazuna@v0.7.1-0.20240331055452-55e14c275c1c/util_windows.go (about)

     1  //
     2  // nazuna :: util_windows.go
     3  //
     4  //   Copyright (c) 2013-2021 Akinori Hattori <hattya@gmail.com>
     5  //
     6  //   SPDX-License-Identifier: MIT
     7  //
     8  
     9  package nazuna
    10  
    11  import (
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"unsafe"
    16  
    17  	"golang.org/x/sys/windows"
    18  )
    19  
    20  func IsLink(path string) bool {
    21  	h, err := createFile(path, 0)
    22  	if err != nil {
    23  		return false
    24  	}
    25  	defer windows.CloseHandle(h)
    26  
    27  	var fi windows.ByHandleFileInformation
    28  	if err := windows.GetFileInformationByHandle(h, &fi); err != nil {
    29  		return false
    30  	}
    31  	// hard link
    32  	if fi.NumberOfLinks > 1 {
    33  		return true
    34  	}
    35  	// reparse point
    36  	if fi.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
    37  		return false
    38  	}
    39  	b := make([]byte, windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
    40  	var retlen uint32
    41  	if err := windows.DeviceIoControl(h, windows.FSCTL_GET_REPARSE_POINT, nil, 0, &b[0], uint32(len(b)), &retlen, nil); err != nil {
    42  		return false
    43  	}
    44  	switch rdb := (*reparseDataBuffer)(unsafe.Pointer(&b[0])); rdb.ReparseTag {
    45  	case windows.IO_REPARSE_TAG_SYMLINK:
    46  	case windows.IO_REPARSE_TAG_MOUNT_POINT:
    47  	default:
    48  		return false
    49  	}
    50  	return true
    51  }
    52  
    53  func LinksTo(path, origin string) bool {
    54  	h, err := createFile(path, 0)
    55  	if err != nil {
    56  		return false
    57  	}
    58  	defer windows.CloseHandle(h)
    59  
    60  	var fi1 windows.ByHandleFileInformation
    61  	if err := windows.GetFileInformationByHandle(h, &fi1); err != nil {
    62  		return false
    63  	}
    64  	// hard link
    65  	if fi1.NumberOfLinks > 1 {
    66  		h, err := createFile(origin, 0)
    67  		if err != nil {
    68  			return false
    69  		}
    70  		defer windows.CloseHandle(h)
    71  
    72  		var fi2 windows.ByHandleFileInformation
    73  		if err := windows.GetFileInformationByHandle(h, &fi2); err != nil {
    74  			return false
    75  		}
    76  		return fi1.VolumeSerialNumber == fi2.VolumeSerialNumber && fi1.FileIndexHigh == fi2.FileIndexHigh && fi1.FileIndexLow == fi2.FileIndexLow
    77  	}
    78  	// reparse point
    79  	if fi1.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
    80  		return false
    81  	}
    82  	path, err = filepath.Abs(path)
    83  	if err != nil {
    84  		return false
    85  	}
    86  	origin, err = filepath.Abs(origin)
    87  	if err != nil {
    88  		return false
    89  	}
    90  	b := make([]byte, windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
    91  	var retlen uint32
    92  	if err := windows.DeviceIoControl(h, windows.FSCTL_GET_REPARSE_POINT, nil, 0, &b[0], uint32(len(b)), &retlen, nil); err != nil {
    93  		return false
    94  	}
    95  	switch rdb := (*reparseDataBuffer)(unsafe.Pointer(&b[0])); rdb.ReparseTag {
    96  	case windows.IO_REPARSE_TAG_SYMLINK:
    97  		rb := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.ReparseBuffer))
    98  		n := (rb.SubstituteNameOffset + rb.SubstituteNameLength) / 2
    99  		p := windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rb.PathBuffer[0]))[rb.SubstituteNameOffset/2 : n : n])
   100  		if rb.Flags&_SYMLINK_FLAG_RELATIVE != 0 {
   101  			path = filepath.Join(filepath.Dir(path), p)
   102  		} else {
   103  			path = p
   104  		}
   105  	case windows.IO_REPARSE_TAG_MOUNT_POINT:
   106  		rb := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.ReparseBuffer))
   107  		n := (rb.SubstituteNameOffset + rb.SubstituteNameLength) / 2
   108  		path = windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rb.PathBuffer[0]))[rb.SubstituteNameOffset/2 : n : n])
   109  	default:
   110  		return false
   111  	}
   112  	if strings.HasPrefix(path, `\??\`) {
   113  		path = path[4:]
   114  		switch {
   115  		case len(path) >= 2 && path[1] == ':':
   116  		case len(path) >= 4 && path[:4] == `UNC\`:
   117  			path = `\\` + path[4:]
   118  		}
   119  	}
   120  	return path == origin
   121  }
   122  
   123  func CreateLink(src, dst string) error {
   124  	if IsDir(src) {
   125  		return createMountPoint(src, dst)
   126  	}
   127  	return os.Link(src, dst)
   128  }
   129  
   130  func Unlink(path string) error {
   131  	if !IsLink(path) {
   132  		return &os.PathError{
   133  			Op:   "unlink",
   134  			Path: path,
   135  			Err:  ErrNotLink,
   136  		}
   137  	}
   138  	return os.Remove(path)
   139  }
   140  
   141  func createFile(path string, access uint32) (windows.Handle, error) {
   142  	p, err := windows.UTF16PtrFromString(path)
   143  	if err != nil {
   144  		return windows.InvalidHandle, err
   145  	}
   146  	return windows.CreateFile(p, access, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OPEN_REPARSE_POINT, 0)
   147  }
   148  
   149  func createMountPoint(src, dst string) error {
   150  	linkErr := func(err error) error {
   151  		return &os.LinkError{
   152  			Op:  "link",
   153  			Old: src,
   154  			New: dst,
   155  			Err: err,
   156  		}
   157  	}
   158  
   159  	if _, err := os.Stat(dst); err == nil {
   160  		return linkErr(windows.ERROR_ALREADY_EXISTS)
   161  	}
   162  	if err := os.MkdirAll(dst, 0o777); err != nil {
   163  		return linkErr(err)
   164  	}
   165  	h, err := createFile(dst, windows.GENERIC_WRITE)
   166  	if err != nil {
   167  		return linkErr(err)
   168  	}
   169  	defer windows.CloseHandle(h)
   170  
   171  	path, err := filepath.Abs(src)
   172  	if err != nil {
   173  		return linkErr(err)
   174  	}
   175  	sn, err := windows.UTF16FromString(`\??\` + path)
   176  	if err != nil {
   177  		return linkErr(err)
   178  	}
   179  	pn, err := windows.UTF16FromString(path)
   180  	if err != nil {
   181  		return linkErr(err)
   182  	}
   183  
   184  	n := len(sn) + len(pn)
   185  	b := make([]byte, windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
   186  	var retlen uint32
   187  	rdb := (*reparseDataBuffer)(unsafe.Pointer(&b[0]))
   188  	rdb.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT
   189  	rb := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.ReparseBuffer))
   190  	rb.SubstituteNameLength = uint16((len(sn) - 1) * 2)
   191  	rb.PrintNameOffset = rb.SubstituteNameLength + 2
   192  	rb.PrintNameLength = uint16((len(pn) - 1) * 2)
   193  	copy((*[0xffff]uint16)(unsafe.Pointer(&rb.PathBuffer[0]))[:n:n], append(sn, pn...))
   194  	rdb.ReparseDataLength = 8 + rb.PrintNameOffset + rb.PrintNameLength + 2
   195  	if err := windows.DeviceIoControl(h, _FSCTL_SET_REPARSE_POINT, &b[0], uint32(rdb.ReparseDataLength+8), nil, 0, &retlen, nil); err != nil {
   196  		return linkErr(err)
   197  	}
   198  	return nil
   199  }