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 }