1
2
3
4
5
6 package version
7
8 import (
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
22 var 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
27 Go version reports the Go version used to build each of the named
28 executable files.
29
30 If no files are named on the command line, go version prints its own
31 version information.
32
33 If a directory is named, go version walks that directory, recursively,
34 looking for recognized Go binaries and reporting their versions.
35 By default, go version does not report unrecognized files found
36 during a directory scan. The -v flag causes it to report unrecognized files.
37
38 The -m flag causes go version to print each executable's embedded
39 module version information, when available. In the output, the module
40 information consists of multiple lines following the version line, each
41 indented by a leading tab character.
42
43 See also: go doc runtime/debug.BuildInfo.
44 `,
45 }
46
47 func init() {
48 CmdVersion.Run = runVersion
49 }
50
51 var (
52 versionM = CmdVersion.Flag.Bool("m", false, "")
53 versionV = CmdVersion.Flag.Bool("v", false, "")
54 )
55
56 func runVersion(ctx context.Context, cmd *base.Command, args []string) {
57 if len(args) == 0 {
58
59
60
61
62
63
64
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
90 func 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
107 func 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
115
116
117
118 func scanFile(file string, info fs.FileInfo, mustPrint bool) {
119 if info.Mode()&fs.ModeSymlink != 0 {
120
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
162
163
164
165 var buildInfoMagic = []byte("\xff Go buildinf:")
166
167
168
169 func findVers(x exe) (vers, mod string) {
170
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
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
204 mod = mod[16 : len(mod)-16]
205 } else {
206 mod = ""
207 }
208 return
209 }
210
211
212 func 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
View as plain text