gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }