github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/agent/probe.go (about) 1 package agent 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "unicode/utf8" 8 9 "github.com/mutagen-io/mutagen/pkg/environment" 10 "github.com/mutagen-io/mutagen/pkg/prompting" 11 ) 12 13 // unameSToGOOS maps uname -s output values to their corresponding GOOS values. 14 // Although some Windows environments (Cygwin, MSYS, and MinGW) support uname, 15 // their values are handled by unameSIsWindowsPosix because they are so varied 16 // (their value depends on the POSIX environment and its version, the system 17 // architecture, and the NT kernel version). 18 var unameSToGOOS = map[string]string{ 19 "AIX": "aix", 20 "Darwin": "darwin", 21 "DragonFly": "dragonfly", 22 "FreeBSD": "freebsd", 23 "Linux": "linux", 24 "NetBSD": "netbsd", 25 "OpenBSD": "openbsd", 26 "SunOS": "solaris", 27 // TODO: Add more obscure uname -s values as necessary, e.g. 28 // debian/kFreeBSD, which returns "GNU/kFreeBSD". 29 } 30 31 // unameSIsWindowsPosix determines whether or not a uname -s output value 32 // represents a Windows POSIX environment. 33 func unameSIsWindowsPosix(value string) bool { 34 return strings.HasPrefix(value, "CYGWIN") || 35 strings.HasPrefix(value, "MINGW") || 36 strings.HasPrefix(value, "MSYS") 37 } 38 39 // unameMToGOARCH maps uname -m output values to their corresponding GOARCH 40 // values. 41 var unameMToGOARCH = map[string]string{ 42 "i386": "386", 43 "i486": "386", 44 "i586": "386", 45 "i686": "386", 46 "x86_64": "amd64", 47 "amd64": "amd64", 48 "armv5l": "arm", 49 "armv6l": "arm", 50 "armv7l": "arm", 51 "armv8l": "arm64", 52 "aarch64": "arm64", 53 "arm64": "arm64", 54 "mips": "mips", 55 "mipsel": "mipsle", 56 "mips64": "mips64", 57 "mips64el": "mips64le", 58 "ppc64": "ppc64", 59 "ppc64le": "ppc64le", 60 "riscv64": "riscv64", 61 "s390x": "s390x", 62 // TODO: Add any more obscure uname -m variations that we might encounter. 63 } 64 65 // osEnvToGOOS maps the value of the "OS" environment variable on Windows to the 66 // corresponding GOOS. There's only one supported value, but we keep things this 67 // way for symmetry and extensibility. 68 var osEnvToGOOS = map[string]string{ 69 "Windows_NT": "windows", 70 } 71 72 // processorArchitectureEnvToGOARCH maps the value of the 73 // "PROCESSOR_ARCHITECTURE" environment variable on Windows to the corresponding 74 // GOARCH. 75 var processorArchitectureEnvToGOARCH = map[string]string{ 76 "x86": "386", 77 "AMD64": "amd64", 78 "ARM": "arm", 79 "ARM64": "arm64", 80 // TODO: Add IA64 (that's the key) if Go ever supports Itanium, though 81 // they've pretty much stated that this will never happen: 82 // https://groups.google.com/forum/#!topic/golang-nuts/RgGF1Dudym4 83 } 84 85 // probePOSIX performs platform probing over an agent transport, working under 86 // the assumption that the remote system is a POSIX system. 87 func probePOSIX(transport Transport) (string, string, error) { 88 // Try to invoke uname and print kernel and machine name. 89 unameSMBytes, err := output(transport, "uname -s -m") 90 if err != nil { 91 return "", "", fmt.Errorf("unable to invoke uname: %w", err) 92 } else if !utf8.Valid(unameSMBytes) { 93 return "", "", errors.New("remote output is not UTF-8 encoded") 94 } 95 96 // Parse uname output. 97 unameSM := strings.Split(strings.TrimSpace(string(unameSMBytes)), " ") 98 if len(unameSM) != 2 { 99 return "", "", errors.New("invalid uname output") 100 } 101 unameS := unameSM[0] 102 unameM := unameSM[1] 103 104 // Translate GOOS. Windows POSIX systems typically include their NT version 105 // number in their uname -s output, so we have to handle those specially. 106 var goos string 107 var ok bool 108 if unameSIsWindowsPosix(unameS) { 109 goos = "windows" 110 } else if goos, ok = unameSToGOOS[unameS]; !ok { 111 return "", "", errors.New("unknown platform") 112 } 113 114 // Translate GOARCH. On AIX systems, uname -m returns the machine's serial 115 // number, but modern AIX only runs on 64-bit PowerPC anyway (and that's 116 // all we support), so we set the architecture directly. 117 var goarch string 118 if goos == "aix" { 119 goarch = "ppc64" 120 } else if goarch, ok = unameMToGOARCH[unameM]; !ok { 121 return "", "", errors.New("unknown architecture") 122 } 123 124 // Success. 125 return goos, goarch, nil 126 } 127 128 // probeWindows performs platform probing over an agent transport, working under 129 // the assumption that the remote system is a Windows system. 130 func probeWindows(transport Transport) (string, string, error) { 131 // Attempt to dump the remote environment. 132 outputBytes, err := output(transport, "cmd.exe /c set") 133 if err != nil { 134 return "", "", fmt.Errorf("unable to invoke remote environment printing: %w", err) 135 } else if !utf8.Valid(outputBytes) { 136 return "", "", errors.New("remote output is not UTF-8 encoded") 137 } 138 139 // Parse the output block into a series of KEY=value specifications. 140 environment := environment.ParseBlock(string(outputBytes)) 141 142 // Extract the OS and PROCESSOR_ARCHITECTURE environment variables. 143 var os, processorArchitecture string 144 for _, e := range environment { 145 if strings.HasPrefix(e, "OS=") { 146 os = e[3:] 147 } else if strings.HasPrefix(e, "PROCESSOR_ARCHITECTURE=") { 148 processorArchitecture = e[23:] 149 } 150 } 151 152 // Translate to GOOS. 153 goos, ok := osEnvToGOOS[os] 154 if !ok { 155 return "", "", errors.New("unknown platform") 156 } 157 158 // Translate to GOARCH. 159 goarch, ok := processorArchitectureEnvToGOARCH[processorArchitecture] 160 if !ok { 161 return "", "", errors.New("unknown architecture") 162 } 163 164 // Success. 165 return goos, goarch, nil 166 } 167 168 // probe attempts to identify the properties of the target platform (namely 169 // GOOS, GOARCH, and whether or not it's a POSIX environment (which it might be 170 // even on Windows)) using the specified transport. 171 func probe(transport Transport, prompter string) (string, string, bool, error) { 172 // Attempt to probe for a POSIX platform. This might apply to certain 173 // Windows environments as well. 174 if err := prompting.Message(prompter, "Probing endpoint (POSIX)..."); err != nil { 175 return "", "", false, fmt.Errorf("unable to message prompter: %w", err) 176 } 177 if goos, goarch, err := probePOSIX(transport); err == nil { 178 return goos, goarch, true, nil 179 } 180 181 // If that fails, attempt a Windows fallback. 182 if err := prompting.Message(prompter, "Probing endpoint (Windows)..."); err != nil { 183 return "", "", false, fmt.Errorf("unable to message prompter: %w", err) 184 } 185 if goos, goarch, err := probeWindows(transport); err == nil { 186 return goos, goarch, false, nil 187 } 188 189 // Failure. 190 return "", "", false, errors.New("exhausted probing methods") 191 }