mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	Updates #7123 Updates #5309 Change-Id: I90bcd87a2fb85a91834a0dd4be6e03db08438672 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			140 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| //go:build !ios
 | |
| 
 | |
| package version
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/hex"
 | |
| 	"errors"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // CmdName returns either the base name of the current binary
 | |
| // using os.Executable. If os.Executable fails (it shouldn't), then
 | |
| // "cmd" is returned.
 | |
| func CmdName() string {
 | |
| 	e, err := os.Executable()
 | |
| 	if err != nil {
 | |
| 		return "cmd"
 | |
| 	}
 | |
| 	return cmdName(e)
 | |
| }
 | |
| 
 | |
| func cmdName(exe string) string {
 | |
| 	// 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(exe), ".exe"))
 | |
| 
 | |
| 	var ret string
 | |
| 	info, err := findModuleInfo(exe)
 | |
| 	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(info, "\n") {
 | |
| 		if goPkg, ok := strings.CutPrefix(line, "path\t"); ok { // like "tailscale.com/cmd/tailscale"
 | |
| 			ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if strings.HasPrefix(ret, "wg") && fallbackName == "tailscale-ipn" {
 | |
| 		// The tailscale-ipn.exe binary for internal build system packaging reasons
 | |
| 		// has a path of "tailscale.io/win/wg64", "tailscale.io/win/wg32", etc.
 | |
| 		// Ignore that name and use "tailscale-ipn" instead.
 | |
| 		return fallbackName
 | |
| 	}
 | |
| 	if ret == "" {
 | |
| 		return fallbackName
 | |
| 	}
 | |
| 	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")
 | |
| )
 |