| package hcsshim |
| |
| import ( |
| "errors" |
| "io" |
| "os" |
| "path/filepath" |
| "strings" |
| "syscall" |
| "unicode/utf16" |
| "unsafe" |
| |
| winio "github.com/Microsoft/go-winio" |
| ) |
| |
| //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 |
| //sys ntSetInformationFile(handle uintptr, iosb *ioStatusBlock, information uintptr, length uint32, class uint32) (status uint32) = ntdll.NtSetInformationFile |
| //sys rtlNtStatusToDosError(status uint32) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb |
| //sys localAlloc(flags uint32, size int) (ptr uintptr) = kernel32.LocalAlloc |
| //sys localFree(ptr uintptr) = kernel32.LocalFree |
| |
| type ioStatusBlock struct { |
| Status, Information uintptr |
| } |
| |
| type objectAttributes struct { |
| Length uintptr |
| RootDirectory uintptr |
| ObjectName uintptr |
| Attributes uintptr |
| SecurityDescriptor uintptr |
| SecurityQoS uintptr |
| } |
| |
| type unicodeString struct { |
| Length uint16 |
| MaximumLength uint16 |
| Buffer uintptr |
| } |
| |
| type fileLinkInformation struct { |
| ReplaceIfExists bool |
| RootDirectory uintptr |
| FileNameLength uint32 |
| FileName [1]uint16 |
| } |
| |
| type fileDispositionInformationEx struct { |
| Flags uintptr |
| } |
| |
| const ( |
| _FileLinkInformation = 11 |
| _FileDispositionInformationEx = 64 |
| |
| _FILE_READ_ATTRIBUTES = 0x0080 |
| _FILE_WRITE_ATTRIBUTES = 0x0100 |
| _DELETE = 0x10000 |
| |
| _FILE_OPEN = 1 |
| _FILE_CREATE = 2 |
| |
| _FILE_DIRECTORY_FILE = 0x00000001 |
| _FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020 |
| _FILE_DELETE_ON_CLOSE = 0x00001000 |
| _FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000 |
| _FILE_OPEN_REPARSE_POINT = 0x00200000 |
| |
| _FILE_DISPOSITION_DELETE = 0x00000001 |
| |
| _OBJ_DONT_REPARSE = 0x1000 |
| |
| _STATUS_REPARSE_POINT_ENCOUNTERED = 0xC000050B |
| ) |
| |
| func openRoot(path string) (*os.File, error) { |
| longpath, err := makeLongAbsPath(path) |
| if err != nil { |
| return nil, err |
| } |
| return winio.OpenForBackup(longpath, syscall.GENERIC_READ, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, syscall.OPEN_EXISTING) |
| } |
| |
| func ntRelativePath(path string) ([]uint16, error) { |
| path = filepath.Clean(path) |
| if strings.Contains(":", path) { |
| // Since alternate data streams must follow the file they |
| // are attached to, finding one here (out of order) is invalid. |
| return nil, errors.New("path contains invalid character `:`") |
| } |
| fspath := filepath.FromSlash(path) |
| if len(fspath) > 0 && fspath[0] == '\\' { |
| return nil, errors.New("expected relative path") |
| } |
| |
| path16 := utf16.Encode(([]rune)(fspath)) |
| if len(path16) > 32767 { |
| return nil, syscall.ENAMETOOLONG |
| } |
| |
| return path16, nil |
| } |
| |
| // openRelativeInternal opens a relative path from the given root, failing if |
| // any of the intermediate path components are reparse points. |
| func openRelativeInternal(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) { |
| var ( |
| h uintptr |
| iosb ioStatusBlock |
| oa objectAttributes |
| ) |
| |
| path16, err := ntRelativePath(path) |
| if err != nil { |
| return nil, err |
| } |
| |
| if root == nil || root.Fd() == 0 { |
| return nil, errors.New("missing root directory") |
| } |
| |
| upathBuffer := localAlloc(0, int(unsafe.Sizeof(unicodeString{}))+len(path16)*2) |
| defer localFree(upathBuffer) |
| |
| upath := (*unicodeString)(unsafe.Pointer(upathBuffer)) |
| upath.Length = uint16(len(path16) * 2) |
| upath.MaximumLength = upath.Length |
| upath.Buffer = upathBuffer + unsafe.Sizeof(*upath) |
| copy((*[32768]uint16)(unsafe.Pointer(upath.Buffer))[:], path16) |
| |
| oa.Length = unsafe.Sizeof(oa) |
| oa.ObjectName = upathBuffer |
| oa.RootDirectory = uintptr(root.Fd()) |
| oa.Attributes = _OBJ_DONT_REPARSE |
| status := ntCreateFile( |
| &h, |
| accessMask|syscall.SYNCHRONIZE, |
| &oa, |
| &iosb, |
| nil, |
| 0, |
| shareFlags, |
| createDisposition, |
| _FILE_OPEN_FOR_BACKUP_INTENT|_FILE_SYNCHRONOUS_IO_NONALERT|flags, |
| nil, |
| 0, |
| ) |
| if status != 0 { |
| return nil, rtlNtStatusToDosError(status) |
| } |
| |
| fullPath, err := makeLongAbsPath(filepath.Join(root.Name(), path)) |
| if err != nil { |
| syscall.Close(syscall.Handle(h)) |
| return nil, err |
| } |
| |
| return os.NewFile(h, fullPath), nil |
| } |
| |
| // openRelative opens a relative path from the given root, failing if |
| // any of the intermediate path components are reparse points. |
| func openRelative(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) { |
| f, err := openRelativeInternal(path, root, accessMask, shareFlags, createDisposition, flags) |
| if err != nil { |
| err = &os.PathError{Op: "open", Path: filepath.Join(root.Name(), path), Err: err} |
| } |
| return f, err |
| } |
| |
| // linkRelative creates a hard link from oldname to newname (relative to oldroot |
| // and newroot), failing if any of the intermediate path components are reparse |
| // points. |
| func linkRelative(oldname string, oldroot *os.File, newname string, newroot *os.File) error { |
| // Open the old file. |
| oldf, err := openRelativeInternal( |
| oldname, |
| oldroot, |
| syscall.FILE_WRITE_ATTRIBUTES, |
| syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
| _FILE_OPEN, |
| 0, |
| ) |
| if err != nil { |
| return &os.LinkError{Op: "link", Old: filepath.Join(oldroot.Name(), oldname), New: filepath.Join(newroot.Name(), newname), Err: err} |
| } |
| defer oldf.Close() |
| |
| // Open the parent of the new file. |
| var parent *os.File |
| parentPath := filepath.Dir(newname) |
| if parentPath != "." { |
| parent, err = openRelativeInternal( |
| parentPath, |
| newroot, |
| syscall.GENERIC_READ, |
| syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
| _FILE_OPEN, |
| _FILE_DIRECTORY_FILE) |
| if err != nil { |
| return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: err} |
| } |
| defer parent.Close() |
| |
| fi, err := winio.GetFileBasicInfo(parent) |
| if err != nil { |
| return err |
| } |
| if (fi.FileAttributes & syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 { |
| return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: rtlNtStatusToDosError(_STATUS_REPARSE_POINT_ENCOUNTERED)} |
| } |
| |
| } else { |
| parent = newroot |
| } |
| |
| // Issue an NT call to create the link. This will be safe because NT will |
| // not open any more directories to create the link, so it cannot walk any |
| // more reparse points. |
| newbase := filepath.Base(newname) |
| newbase16, err := ntRelativePath(newbase) |
| if err != nil { |
| return err |
| } |
| |
| size := int(unsafe.Offsetof(fileLinkInformation{}.FileName)) + len(newbase16)*2 |
| linkinfoBuffer := localAlloc(0, size) |
| defer localFree(linkinfoBuffer) |
| linkinfo := (*fileLinkInformation)(unsafe.Pointer(linkinfoBuffer)) |
| linkinfo.RootDirectory = parent.Fd() |
| linkinfo.FileNameLength = uint32(len(newbase16) * 2) |
| copy((*[32768]uint16)(unsafe.Pointer(&linkinfo.FileName[0]))[:], newbase16) |
| |
| var iosb ioStatusBlock |
| status := ntSetInformationFile( |
| oldf.Fd(), |
| &iosb, |
| linkinfoBuffer, |
| uint32(size), |
| _FileLinkInformation, |
| ) |
| if status != 0 { |
| return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(parent.Name(), newbase), Err: rtlNtStatusToDosError(status)} |
| } |
| |
| return nil |
| } |
| |
| // deleteOnClose marks a file to be deleted when the handle is closed. |
| func deleteOnClose(f *os.File) error { |
| disposition := fileDispositionInformationEx{Flags: _FILE_DISPOSITION_DELETE} |
| var iosb ioStatusBlock |
| status := ntSetInformationFile( |
| f.Fd(), |
| &iosb, |
| uintptr(unsafe.Pointer(&disposition)), |
| uint32(unsafe.Sizeof(disposition)), |
| _FileDispositionInformationEx, |
| ) |
| if status != 0 { |
| return rtlNtStatusToDosError(status) |
| } |
| return nil |
| } |
| |
| // clearReadOnly clears the readonly attribute on a file. |
| func clearReadOnly(f *os.File) error { |
| bi, err := winio.GetFileBasicInfo(f) |
| if err != nil { |
| return err |
| } |
| if bi.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY == 0 { |
| return nil |
| } |
| sbi := winio.FileBasicInfo{ |
| FileAttributes: bi.FileAttributes &^ syscall.FILE_ATTRIBUTE_READONLY, |
| } |
| if sbi.FileAttributes == 0 { |
| sbi.FileAttributes = syscall.FILE_ATTRIBUTE_NORMAL |
| } |
| return winio.SetFileBasicInfo(f, &sbi) |
| } |
| |
| // removeRelative removes a file or directory relative to a root, failing if any |
| // intermediate path components are reparse points. |
| func removeRelative(path string, root *os.File) error { |
| f, err := openRelativeInternal( |
| path, |
| root, |
| _FILE_READ_ATTRIBUTES|_FILE_WRITE_ATTRIBUTES|_DELETE, |
| syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
| _FILE_OPEN, |
| _FILE_OPEN_REPARSE_POINT) |
| if err == nil { |
| defer f.Close() |
| err = deleteOnClose(f) |
| if err == syscall.ERROR_ACCESS_DENIED { |
| // Maybe the file is marked readonly. Clear the bit and retry. |
| clearReadOnly(f) |
| err = deleteOnClose(f) |
| } |
| } |
| if err != nil { |
| return &os.PathError{Op: "remove", Path: filepath.Join(root.Name(), path), Err: err} |
| } |
| return nil |
| } |
| |
| // removeAllRelative removes a directory tree relative to a root, failing if any |
| // intermediate path components are reparse points. |
| func removeAllRelative(path string, root *os.File) error { |
| fi, err := lstatRelative(path, root) |
| if err != nil { |
| if os.IsNotExist(err) { |
| return nil |
| } |
| return err |
| } |
| fileAttributes := fi.Sys().(*syscall.Win32FileAttributeData).FileAttributes |
| if fileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 || fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { |
| // If this is a reparse point, it can't have children. Simple remove will do. |
| err := removeRelative(path, root) |
| if err == nil || os.IsNotExist(err) { |
| return nil |
| } |
| return err |
| } |
| |
| // It is necessary to use os.Open as Readdirnames does not work with |
| // openRelative. This is safe because the above lstatrelative fails |
| // if the target is outside the root, and we know this is not a |
| // symlink from the above FILE_ATTRIBUTE_REPARSE_POINT check. |
| fd, err := os.Open(filepath.Join(root.Name(), path)) |
| if err != nil { |
| if os.IsNotExist(err) { |
| // Race. It was deleted between the Lstat and Open. |
| // Return nil per RemoveAll's docs. |
| return nil |
| } |
| return err |
| } |
| |
| // Remove contents & return first error. |
| for { |
| names, err1 := fd.Readdirnames(100) |
| for _, name := range names { |
| err1 := removeAllRelative(path+string(os.PathSeparator)+name, root) |
| if err == nil { |
| err = err1 |
| } |
| } |
| if err1 == io.EOF { |
| break |
| } |
| // If Readdirnames returned an error, use it. |
| if err == nil { |
| err = err1 |
| } |
| if len(names) == 0 { |
| break |
| } |
| } |
| fd.Close() |
| |
| // Remove directory. |
| err1 := removeRelative(path, root) |
| if err1 == nil || os.IsNotExist(err1) { |
| return nil |
| } |
| if err == nil { |
| err = err1 |
| } |
| return err |
| } |
| |
| // mkdirRelative creates a directory relative to a root, failing if any |
| // intermediate path components are reparse points. |
| func mkdirRelative(path string, root *os.File) error { |
| f, err := openRelativeInternal( |
| path, |
| root, |
| 0, |
| syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
| _FILE_CREATE, |
| _FILE_DIRECTORY_FILE) |
| if err == nil { |
| f.Close() |
| } else { |
| err = &os.PathError{Op: "mkdir", Path: filepath.Join(root.Name(), path), Err: err} |
| } |
| return err |
| } |
| |
| // lstatRelative performs a stat operation on a file relative to a root, failing |
| // if any intermediate path components are reparse points. |
| func lstatRelative(path string, root *os.File) (os.FileInfo, error) { |
| f, err := openRelativeInternal( |
| path, |
| root, |
| _FILE_READ_ATTRIBUTES, |
| syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
| _FILE_OPEN, |
| _FILE_OPEN_REPARSE_POINT) |
| if err != nil { |
| return nil, &os.PathError{Op: "stat", Path: filepath.Join(root.Name(), path), Err: err} |
| } |
| defer f.Close() |
| return f.Stat() |
| } |
| |
| // ensureNotReparsePointRelative validates that a given file (relative to a |
| // root) and all intermediate path components are not a reparse points. |
| func ensureNotReparsePointRelative(path string, root *os.File) error { |
| // Perform an open with OBJ_DONT_REPARSE but without specifying FILE_OPEN_REPARSE_POINT. |
| f, err := openRelative( |
| path, |
| root, |
| 0, |
| syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
| _FILE_OPEN, |
| 0) |
| if err != nil { |
| return err |
| } |
| f.Close() |
| return nil |
| } |