isatty_windows.go (3370B)
1 //go:build windows && !appengine 2 // +build windows,!appengine 3 4 package isatty 5 6 import ( 7 "errors" 8 "strings" 9 "syscall" 10 "unicode/utf16" 11 "unsafe" 12 ) 13 14 const ( 15 objectNameInfo uintptr = 1 16 fileNameInfo = 2 17 fileTypePipe = 3 18 ) 19 20 var ( 21 kernel32 = syscall.NewLazyDLL("kernel32.dll") 22 ntdll = syscall.NewLazyDLL("ntdll.dll") 23 procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 24 procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") 25 procGetFileType = kernel32.NewProc("GetFileType") 26 procNtQueryObject = ntdll.NewProc("NtQueryObject") 27 ) 28 29 func init() { 30 // Check if GetFileInformationByHandleEx is available. 31 if procGetFileInformationByHandleEx.Find() != nil { 32 procGetFileInformationByHandleEx = nil 33 } 34 } 35 36 // IsTerminal return true if the file descriptor is terminal. 37 func IsTerminal(fd uintptr) bool { 38 var st uint32 39 r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) 40 return r != 0 && e == 0 41 } 42 43 // Check pipe name is used for cygwin/msys2 pty. 44 // Cygwin/MSYS2 PTY has a name like: 45 // \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master 46 func isCygwinPipeName(name string) bool { 47 token := strings.Split(name, "-") 48 if len(token) < 5 { 49 return false 50 } 51 52 if token[0] != `\msys` && 53 token[0] != `\cygwin` && 54 token[0] != `\Device\NamedPipe\msys` && 55 token[0] != `\Device\NamedPipe\cygwin` { 56 return false 57 } 58 59 if token[1] == "" { 60 return false 61 } 62 63 if !strings.HasPrefix(token[2], "pty") { 64 return false 65 } 66 67 if token[3] != `from` && token[3] != `to` { 68 return false 69 } 70 71 if token[4] != "master" { 72 return false 73 } 74 75 return true 76 } 77 78 // getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler 79 // since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion 80 // guys are using Windows XP, this is a workaround for those guys, it will also work on system from 81 // Windows vista to 10 82 // see https://stackoverflow.com/a/18792477 for details 83 func getFileNameByHandle(fd uintptr) (string, error) { 84 if procNtQueryObject == nil { 85 return "", errors.New("ntdll.dll: NtQueryObject not supported") 86 } 87 88 var buf [4 + syscall.MAX_PATH]uint16 89 var result int 90 r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5, 91 fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0) 92 if r != 0 { 93 return "", e 94 } 95 return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil 96 } 97 98 // IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 99 // terminal. 100 func IsCygwinTerminal(fd uintptr) bool { 101 if procGetFileInformationByHandleEx == nil { 102 name, err := getFileNameByHandle(fd) 103 if err != nil { 104 return false 105 } 106 return isCygwinPipeName(name) 107 } 108 109 // Cygwin/msys's pty is a pipe. 110 ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) 111 if ft != fileTypePipe || e != 0 { 112 return false 113 } 114 115 var buf [2 + syscall.MAX_PATH]uint16 116 r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 117 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), 118 uintptr(len(buf)*2), 0, 0) 119 if r == 0 || e != 0 { 120 return false 121 } 122 123 l := *(*uint32)(unsafe.Pointer(&buf)) 124 return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) 125 }