cmd_builder.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. package exe
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "os/user"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "syscall"
  12. "time"
  13. "github.com/leonelquinteros/gotext"
  14. "github.com/Jguer/yay/v10/pkg/settings/parser"
  15. "github.com/Jguer/yay/v10/pkg/text"
  16. )
  17. const SudoLoopDuration = 241
  18. type GitCmdBuilder interface {
  19. Runner
  20. BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd
  21. }
  22. type ICmdBuilder interface {
  23. Runner
  24. BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd
  25. BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd
  26. BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd
  27. AddMakepkgFlag(string)
  28. SetPacmanDBPath(string)
  29. SudoLoop()
  30. }
  31. type CmdBuilder struct {
  32. GitBin string
  33. GitFlags []string
  34. MakepkgFlags []string
  35. MakepkgConfPath string
  36. MakepkgBin string
  37. SudoBin string
  38. SudoFlags []string
  39. SudoLoopEnabled bool
  40. PacmanBin string
  41. PacmanConfigPath string
  42. PacmanDBPath string
  43. Runner Runner
  44. }
  45. func (c *CmdBuilder) BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd {
  46. args := make([]string, len(c.GitFlags), len(c.GitFlags)+len(extraArgs))
  47. copy(args, c.GitFlags)
  48. if dir != "" {
  49. args = append(args, "-C", dir)
  50. }
  51. if len(extraArgs) > 0 {
  52. args = append(args, extraArgs...)
  53. }
  54. cmd := exec.CommandContext(ctx, c.GitBin, args...)
  55. cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
  56. cmd = c.deElevateCommand(ctx, cmd)
  57. return cmd
  58. }
  59. func (c *CmdBuilder) AddMakepkgFlag(flag string) {
  60. c.MakepkgFlags = append(c.MakepkgFlags, flag)
  61. }
  62. func (c *CmdBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd {
  63. args := make([]string, len(c.MakepkgFlags), len(c.MakepkgFlags)+len(extraArgs))
  64. copy(args, c.MakepkgFlags)
  65. if c.MakepkgConfPath != "" {
  66. args = append(args, "--config", c.MakepkgConfPath)
  67. }
  68. if len(extraArgs) > 0 {
  69. args = append(args, extraArgs...)
  70. }
  71. cmd := exec.CommandContext(ctx, c.MakepkgBin, args...)
  72. cmd.Dir = dir
  73. cmd = c.deElevateCommand(ctx, cmd)
  74. return cmd
  75. }
  76. func (c *CmdBuilder) SetPacmanDBPath(dbPath string) {
  77. c.PacmanDBPath = dbPath
  78. }
  79. // deElevateCommand, `systemd-run` code based on pikaur.
  80. func (c *CmdBuilder) deElevateCommand(ctx context.Context, cmd *exec.Cmd) *exec.Cmd {
  81. if os.Geteuid() != 0 {
  82. return cmd
  83. }
  84. ogCaller := ""
  85. if caller := os.Getenv("SUDO_USER"); caller != "" {
  86. ogCaller = caller
  87. } else if caller := os.Getenv("DOAS_USER"); caller != "" {
  88. ogCaller = caller
  89. }
  90. if userFound, err := user.Lookup(ogCaller); err == nil {
  91. cmd.SysProcAttr = &syscall.SysProcAttr{}
  92. uid, _ := strconv.Atoi(userFound.Uid)
  93. gid, _ := strconv.Atoi(userFound.Gid)
  94. cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
  95. return cmd
  96. }
  97. cmdArgs := []string{
  98. "--service-type=oneshot",
  99. "--pipe", "--wait", "--pty",
  100. "-p", "DynamicUser=yes",
  101. "-p", "CacheDirectory=yay",
  102. "-E", "HOME=/tmp",
  103. }
  104. if cmd.Dir != "" {
  105. cmdArgs = append(cmdArgs, "-p", fmt.Sprintf("WorkingDirectory=%s", cmd.Dir))
  106. }
  107. for _, envVarName := range [...]string{"http_proxy", "https_proxy", "ftp_proxy"} {
  108. if env := os.Getenv(envVarName); env != "" {
  109. cmdArgs = append(cmdArgs, "-E", fmt.Sprintf("%s=%s", envVarName, env))
  110. }
  111. }
  112. path, err := exec.LookPath(cmd.Args[0])
  113. if err != nil {
  114. panic("path should have already been validated")
  115. }
  116. cmdArgs = append(cmdArgs, path)
  117. cmdArgs = append(cmdArgs, cmd.Args[1:]...)
  118. systemdCmd := exec.CommandContext(ctx, "systemd-run", cmdArgs...)
  119. return systemdCmd
  120. }
  121. func (c *CmdBuilder) buildPrivilegeElevatorCommand(ctx context.Context, ogArgs []string) *exec.Cmd {
  122. if c.SudoBin == "su" {
  123. return exec.CommandContext(ctx, c.SudoBin, "-c", strings.Join(ogArgs, " "))
  124. }
  125. argArr := make([]string, 0, len(c.SudoFlags)+len(ogArgs))
  126. argArr = append(argArr, c.SudoFlags...)
  127. argArr = append(argArr, ogArgs...)
  128. return exec.CommandContext(ctx, c.SudoBin, argArr...)
  129. }
  130. func (c *CmdBuilder) BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd {
  131. argArr := make([]string, 0, 32)
  132. needsRoot := args.NeedRoot(mode)
  133. argArr = append(argArr, c.PacmanBin)
  134. argArr = append(argArr, args.FormatGlobals()...)
  135. argArr = append(argArr, args.FormatArgs()...)
  136. if noConfirm {
  137. argArr = append(argArr, "--noconfirm")
  138. }
  139. argArr = append(argArr, "--config", c.PacmanConfigPath, "--")
  140. argArr = append(argArr, args.Targets...)
  141. if needsRoot {
  142. waitLock(c.PacmanDBPath)
  143. if os.Geteuid() != 0 {
  144. return c.buildPrivilegeElevatorCommand(ctx, argArr)
  145. }
  146. }
  147. return exec.CommandContext(ctx, argArr[0], argArr[1:]...)
  148. }
  149. // waitLock will lock yay checking the status of db.lck until it does not exist.
  150. func waitLock(dbPath string) {
  151. lockDBPath := filepath.Join(dbPath, "db.lck")
  152. if _, err := os.Stat(lockDBPath); err != nil {
  153. return
  154. }
  155. text.Warnln(gotext.Get("%s is present.", lockDBPath))
  156. text.Warn(gotext.Get("There may be another Pacman instance running. Waiting..."))
  157. for {
  158. time.Sleep(3 * time.Second)
  159. if _, err := os.Stat(lockDBPath); err != nil {
  160. fmt.Println()
  161. return
  162. }
  163. }
  164. }
  165. func (c *CmdBuilder) SudoLoop() {
  166. c.updateSudo()
  167. go c.sudoLoopBackground()
  168. }
  169. func (c *CmdBuilder) sudoLoopBackground() {
  170. for {
  171. c.updateSudo()
  172. time.Sleep(SudoLoopDuration * time.Second)
  173. }
  174. }
  175. func (c *CmdBuilder) updateSudo() {
  176. for {
  177. err := c.Show(exec.Command(c.SudoBin, "-v"))
  178. if err != nil {
  179. fmt.Fprintln(os.Stderr, err)
  180. } else {
  181. break
  182. }
  183. }
  184. }
  185. func (c *CmdBuilder) Show(cmd *exec.Cmd) error {
  186. return c.Runner.Show(cmd)
  187. }
  188. func (c *CmdBuilder) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) {
  189. return c.Runner.Capture(cmd)
  190. }