cmd_builder.go 6.5 KB

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