1// Copyright 2011 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package version implements the ``go version'' command. 6package version 7 8import ( 9 "bytes" 10 "context" 11 "encoding/binary" 12 "fmt" 13 "io/fs" 14 "os" 15 "path/filepath" 16 "runtime" 17 "strings" 18 19 "cmd/go/internal/base" 20) 21 22var CmdVersion = &base.Command{ 23 UsageLine: "go version [-m] [-v] [file ...]", 24 Short: "print Go version", 25 Long: `Version prints the build information for Go executables. 26 27Go version reports the Go version used to build each of the named 28executable files. 29 30If no files are named on the command line, go version prints its own 31version information. 32 33If a directory is named, go version walks that directory, recursively, 34looking for recognized Go binaries and reporting their versions. 35By default, go version does not report unrecognized files found 36during a directory scan. The -v flag causes it to report unrecognized files. 37 38The -m flag causes go version to print each executable's embedded 39module version information, when available. In the output, the module 40information consists of multiple lines following the version line, each 41indented by a leading tab character. 42 43See also: go doc runtime/debug.BuildInfo. 44`, 45} 46 47func init() { 48 CmdVersion.Run = runVersion // break init cycle 49} 50 51var ( 52 versionM = CmdVersion.Flag.Bool("m", false, "") 53 versionV = CmdVersion.Flag.Bool("v", false, "") 54) 55 56func runVersion(ctx context.Context, cmd *base.Command, args []string) { 57 if len(args) == 0 { 58 // If any of this command's flags were passed explicitly, error 59 // out, because they only make sense with arguments. 60 // 61 // Don't error if the flags came from GOFLAGS, since that can be 62 // a reasonable use case. For example, imagine GOFLAGS=-v to 63 // turn "verbose mode" on for all Go commands, which should not 64 // break "go version". 65 if (!base.InGOFLAGS("-m") && *versionM) || (!base.InGOFLAGS("-v") && *versionV) { 66 fmt.Fprintf(os.Stderr, "go version: flags can only be used with arguments\n") 67 base.SetExitStatus(2) 68 return 69 } 70 fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) 71 return 72 } 73 74 for _, arg := range args { 75 info, err := os.Stat(arg) 76 if err != nil { 77 fmt.Fprintf(os.Stderr, "%v\n", err) 78 base.SetExitStatus(1) 79 continue 80 } 81 if info.IsDir() { 82 scanDir(arg) 83 } else { 84 scanFile(arg, info, true) 85 } 86 } 87} 88 89// scanDir scans a directory for executables to run scanFile on. 90func scanDir(dir string) { 91 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 92 if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 { 93 info, err := d.Info() 94 if err != nil { 95 if *versionV { 96 fmt.Fprintf(os.Stderr, "%s: %v\n", path, err) 97 } 98 return nil 99 } 100 scanFile(path, info, *versionV) 101 } 102 return nil 103 }) 104} 105 106// isExe reports whether the file should be considered executable. 107func isExe(file string, info fs.FileInfo) bool { 108 if runtime.GOOS == "windows" { 109 return strings.HasSuffix(strings.ToLower(file), ".exe") 110 } 111 return info.Mode().IsRegular() && info.Mode()&0111 != 0 112} 113 114// scanFile scans file to try to report the Go and module versions. 115// If mustPrint is true, scanFile will report any error reading file. 116// Otherwise (mustPrint is false, because scanFile is being called 117// by scanDir) scanFile prints nothing for non-Go executables. 118func scanFile(file string, info fs.FileInfo, mustPrint bool) { 119 if info.Mode()&fs.ModeSymlink != 0 { 120 // Accept file symlinks only. 121 i, err := os.Stat(file) 122 if err != nil || !i.Mode().IsRegular() { 123 if mustPrint { 124 fmt.Fprintf(os.Stderr, "%s: symlink\n", file) 125 } 126 return 127 } 128 info = i 129 } 130 131 if !isExe(file, info) { 132 if mustPrint { 133 fmt.Fprintf(os.Stderr, "%s: not executable file\n", file) 134 } 135 return 136 } 137 138 x, err := openExe(file) 139 if err != nil { 140 if mustPrint { 141 fmt.Fprintf(os.Stderr, "%s: %v\n", file, err) 142 } 143 return 144 } 145 defer x.Close() 146 147 vers, mod := findVers(x) 148 if vers == "" { 149 if mustPrint { 150 fmt.Fprintf(os.Stderr, "%s: go version not found\n", file) 151 } 152 return 153 } 154 155 fmt.Printf("%s: %s\n", file, vers) 156 if *versionM && mod != "" { 157 fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t")) 158 } 159} 160 161// The build info blob left by the linker is identified by 162// a 16-byte header, consisting of buildInfoMagic (14 bytes), 163// the binary's pointer size (1 byte), 164// and whether the binary is big endian (1 byte). 165var buildInfoMagic = []byte("\xff Go buildinf:") 166 167// findVers finds and returns the Go version and module version information 168// in the executable x. 169func findVers(x exe) (vers, mod string) { 170 // Read the first 64kB of text to find the build info blob. 171 text := x.DataStart() 172 data, err := x.ReadData(text, 64*1024) 173 if err != nil { 174 return 175 } 176 for ; !bytes.HasPrefix(data, buildInfoMagic); data = data[32:] { 177 if len(data) < 32 { 178 return 179 } 180 } 181 182 // Decode the blob. 183 ptrSize := int(data[14]) 184 bigEndian := data[15] != 0 185 var bo binary.ByteOrder 186 if bigEndian { 187 bo = binary.BigEndian 188 } else { 189 bo = binary.LittleEndian 190 } 191 var readPtr func([]byte) uint64 192 if ptrSize == 4 { 193 readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) } 194 } else { 195 readPtr = bo.Uint64 196 } 197 vers = readString(x, ptrSize, readPtr, readPtr(data[16:])) 198 if vers == "" { 199 return 200 } 201 mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:])) 202 if len(mod) >= 33 && mod[len(mod)-17] == '\n' { 203 // Strip module framing. 204 mod = mod[16 : len(mod)-16] 205 } else { 206 mod = "" 207 } 208 return 209} 210 211// readString returns the string at address addr in the executable x. 212func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string { 213 hdr, err := x.ReadData(addr, uint64(2*ptrSize)) 214 if err != nil || len(hdr) < 2*ptrSize { 215 return "" 216 } 217 dataAddr := readPtr(hdr) 218 dataLen := readPtr(hdr[ptrSize:]) 219 data, err := x.ReadData(dataAddr, dataLen) 220 if err != nil || uint64(len(data)) < dataLen { 221 return "" 222 } 223 return string(data) 224} 225