cmd_builder.go 7.1 KB

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