cmdname.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // Copyright (c) 2020 Tailscale Inc & 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. //go:build !ios
  5. // +build !ios
  6. package version
  7. import (
  8. "bytes"
  9. "encoding/hex"
  10. "errors"
  11. "io"
  12. "os"
  13. "path"
  14. "path/filepath"
  15. "strings"
  16. )
  17. // CmdName returns either the base name of the current binary
  18. // using os.Executable. If os.Executable fails (it shouldn't), then
  19. // "cmd" is returned.
  20. func CmdName() string {
  21. e, err := os.Executable()
  22. if err != nil {
  23. return "cmd"
  24. }
  25. return cmdName(e)
  26. }
  27. func cmdName(exe string) string {
  28. // fallbackName, the lowercase basename of the executable, is what we return if
  29. // we can't find the Go module metadata embedded in the file.
  30. fallbackName := filepath.Base(strings.TrimSuffix(strings.ToLower(exe), ".exe"))
  31. var ret string
  32. info, err := findModuleInfo(exe)
  33. if err != nil {
  34. return fallbackName
  35. }
  36. // v is like:
  37. // "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....
  38. for _, line := range strings.Split(info, "\n") {
  39. if strings.HasPrefix(line, "path\t") {
  40. goPkg := strings.TrimPrefix(line, "path\t") // like "tailscale.com/cmd/tailscale"
  41. ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath
  42. break
  43. }
  44. }
  45. if strings.HasPrefix(ret, "wg") && fallbackName == "tailscale-ipn" {
  46. // The tailscale-ipn.exe binary for internal build system packaging reasons
  47. // has a path of "tailscale.io/win/wg64", "tailscale.io/win/wg32", etc.
  48. // Ignore that name and use "tailscale-ipn" instead.
  49. return fallbackName
  50. }
  51. if ret == "" {
  52. return fallbackName
  53. }
  54. return ret
  55. }
  56. // findModuleInfo returns the Go module info from the executable file.
  57. func findModuleInfo(file string) (s string, err error) {
  58. f, err := os.Open(file)
  59. if err != nil {
  60. return "", err
  61. }
  62. defer f.Close()
  63. // Scan through f until we find infoStart.
  64. buf := make([]byte, 65536)
  65. start, err := findOffset(f, buf, infoStart)
  66. if err != nil {
  67. return "", err
  68. }
  69. start += int64(len(infoStart))
  70. // Seek to the end of infoStart and scan for infoEnd.
  71. _, err = f.Seek(start, io.SeekStart)
  72. if err != nil {
  73. return "", err
  74. }
  75. end, err := findOffset(f, buf, infoEnd)
  76. if err != nil {
  77. return "", err
  78. }
  79. length := end - start
  80. // As of Aug 2021, tailscaled's mod info was about 2k.
  81. if length > int64(len(buf)) {
  82. return "", errors.New("mod info too large")
  83. }
  84. // We have located modinfo. Read it into buf.
  85. buf = buf[:length]
  86. _, err = f.Seek(start, io.SeekStart)
  87. if err != nil {
  88. return "", err
  89. }
  90. _, err = io.ReadFull(f, buf)
  91. if err != nil {
  92. return "", err
  93. }
  94. return string(buf), nil
  95. }
  96. // findOffset finds the absolute offset of needle in f,
  97. // starting at f's current read position,
  98. // using temporary buffer buf.
  99. func findOffset(f *os.File, buf, needle []byte) (int64, error) {
  100. for {
  101. // Fill buf and look within it.
  102. n, err := f.Read(buf)
  103. if err != nil {
  104. return -1, err
  105. }
  106. i := bytes.Index(buf[:n], needle)
  107. if i < 0 {
  108. // Not found. Rewind a little bit in case we happened to end halfway through needle.
  109. rewind, err := f.Seek(int64(-len(needle)), io.SeekCurrent)
  110. if err != nil {
  111. return -1, err
  112. }
  113. // If we're at EOF and rewound exactly len(needle) bytes, return io.EOF.
  114. _, err = f.ReadAt(buf[:1], rewind+int64(len(needle)))
  115. if err == io.EOF {
  116. return -1, err
  117. }
  118. continue
  119. }
  120. // Found! Figure out exactly where.
  121. cur, err := f.Seek(0, io.SeekCurrent)
  122. if err != nil {
  123. return -1, err
  124. }
  125. return cur - int64(n) + int64(i), nil
  126. }
  127. }
  128. // These constants are taken from rsc.io/goversion.
  129. var (
  130. infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
  131. infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
  132. )