cmd_builder.go 7.2 KB

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