1
0

orphans_linux.go 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. //go:build linux
  2. package mtproto
  3. import (
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "syscall"
  10. )
  11. // killStrayMtgProcesses terminates orphaned mtg sidecars left over from a
  12. // previous x-ui run and returns how many were killed.
  13. //
  14. // x-ui starts one mtg process per mtproto inbound outside its own lifecycle, and
  15. // on Linux a child is not guaranteed to die with the panel (there is no
  16. // kill-on-exit, unlike the Windows job object). A survivor keeps holding the
  17. // inbound port with a now-stale secret, so new clients are silently
  18. // domain-fronted to the FakeTLS domain instead of proxied to Telegram. x-ui is
  19. // the sole owner of mtg, so any process matching our binary name at startup is
  20. // an orphan and is safe to kill before we start our own.
  21. //
  22. // binaryPath is the configured mtg path (e.g. "bin/mtg-linux-amd64"); matching
  23. // is done on the executable's base name so it is independent of the bin folder
  24. // and still works after an update has deleted the binary (the running process's
  25. // /proc/<pid>/exe then reads as "<path> (deleted)", so argv[0] is used too).
  26. func killStrayMtgProcesses(binaryPath string) int {
  27. base := filepath.Base(binaryPath)
  28. if base == "" || base == "." || base == string(filepath.Separator) {
  29. return 0
  30. }
  31. self := os.Getpid()
  32. entries, err := os.ReadDir("/proc")
  33. if err != nil {
  34. return 0
  35. }
  36. killed := 0
  37. for _, e := range entries {
  38. pid, err := strconv.Atoi(e.Name())
  39. if err != nil || pid == self {
  40. continue
  41. }
  42. if procExeBase(pid) != base && cmdlineArgv0Base(pid) != base {
  43. continue
  44. }
  45. if err := syscall.Kill(pid, syscall.SIGKILL); err == nil {
  46. killed++
  47. }
  48. }
  49. return killed
  50. }
  51. // procExeBase returns the base name of /proc/<pid>/exe, or "" if unreadable.
  52. func procExeBase(pid int) string {
  53. exe, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
  54. if err != nil {
  55. return ""
  56. }
  57. return filepath.Base(exe)
  58. }
  59. // cmdlineArgv0Base returns the base name of argv[0] from /proc/<pid>/cmdline,
  60. // the reliable fallback when the binary has been replaced or exe is unreadable.
  61. func cmdlineArgv0Base(pid int) string {
  62. data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
  63. if err != nil || len(data) == 0 {
  64. return ""
  65. }
  66. argv0 := data
  67. if i := strings.IndexByte(string(data), 0); i >= 0 {
  68. argv0 = data[:i]
  69. }
  70. if len(argv0) == 0 {
  71. return ""
  72. }
  73. return filepath.Base(string(argv0))
  74. }