2020-02-19 19:22:12 +00:00
|
|
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2021-08-05 22:42:39 +00:00
|
|
|
//go:build (go1.16 && !ios) || (!go1.16 && !darwin) || (!go1.16 && !arm64)
|
2020-12-19 10:16:44 +00:00
|
|
|
// +build go1.16,!ios !go1.16,!darwin !go1.16,!arm64
|
2020-04-07 16:28:09 +00:00
|
|
|
|
2020-02-19 19:22:12 +00:00
|
|
|
package version
|
|
|
|
|
|
|
|
import (
|
2021-08-10 00:31:29 +00:00
|
|
|
"bytes"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"io"
|
2020-02-19 19:22:12 +00:00
|
|
|
"os"
|
2020-07-30 14:59:43 +00:00
|
|
|
"path"
|
2020-07-30 06:59:06 +00:00
|
|
|
"path/filepath"
|
2020-02-19 19:22:12 +00:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// CmdName returns either the base name of the current binary
|
2020-06-08 17:30:16 +00:00
|
|
|
// using os.Executable. If os.Executable fails (it shouldn't), then
|
2020-02-19 19:22:12 +00:00
|
|
|
// "cmd" is returned.
|
|
|
|
func CmdName() string {
|
|
|
|
e, err := os.Executable()
|
|
|
|
if err != nil {
|
|
|
|
return "cmd"
|
|
|
|
}
|
2020-07-30 14:59:43 +00:00
|
|
|
|
|
|
|
// fallbackName, the lowercase basename of the executable, is what we return if
|
|
|
|
// we can't find the Go module metadata embedded in the file.
|
|
|
|
fallbackName := filepath.Base(strings.TrimSuffix(strings.ToLower(e), ".exe"))
|
|
|
|
|
2020-02-19 19:22:12 +00:00
|
|
|
var ret string
|
2021-08-10 00:31:29 +00:00
|
|
|
info, err := findModuleInfo(e)
|
2020-02-19 19:22:12 +00:00
|
|
|
if err != nil {
|
2020-07-30 14:59:43 +00:00
|
|
|
return fallbackName
|
|
|
|
}
|
|
|
|
// v is like:
|
|
|
|
// "path\ttailscale.com/cmd/tailscale\nmod\ttailscale.com\t(devel)\t\ndep\tgithub.com/apenwarr/fixconsole\tv0.0.0-20191012055117-5a9f6489cc29\th1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=\ndep\tgithub....
|
2021-08-10 00:31:29 +00:00
|
|
|
for _, line := range strings.Split(info, "\n") {
|
2020-07-30 14:59:43 +00:00
|
|
|
if strings.HasPrefix(line, "path\t") {
|
|
|
|
goPkg := strings.TrimPrefix(line, "path\t") // like "tailscale.com/cmd/tailscale"
|
|
|
|
ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath
|
|
|
|
break
|
2020-02-19 19:22:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if ret == "" {
|
2020-07-30 14:59:43 +00:00
|
|
|
return fallbackName
|
2020-02-19 19:22:12 +00:00
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
2021-08-10 00:31:29 +00:00
|
|
|
|
|
|
|
// findModuleInfo returns the Go module info from the executable file.
|
|
|
|
func findModuleInfo(file string) (s string, err error) {
|
|
|
|
f, err := os.Open(file)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
// Scan through f until we find infoStart.
|
|
|
|
buf := make([]byte, 65536)
|
|
|
|
start, err := findOffset(f, buf, infoStart)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
start += int64(len(infoStart))
|
|
|
|
// Seek to the end of infoStart and scan for infoEnd.
|
|
|
|
_, err = f.Seek(start, io.SeekStart)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
end, err := findOffset(f, buf, infoEnd)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
length := end - start
|
|
|
|
// As of Aug 2021, tailscaled's mod info was about 2k.
|
|
|
|
if length > int64(len(buf)) {
|
|
|
|
return "", errors.New("mod info too large")
|
|
|
|
}
|
|
|
|
// We have located modinfo. Read it into buf.
|
|
|
|
buf = buf[:length]
|
|
|
|
_, err = f.Seek(start, io.SeekStart)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
_, err = io.ReadFull(f, buf)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(buf), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// findOffset finds the absolute offset of needle in f,
|
|
|
|
// starting at f's current read position,
|
|
|
|
// using temporary buffer buf.
|
|
|
|
func findOffset(f *os.File, buf, needle []byte) (int64, error) {
|
|
|
|
for {
|
|
|
|
// Fill buf and look within it.
|
|
|
|
n, err := f.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
i := bytes.Index(buf[:n], needle)
|
|
|
|
if i < 0 {
|
|
|
|
// Not found. Rewind a little bit in case we happened to end halfway through needle.
|
|
|
|
rewind, err := f.Seek(int64(-len(needle)), io.SeekCurrent)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
// If we're at EOF and rewound exactly len(needle) bytes, return io.EOF.
|
|
|
|
_, err = f.ReadAt(buf[:1], rewind+int64(len(needle)))
|
|
|
|
if err == io.EOF {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Found! Figure out exactly where.
|
|
|
|
cur, err := f.Seek(0, io.SeekCurrent)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
return cur - int64(n) + int64(i), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// These constants are taken from rsc.io/goversion.
|
|
|
|
|
|
|
|
var (
|
|
|
|
infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
|
|
|
|
infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
|
|
|
|
)
|