isatty_windows.go 3.5 KB

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