github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/version/version.go (about) 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. 6 package version 7 8 import ( 9 "bytes" 10 "encoding/binary" 11 "fmt" 12 "os" 13 "path/filepath" 14 "runtime" 15 "strings" 16 17 "github.com/gagliardetto/golang-go/cmd/go/not-internal/base" 18 ) 19 20 var 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 25 Go version reports the Go version used to build each of the named 26 executable files. 27 28 If no files are named on the command line, go version prints its own 29 version information. 30 31 If a directory is named, go version walks that directory, recursively, 32 looking for recognized Go binaries and reporting their versions. 33 By default, go version does not report unrecognized files found 34 during a directory scan. The -v flag causes it to report unrecognized files. 35 36 The -m flag causes go version to print each executable's embedded 37 module version information, when available. In the output, the module 38 information consists of multiple lines following the version line, each 39 indented by a leading tab character. 40 41 See also: go doc runtime/debug.BuildInfo. 42 `, 43 } 44 45 func init() { 46 CmdVersion.Run = runVersion // break init cycle 47 } 48 49 var ( 50 versionM = CmdVersion.Flag.Bool("m", false, "") 51 versionV = CmdVersion.Flag.Bool("v", false, "") 52 ) 53 54 func 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. 75 func 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. 85 func 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. 96 func 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). 142 var buildInfoMagic = []byte("\xff Go buildinf:") 143 144 // findVers finds and returns the Go version and module version information 145 // in the executable x. 146 func 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. 189 func 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 }