cmd_builder.go 6.1 KB

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