| |
| |
| |
|
|
| package windows |
|
|
| import ( |
| "internal/oserror" |
| "runtime" |
| "structs" |
| "syscall" |
| "unsafe" |
| ) |
|
|
| |
| const ( |
| O_DIRECTORY = 0x04000 |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const ( |
| O_NOFOLLOW_ANY = 0x200000000 |
| O_WRITE_ATTRS = 0x800000000 |
| ) |
|
|
| func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ syscall.Handle, e1 error) { |
| if len(name) == 0 { |
| return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND |
| } |
|
|
| var access, options uint32 |
| |
| fileFlags := uint32(flag) & FileFlagsMask |
| if fileFlags&^ValidFileFlagsMask != 0 { |
| return syscall.InvalidHandle, oserror.ErrInvalid |
| } |
| if fileFlags&O_FILE_FLAG_OVERLAPPED == 0 { |
| options |= FILE_SYNCHRONOUS_IO_NONALERT |
| } |
| if fileFlags&O_FILE_FLAG_DELETE_ON_CLOSE != 0 { |
| access |= DELETE |
| } |
| setOptionFlag := func(ntFlag, win32Flag uint32) { |
| if fileFlags&win32Flag != 0 { |
| options |= ntFlag |
| } |
| } |
| setOptionFlag(FILE_NO_INTERMEDIATE_BUFFERING, O_FILE_FLAG_NO_BUFFERING) |
| setOptionFlag(FILE_WRITE_THROUGH, O_FILE_FLAG_WRITE_THROUGH) |
| setOptionFlag(FILE_SEQUENTIAL_ONLY, O_FILE_FLAG_SEQUENTIAL_SCAN) |
| setOptionFlag(FILE_RANDOM_ACCESS, O_FILE_FLAG_RANDOM_ACCESS) |
| setOptionFlag(FILE_OPEN_FOR_BACKUP_INTENT, O_FILE_FLAG_BACKUP_SEMANTICS) |
| setOptionFlag(FILE_SESSION_AWARE, O_FILE_FLAG_SESSION_AWARE) |
| setOptionFlag(FILE_DELETE_ON_CLOSE, O_FILE_FLAG_DELETE_ON_CLOSE) |
| setOptionFlag(FILE_OPEN_NO_RECALL, O_FILE_FLAG_OPEN_NO_RECALL) |
| setOptionFlag(FILE_OPEN_REPARSE_POINT, O_FILE_FLAG_OPEN_REPARSE_POINT) |
|
|
| switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { |
| case syscall.O_RDONLY: |
| |
| access |= FILE_GENERIC_READ |
| case syscall.O_WRONLY: |
| access |= FILE_GENERIC_WRITE |
| options |= FILE_NON_DIRECTORY_FILE |
| case syscall.O_RDWR: |
| access |= FILE_GENERIC_READ | FILE_GENERIC_WRITE |
| options |= FILE_NON_DIRECTORY_FILE |
| default: |
| |
| |
| access |= SYNCHRONIZE |
| } |
| if flag&syscall.O_CREAT != 0 { |
| access |= FILE_GENERIC_WRITE |
| } |
| if fileFlags&O_FILE_FLAG_NO_BUFFERING != 0 { |
| |
| access &^= FILE_APPEND_DATA |
| } |
| if flag&syscall.O_APPEND != 0 { |
| access |= FILE_APPEND_DATA |
| |
| |
| if flag&syscall.O_TRUNC == 0 { |
| access &^= FILE_WRITE_DATA |
| } |
| } |
| if flag&O_DIRECTORY != 0 { |
| options |= FILE_DIRECTORY_FILE |
| access |= FILE_LIST_DIRECTORY |
| } |
| if flag&syscall.O_SYNC != 0 { |
| options |= FILE_WRITE_THROUGH |
| } |
| if flag&O_WRITE_ATTRS != 0 { |
| access |= FILE_WRITE_ATTRIBUTES |
| } |
| |
| access |= STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_READ_EA |
|
|
| objAttrs := &OBJECT_ATTRIBUTES{} |
| if flag&O_NOFOLLOW_ANY != 0 { |
| objAttrs.Attributes |= OBJ_DONT_REPARSE |
| } |
| if flag&syscall.O_CLOEXEC == 0 { |
| objAttrs.Attributes |= OBJ_INHERIT |
| } |
| if fileFlags&O_FILE_FLAG_POSIX_SEMANTICS == 0 { |
| objAttrs.Attributes |= OBJ_CASE_INSENSITIVE |
| } |
| if err := objAttrs.init(dirfd, name); err != nil { |
| return syscall.InvalidHandle, err |
| } |
|
|
| |
| |
| |
| |
| |
| var disposition uint32 |
| switch { |
| case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): |
| disposition = FILE_CREATE |
| options |= FILE_OPEN_REPARSE_POINT |
| case flag&syscall.O_CREAT == syscall.O_CREAT: |
| disposition = FILE_OPEN_IF |
| default: |
| disposition = FILE_OPEN |
| } |
|
|
| fileAttrs := uint32(FILE_ATTRIBUTE_NORMAL) |
| if perm&syscall.S_IWRITE == 0 { |
| fileAttrs = FILE_ATTRIBUTE_READONLY |
| } |
|
|
| var h syscall.Handle |
| err := NtCreateFile( |
| &h, |
| SYNCHRONIZE|access, |
| objAttrs, |
| &IO_STATUS_BLOCK{}, |
| nil, |
| fileAttrs, |
| FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, |
| disposition, |
| FILE_OPEN_FOR_BACKUP_INTENT|options, |
| nil, |
| 0, |
| ) |
| if err != nil { |
| return h, ntCreateFileError(err, flag) |
| } |
|
|
| if flag&syscall.O_TRUNC != 0 { |
| err = syscall.Ftruncate(h, 0) |
| if err == ERROR_INVALID_PARAMETER { |
| |
| |
| |
| if t, err1 := syscall.GetFileType(h); err1 == nil && (t == syscall.FILE_TYPE_PIPE || t == syscall.FILE_TYPE_CHAR) { |
| err = nil |
| } |
| } |
| if err != nil { |
| syscall.CloseHandle(h) |
| return syscall.InvalidHandle, err |
| } |
| } |
|
|
| return h, nil |
| } |
|
|
| |
| func ntCreateFileError(err error, flag uint64) error { |
| s, ok := err.(NTStatus) |
| if !ok { |
| |
| return err |
| } |
| switch s { |
| case STATUS_REPARSE_POINT_ENCOUNTERED: |
| return syscall.ELOOP |
| case STATUS_NOT_A_DIRECTORY: |
| |
| |
| |
| |
| |
| |
| |
| |
| if flag&O_DIRECTORY != 0 { |
| return syscall.ENOTDIR |
| } |
| case STATUS_FILE_IS_A_DIRECTORY: |
| return syscall.EISDIR |
| case STATUS_OBJECT_NAME_COLLISION: |
| return syscall.EEXIST |
| } |
| return s.Errno() |
| } |
|
|
| func Mkdirat(dirfd syscall.Handle, name string, mode uint32) error { |
| objAttrs := &OBJECT_ATTRIBUTES{} |
| if err := objAttrs.init(dirfd, name); err != nil { |
| return err |
| } |
| var h syscall.Handle |
| err := NtCreateFile( |
| &h, |
| FILE_GENERIC_READ, |
| objAttrs, |
| &IO_STATUS_BLOCK{}, |
| nil, |
| syscall.FILE_ATTRIBUTE_NORMAL, |
| syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
| FILE_CREATE, |
| FILE_DIRECTORY_FILE, |
| nil, |
| 0, |
| ) |
| if err != nil { |
| return ntCreateFileError(err, 0) |
| } |
| syscall.CloseHandle(h) |
| return nil |
| } |
|
|
| func Deleteat(dirfd syscall.Handle, name string, options uint32) error { |
| if name == "." { |
| |
| |
| return syscall.EINVAL |
| } |
| objAttrs := &OBJECT_ATTRIBUTES{} |
| if err := objAttrs.init(dirfd, name); err != nil { |
| return err |
| } |
| var h syscall.Handle |
| err := NtOpenFile( |
| &h, |
| SYNCHRONIZE|FILE_READ_ATTRIBUTES|DELETE, |
| objAttrs, |
| &IO_STATUS_BLOCK{}, |
| FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, |
| FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options, |
| ) |
| if err != nil { |
| return ntCreateFileError(err, 0) |
| } |
| defer syscall.CloseHandle(h) |
|
|
| if TestDeleteatFallback { |
| return deleteatFallback(h) |
| } |
|
|
| const FileDispositionInformationEx = 64 |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| err = NtSetInformationFile( |
| h, |
| &IO_STATUS_BLOCK{}, |
| unsafe.Pointer(&FILE_DISPOSITION_INFORMATION_EX{ |
| Flags: FILE_DISPOSITION_DELETE | |
| FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK | |
| FILE_DISPOSITION_POSIX_SEMANTICS | |
| |
| |
| |
| FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE, |
| }), |
| uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION_EX{})), |
| FileDispositionInformationEx, |
| ) |
| switch err { |
| case nil: |
| return nil |
| case STATUS_INVALID_INFO_CLASS, |
| STATUS_INVALID_PARAMETER, |
| STATUS_NOT_SUPPORTED: |
| return deleteatFallback(h) |
| default: |
| return err.(NTStatus).Errno() |
| } |
| } |
|
|
| |
| |
| var TestDeleteatFallback bool |
|
|
| |
| |
| |
| func deleteatFallback(h syscall.Handle) error { |
| var data syscall.ByHandleFileInformation |
| if err := syscall.GetFileInformationByHandle(h, &data); err == nil && data.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { |
| |
| |
| wh, err := ReOpenFile(h, |
| FILE_WRITE_ATTRIBUTES, |
| FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, |
| syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, |
| ) |
| if err != nil { |
| return err |
| } |
| err = SetFileInformationByHandle( |
| wh, |
| FileBasicInfo, |
| unsafe.Pointer(&FILE_BASIC_INFO{ |
| FileAttributes: data.FileAttributes &^ FILE_ATTRIBUTE_READONLY, |
| }), |
| uint32(unsafe.Sizeof(FILE_BASIC_INFO{})), |
| ) |
| syscall.CloseHandle(wh) |
| if err != nil { |
| return err |
| } |
| } |
|
|
| return SetFileInformationByHandle( |
| h, |
| FileDispositionInfo, |
| unsafe.Pointer(&FILE_DISPOSITION_INFO{ |
| DeleteFile: true, |
| }), |
| uint32(unsafe.Sizeof(FILE_DISPOSITION_INFO{})), |
| ) |
| } |
|
|
| func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error { |
| objAttrs := &OBJECT_ATTRIBUTES{} |
| if err := objAttrs.init(olddirfd, oldpath); err != nil { |
| return err |
| } |
| var h syscall.Handle |
| err := NtOpenFile( |
| &h, |
| SYNCHRONIZE|DELETE, |
| objAttrs, |
| &IO_STATUS_BLOCK{}, |
| FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, |
| FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT, |
| ) |
| if err != nil { |
| return ntCreateFileError(err, 0) |
| } |
| defer syscall.CloseHandle(h) |
|
|
| renameInfoEx := FILE_RENAME_INFORMATION_EX{ |
| Flags: FILE_RENAME_REPLACE_IF_EXISTS | |
| FILE_RENAME_POSIX_SEMANTICS, |
| RootDirectory: newdirfd, |
| } |
| p16, err := syscall.UTF16FromString(newpath) |
| if err != nil { |
| return err |
| } |
| if len(p16) > len(renameInfoEx.FileName) { |
| return syscall.EINVAL |
| } |
| copy(renameInfoEx.FileName[:], p16) |
| renameInfoEx.FileNameLength = uint32((len(p16) - 1) * 2) |
|
|
| const ( |
| FileRenameInformation = 10 |
| FileRenameInformationEx = 65 |
| ) |
| err = NtSetInformationFile( |
| h, |
| &IO_STATUS_BLOCK{}, |
| unsafe.Pointer(&renameInfoEx), |
| uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION_EX{})), |
| FileRenameInformationEx, |
| ) |
| if err == nil { |
| return nil |
| } |
|
|
| |
| |
| |
| |
| |
| renameInfo := FILE_RENAME_INFORMATION{ |
| ReplaceIfExists: true, |
| RootDirectory: newdirfd, |
| } |
| copy(renameInfo.FileName[:], p16) |
| renameInfo.FileNameLength = renameInfoEx.FileNameLength |
|
|
| err = NtSetInformationFile( |
| h, |
| &IO_STATUS_BLOCK{}, |
| unsafe.Pointer(&renameInfo), |
| uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION{})), |
| FileRenameInformation, |
| ) |
| if st, ok := err.(NTStatus); ok { |
| return st.Errno() |
| } |
| return err |
| } |
|
|
| func Linkat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error { |
| objAttrs := &OBJECT_ATTRIBUTES{} |
| if err := objAttrs.init(olddirfd, oldpath); err != nil { |
| return err |
| } |
| var h syscall.Handle |
| err := NtOpenFile( |
| &h, |
| SYNCHRONIZE|FILE_WRITE_ATTRIBUTES, |
| objAttrs, |
| &IO_STATUS_BLOCK{}, |
| FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, |
| FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT, |
| ) |
| if err != nil { |
| return ntCreateFileError(err, 0) |
| } |
| defer syscall.CloseHandle(h) |
|
|
| linkInfo := FILE_LINK_INFORMATION{ |
| RootDirectory: newdirfd, |
| } |
| p16, err := syscall.UTF16FromString(newpath) |
| if err != nil { |
| return err |
| } |
| if len(p16) > len(linkInfo.FileName) { |
| return syscall.EINVAL |
| } |
| copy(linkInfo.FileName[:], p16) |
| linkInfo.FileNameLength = uint32((len(p16) - 1) * 2) |
|
|
| const ( |
| FileLinkInformation = 11 |
| ) |
| err = NtSetInformationFile( |
| h, |
| &IO_STATUS_BLOCK{}, |
| unsafe.Pointer(&linkInfo), |
| uint32(unsafe.Sizeof(FILE_LINK_INFORMATION{})), |
| FileLinkInformation, |
| ) |
| if st, ok := err.(NTStatus); ok { |
| return st.Errno() |
| } |
| return err |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| type SymlinkatFlags uint |
|
|
| const ( |
| SYMLINKAT_DIRECTORY = SymlinkatFlags(1 << iota) |
| SYMLINKAT_RELATIVE |
| ) |
|
|
| func Symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error { |
| |
| |
| |
| |
| |
| return withPrivilege("SeCreateSymbolicLinkPrivilege", func() error { |
| return symlinkat(oldname, newdirfd, newname, flags) |
| }) |
| } |
|
|
| func symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error { |
| oldnameu16, err := syscall.UTF16FromString(oldname) |
| if err != nil { |
| return err |
| } |
| oldnameu16 = oldnameu16[:len(oldnameu16)-1] |
|
|
| var options uint32 |
| if flags&SYMLINKAT_DIRECTORY != 0 { |
| options |= FILE_DIRECTORY_FILE |
| } else { |
| options |= FILE_NON_DIRECTORY_FILE |
| } |
|
|
| objAttrs := &OBJECT_ATTRIBUTES{} |
| if err := objAttrs.init(newdirfd, newname); err != nil { |
| return err |
| } |
| var h syscall.Handle |
| err = NtCreateFile( |
| &h, |
| SYNCHRONIZE|FILE_WRITE_ATTRIBUTES|DELETE, |
| objAttrs, |
| &IO_STATUS_BLOCK{}, |
| nil, |
| syscall.FILE_ATTRIBUTE_NORMAL, |
| 0, |
| FILE_CREATE, |
| FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options, |
| nil, |
| 0, |
| ) |
| if err != nil { |
| return ntCreateFileError(err, 0) |
| } |
| defer syscall.CloseHandle(h) |
|
|
| |
| type reparseDataBufferT struct { |
| _ structs.HostLayout |
|
|
| ReparseTag uint32 |
| ReparseDataLength uint16 |
| Reserved uint16 |
|
|
| SubstituteNameOffset uint16 |
| SubstituteNameLength uint16 |
| PrintNameOffset uint16 |
| PrintNameLength uint16 |
| Flags uint32 |
| } |
|
|
| const ( |
| headerSize = uint16(unsafe.Offsetof(reparseDataBufferT{}.SubstituteNameOffset)) |
| bufferSize = uint16(unsafe.Sizeof(reparseDataBufferT{})) |
| ) |
|
|
| |
| rdbbuf := make([]byte, bufferSize+uint16(2*len(oldnameu16))) |
|
|
| rdb := (*reparseDataBufferT)(unsafe.Pointer(&rdbbuf[0])) |
| rdb.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK |
| rdb.ReparseDataLength = uint16(len(rdbbuf)) - uint16(headerSize) |
| rdb.SubstituteNameOffset = 0 |
| rdb.SubstituteNameLength = uint16(2 * len(oldnameu16)) |
| rdb.PrintNameOffset = 0 |
| rdb.PrintNameLength = rdb.SubstituteNameLength |
| if flags&SYMLINKAT_RELATIVE != 0 { |
| rdb.Flags = SYMLINK_FLAG_RELATIVE |
| } |
|
|
| namebuf := rdbbuf[bufferSize:] |
| copy(namebuf, unsafe.String((*byte)(unsafe.Pointer(&oldnameu16[0])), 2*len(oldnameu16))) |
|
|
| err = syscall.DeviceIoControl( |
| h, |
| FSCTL_SET_REPARSE_POINT, |
| &rdbbuf[0], |
| uint32(len(rdbbuf)), |
| nil, |
| 0, |
| nil, |
| nil) |
| if err != nil { |
| |
| const FileDispositionInformation = 13 |
| NtSetInformationFile( |
| h, |
| &IO_STATUS_BLOCK{}, |
| unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{ |
| DeleteFile: true, |
| }), |
| uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})), |
| FileDispositionInformation, |
| ) |
| return err |
| } |
|
|
| return nil |
| } |
|
|
| |
| |
| |
| func withPrivilege(privilege string, f func() error) error { |
| runtime.LockOSThread() |
| defer runtime.UnlockOSThread() |
|
|
| err := ImpersonateSelf(SecurityImpersonation) |
| if err != nil { |
| return f() |
| } |
| defer RevertToSelf() |
|
|
| curThread, err := GetCurrentThread() |
| if err != nil { |
| return f() |
| } |
| var token syscall.Token |
| err = OpenThreadToken(curThread, syscall.TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, false, &token) |
| if err != nil { |
| return f() |
| } |
| defer syscall.CloseHandle(syscall.Handle(token)) |
|
|
| privStr, err := syscall.UTF16PtrFromString(privilege) |
| if err != nil { |
| return f() |
| } |
| var tokenPriv TOKEN_PRIVILEGES |
| err = LookupPrivilegeValue(nil, privStr, &tokenPriv.Privileges[0].Luid) |
| if err != nil { |
| return f() |
| } |
|
|
| tokenPriv.PrivilegeCount = 1 |
| tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED |
| err = AdjustTokenPrivileges(token, false, &tokenPriv, 0, nil, nil) |
| if err != nil { |
| return f() |
| } |
|
|
| return f() |
| } |
|
|