Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/go/internal/version/version.go

Documentation: cmd/go/internal/version

     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  	"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 // break init cycle
    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  		// 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.
    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  // isExe reports whether the file should be considered executable.
   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  // 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.
   118  func 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).
   165  var buildInfoMagic = []byte("\xff Go buildinf:")
   166  
   167  // findVers finds and returns the Go version and module version information
   168  // in the executable x.
   169  func 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.
   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