|
|
@@ -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")
|
|
|
+)
|