version: remove rsc.io/goversion dependency
rsc.io/goversion is really expensive. Running version.ReadExe on tailscaled on darwin allocates 47k objects, almost 11mb. All we want is the module info. For that, all we need to do is scan through the binary looking for the magic start/end strings and then grab the bytes in between them. We can do that easily and quickly with nothing but a 64k buffer. Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
This commit is contained in:

committed by
Josh Bleecher Snyder

parent
bdb93c5942
commit
a4e19f2233
@ -8,12 +8,14 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"rsc.io/goversion/version"
|
||||
)
|
||||
|
||||
// CmdName returns either the base name of the current binary
|
||||
@ -30,13 +32,13 @@ func CmdName() string {
|
||||
fallbackName := filepath.Base(strings.TrimSuffix(strings.ToLower(e), ".exe"))
|
||||
|
||||
var ret string
|
||||
v, err := version.ReadExe(e)
|
||||
info, err := findModuleInfo(e)
|
||||
if err != nil {
|
||||
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....
|
||||
for _, line := range strings.Split(v.ModuleInfo, "\n") {
|
||||
for _, line := range strings.Split(info, "\n") {
|
||||
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
|
||||
@ -48,3 +50,84 @@ func CmdName() string {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// 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")
|
||||
)
|
||||
|
Reference in New Issue
Block a user