github.com/pubgo/xprocess@v0.1.11/xprocess_isatty/isatty_windows.go (about)

     1  // +build windows
     2  // +build !appengine
     3  
     4  package xprocess_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 avilable 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  }