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