1package hcsshim 2 3import ( 4 "errors" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 "syscall" 10 "unicode/utf16" 11 "unsafe" 12 13 winio "github.com/Microsoft/go-winio" 14) 15 16//sys ntCreateFile(handle *uintptr, accessMask uint32, oa *objectAttributes, iosb *ioStatusBlock, allocationSize *uint64, fileAttributes uint32, shareAccess uint32, createDisposition uint32, createOptions uint32, eaBuffer *byte, eaLength uint32) (status uint32) = ntdll.NtCreateFile 17//sys ntSetInformationFile(handle uintptr, iosb *ioStatusBlock, information uintptr, length uint32, class uint32) (status uint32) = ntdll.NtSetInformationFile 18//sys rtlNtStatusToDosError(status uint32) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb 19//sys localAlloc(flags uint32, size int) (ptr uintptr) = kernel32.LocalAlloc 20//sys localFree(ptr uintptr) = kernel32.LocalFree 21 22type ioStatusBlock struct { 23 Status, Information uintptr 24} 25 26type objectAttributes struct { 27 Length uintptr 28 RootDirectory uintptr 29 ObjectName uintptr 30 Attributes uintptr 31 SecurityDescriptor uintptr 32 SecurityQoS uintptr 33} 34 35type unicodeString struct { 36 Length uint16 37 MaximumLength uint16 38 Buffer uintptr 39} 40 41type fileLinkInformation struct { 42 ReplaceIfExists bool 43 RootDirectory uintptr 44 FileNameLength uint32 45 FileName [1]uint16 46} 47 48type fileDispositionInformationEx struct { 49 Flags uintptr 50} 51 52const ( 53 _FileLinkInformation = 11 54 _FileDispositionInformationEx = 64 55 56 _FILE_READ_ATTRIBUTES = 0x0080 57 _FILE_WRITE_ATTRIBUTES = 0x0100 58 _DELETE = 0x10000 59 60 _FILE_OPEN = 1 61 _FILE_CREATE = 2 62 63 _FILE_DIRECTORY_FILE = 0x00000001 64 _FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020 65 _FILE_DELETE_ON_CLOSE = 0x00001000 66 _FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000 67 _FILE_OPEN_REPARSE_POINT = 0x00200000 68 69 _FILE_DISPOSITION_DELETE = 0x00000001 70 71 _OBJ_DONT_REPARSE = 0x1000 72 73 _STATUS_REPARSE_POINT_ENCOUNTERED = 0xC000050B 74) 75 76func openRoot(path string) (*os.File, error) { 77 longpath, err := makeLongAbsPath(path) 78 if err != nil { 79 return nil, err 80 } 81 return winio.OpenForBackup(longpath, syscall.GENERIC_READ, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, syscall.OPEN_EXISTING) 82} 83 84func ntRelativePath(path string) ([]uint16, error) { 85 path = filepath.Clean(path) 86 if strings.Contains(":", path) { 87 // Since alternate data streams must follow the file they 88 // are attached to, finding one here (out of order) is invalid. 89 return nil, errors.New("path contains invalid character `:`") 90 } 91 fspath := filepath.FromSlash(path) 92 if len(fspath) > 0 && fspath[0] == '\\' { 93 return nil, errors.New("expected relative path") 94 } 95 96 path16 := utf16.Encode(([]rune)(fspath)) 97 if len(path16) > 32767 { 98 return nil, syscall.ENAMETOOLONG 99 } 100 101 return path16, nil 102} 103 104// openRelativeInternal opens a relative path from the given root, failing if 105// any of the intermediate path components are reparse points. 106func openRelativeInternal(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) { 107 var ( 108 h uintptr 109 iosb ioStatusBlock 110 oa objectAttributes 111 ) 112 113 path16, err := ntRelativePath(path) 114 if err != nil { 115 return nil, err 116 } 117 118 if root == nil || root.Fd() == 0 { 119 return nil, errors.New("missing root directory") 120 } 121 122 upathBuffer := localAlloc(0, int(unsafe.Sizeof(unicodeString{}))+len(path16)*2) 123 defer localFree(upathBuffer) 124 125 upath := (*unicodeString)(unsafe.Pointer(upathBuffer)) 126 upath.Length = uint16(len(path16) * 2) 127 upath.MaximumLength = upath.Length 128 upath.Buffer = upathBuffer + unsafe.Sizeof(*upath) 129 copy((*[32768]uint16)(unsafe.Pointer(upath.Buffer))[:], path16) 130 131 oa.Length = unsafe.Sizeof(oa) 132 oa.ObjectName = upathBuffer 133 oa.RootDirectory = uintptr(root.Fd()) 134 oa.Attributes = _OBJ_DONT_REPARSE 135 status := ntCreateFile( 136 &h, 137 accessMask|syscall.SYNCHRONIZE, 138 &oa, 139 &iosb, 140 nil, 141 0, 142 shareFlags, 143 createDisposition, 144 _FILE_OPEN_FOR_BACKUP_INTENT|_FILE_SYNCHRONOUS_IO_NONALERT|flags, 145 nil, 146 0, 147 ) 148 if status != 0 { 149 return nil, rtlNtStatusToDosError(status) 150 } 151 152 fullPath, err := makeLongAbsPath(filepath.Join(root.Name(), path)) 153 if err != nil { 154 syscall.Close(syscall.Handle(h)) 155 return nil, err 156 } 157 158 return os.NewFile(h, fullPath), nil 159} 160 161// openRelative opens a relative path from the given root, failing if 162// any of the intermediate path components are reparse points. 163func openRelative(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) { 164 f, err := openRelativeInternal(path, root, accessMask, shareFlags, createDisposition, flags) 165 if err != nil { 166 err = &os.PathError{Op: "open", Path: filepath.Join(root.Name(), path), Err: err} 167 } 168 return f, err 169} 170 171// linkRelative creates a hard link from oldname to newname (relative to oldroot 172// and newroot), failing if any of the intermediate path components are reparse 173// points. 174func linkRelative(oldname string, oldroot *os.File, newname string, newroot *os.File) error { 175 // Open the old file. 176 oldf, err := openRelativeInternal( 177 oldname, 178 oldroot, 179 syscall.FILE_WRITE_ATTRIBUTES, 180 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 181 _FILE_OPEN, 182 0, 183 ) 184 if err != nil { 185 return &os.LinkError{Op: "link", Old: filepath.Join(oldroot.Name(), oldname), New: filepath.Join(newroot.Name(), newname), Err: err} 186 } 187 defer oldf.Close() 188 189 // Open the parent of the new file. 190 var parent *os.File 191 parentPath := filepath.Dir(newname) 192 if parentPath != "." { 193 parent, err = openRelativeInternal( 194 parentPath, 195 newroot, 196 syscall.GENERIC_READ, 197 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 198 _FILE_OPEN, 199 _FILE_DIRECTORY_FILE) 200 if err != nil { 201 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: err} 202 } 203 defer parent.Close() 204 205 fi, err := winio.GetFileBasicInfo(parent) 206 if err != nil { 207 return err 208 } 209 if (fi.FileAttributes & syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 { 210 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: rtlNtStatusToDosError(_STATUS_REPARSE_POINT_ENCOUNTERED)} 211 } 212 213 } else { 214 parent = newroot 215 } 216 217 // Issue an NT call to create the link. This will be safe because NT will 218 // not open any more directories to create the link, so it cannot walk any 219 // more reparse points. 220 newbase := filepath.Base(newname) 221 newbase16, err := ntRelativePath(newbase) 222 if err != nil { 223 return err 224 } 225 226 size := int(unsafe.Offsetof(fileLinkInformation{}.FileName)) + len(newbase16)*2 227 linkinfoBuffer := localAlloc(0, size) 228 defer localFree(linkinfoBuffer) 229 linkinfo := (*fileLinkInformation)(unsafe.Pointer(linkinfoBuffer)) 230 linkinfo.RootDirectory = parent.Fd() 231 linkinfo.FileNameLength = uint32(len(newbase16) * 2) 232 copy((*[32768]uint16)(unsafe.Pointer(&linkinfo.FileName[0]))[:], newbase16) 233 234 var iosb ioStatusBlock 235 status := ntSetInformationFile( 236 oldf.Fd(), 237 &iosb, 238 linkinfoBuffer, 239 uint32(size), 240 _FileLinkInformation, 241 ) 242 if status != 0 { 243 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(parent.Name(), newbase), Err: rtlNtStatusToDosError(status)} 244 } 245 246 return nil 247} 248 249// deleteOnClose marks a file to be deleted when the handle is closed. 250func deleteOnClose(f *os.File) error { 251 disposition := fileDispositionInformationEx{Flags: _FILE_DISPOSITION_DELETE} 252 var iosb ioStatusBlock 253 status := ntSetInformationFile( 254 f.Fd(), 255 &iosb, 256 uintptr(unsafe.Pointer(&disposition)), 257 uint32(unsafe.Sizeof(disposition)), 258 _FileDispositionInformationEx, 259 ) 260 if status != 0 { 261 return rtlNtStatusToDosError(status) 262 } 263 return nil 264} 265 266// clearReadOnly clears the readonly attribute on a file. 267func clearReadOnly(f *os.File) error { 268 bi, err := winio.GetFileBasicInfo(f) 269 if err != nil { 270 return err 271 } 272 if bi.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY == 0 { 273 return nil 274 } 275 sbi := winio.FileBasicInfo{ 276 FileAttributes: bi.FileAttributes &^ syscall.FILE_ATTRIBUTE_READONLY, 277 } 278 if sbi.FileAttributes == 0 { 279 sbi.FileAttributes = syscall.FILE_ATTRIBUTE_NORMAL 280 } 281 return winio.SetFileBasicInfo(f, &sbi) 282} 283 284// removeRelative removes a file or directory relative to a root, failing if any 285// intermediate path components are reparse points. 286func removeRelative(path string, root *os.File) error { 287 f, err := openRelativeInternal( 288 path, 289 root, 290 _FILE_READ_ATTRIBUTES|_FILE_WRITE_ATTRIBUTES|_DELETE, 291 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 292 _FILE_OPEN, 293 _FILE_OPEN_REPARSE_POINT) 294 if err == nil { 295 defer f.Close() 296 err = deleteOnClose(f) 297 if err == syscall.ERROR_ACCESS_DENIED { 298 // Maybe the file is marked readonly. Clear the bit and retry. 299 clearReadOnly(f) 300 err = deleteOnClose(f) 301 } 302 } 303 if err != nil { 304 return &os.PathError{Op: "remove", Path: filepath.Join(root.Name(), path), Err: err} 305 } 306 return nil 307} 308 309// removeAllRelative removes a directory tree relative to a root, failing if any 310// intermediate path components are reparse points. 311func removeAllRelative(path string, root *os.File) error { 312 fi, err := lstatRelative(path, root) 313 if err != nil { 314 if os.IsNotExist(err) { 315 return nil 316 } 317 return err 318 } 319 fileAttributes := fi.Sys().(*syscall.Win32FileAttributeData).FileAttributes 320 if fileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 || fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 321 // If this is a reparse point, it can't have children. Simple remove will do. 322 err := removeRelative(path, root) 323 if err == nil || os.IsNotExist(err) { 324 return nil 325 } 326 return err 327 } 328 329 // It is necessary to use os.Open as Readdirnames does not work with 330 // openRelative. This is safe because the above lstatrelative fails 331 // if the target is outside the root, and we know this is not a 332 // symlink from the above FILE_ATTRIBUTE_REPARSE_POINT check. 333 fd, err := os.Open(filepath.Join(root.Name(), path)) 334 if err != nil { 335 if os.IsNotExist(err) { 336 // Race. It was deleted between the Lstat and Open. 337 // Return nil per RemoveAll's docs. 338 return nil 339 } 340 return err 341 } 342 343 // Remove contents & return first error. 344 for { 345 names, err1 := fd.Readdirnames(100) 346 for _, name := range names { 347 err1 := removeAllRelative(path+string(os.PathSeparator)+name, root) 348 if err == nil { 349 err = err1 350 } 351 } 352 if err1 == io.EOF { 353 break 354 } 355 // If Readdirnames returned an error, use it. 356 if err == nil { 357 err = err1 358 } 359 if len(names) == 0 { 360 break 361 } 362 } 363 fd.Close() 364 365 // Remove directory. 366 err1 := removeRelative(path, root) 367 if err1 == nil || os.IsNotExist(err1) { 368 return nil 369 } 370 if err == nil { 371 err = err1 372 } 373 return err 374} 375 376// mkdirRelative creates a directory relative to a root, failing if any 377// intermediate path components are reparse points. 378func mkdirRelative(path string, root *os.File) error { 379 f, err := openRelativeInternal( 380 path, 381 root, 382 0, 383 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 384 _FILE_CREATE, 385 _FILE_DIRECTORY_FILE) 386 if err == nil { 387 f.Close() 388 } else { 389 err = &os.PathError{Op: "mkdir", Path: filepath.Join(root.Name(), path), Err: err} 390 } 391 return err 392} 393 394// lstatRelative performs a stat operation on a file relative to a root, failing 395// if any intermediate path components are reparse points. 396func lstatRelative(path string, root *os.File) (os.FileInfo, error) { 397 f, err := openRelativeInternal( 398 path, 399 root, 400 _FILE_READ_ATTRIBUTES, 401 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 402 _FILE_OPEN, 403 _FILE_OPEN_REPARSE_POINT) 404 if err != nil { 405 return nil, &os.PathError{Op: "stat", Path: filepath.Join(root.Name(), path), Err: err} 406 } 407 defer f.Close() 408 return f.Stat() 409} 410 411// ensureNotReparsePointRelative validates that a given file (relative to a 412// root) and all intermediate path components are not a reparse points. 413func ensureNotReparsePointRelative(path string, root *os.File) error { 414 // Perform an open with OBJ_DONT_REPARSE but without specifying FILE_OPEN_REPARSE_POINT. 415 f, err := openRelative( 416 path, 417 root, 418 0, 419 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 420 _FILE_OPEN, 421 0) 422 if err != nil { 423 return err 424 } 425 f.Close() 426 return nil 427} 428