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