builtin_fn_cmd_unix.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. //go:build !windows && !plan9
  2. package eval
  3. import (
  4. "errors"
  5. "os"
  6. "os/exec"
  7. "strconv"
  8. "syscall"
  9. "src.elv.sh/pkg/env"
  10. "src.elv.sh/pkg/eval/errs"
  11. "src.elv.sh/pkg/eval/vals"
  12. "src.elv.sh/pkg/sys/eunix"
  13. )
  14. // ErrNotInSameProcessGroup is thrown when the process IDs passed to fg are not
  15. // in the same process group.
  16. var ErrNotInSameProcessGroup = errors.New("not in the same process group")
  17. // Reference to syscall.Exec. Can be overridden in tests.
  18. var syscallExec = syscall.Exec
  19. func execFn(fm *Frame, args ...any) error {
  20. var argstrings []string
  21. if len(args) == 0 {
  22. argstrings = []string{"elvish"}
  23. } else {
  24. argstrings = make([]string, len(args))
  25. for i, a := range args {
  26. argstrings[i] = vals.ToString(a)
  27. }
  28. }
  29. var err error
  30. argstrings[0], err = exec.LookPath(argstrings[0])
  31. if err != nil {
  32. return err
  33. }
  34. fm.Evaler.PreExit()
  35. decSHLVL()
  36. return syscallExec(argstrings[0], argstrings, os.Environ())
  37. }
  38. // Decrements $E:SHLVL. Called from execFn to ensure that $E:SHLVL remains the
  39. // same in the new command.
  40. func decSHLVL() {
  41. i, err := strconv.Atoi(os.Getenv(env.SHLVL))
  42. if err != nil {
  43. return
  44. }
  45. os.Setenv(env.SHLVL, strconv.Itoa(i-1))
  46. }
  47. func fg(pids ...int) error {
  48. if len(pids) == 0 {
  49. return errs.ArityMismatch{What: "arguments", ValidLow: 1, ValidHigh: -1, Actual: len(pids)}
  50. }
  51. var thepgid int
  52. for i, pid := range pids {
  53. pgid, err := syscall.Getpgid(pid)
  54. if err != nil {
  55. return err
  56. }
  57. if i == 0 {
  58. thepgid = pgid
  59. } else if pgid != thepgid {
  60. return ErrNotInSameProcessGroup
  61. }
  62. }
  63. err := eunix.Tcsetpgrp(0, thepgid)
  64. if err != nil {
  65. return err
  66. }
  67. errors := make([]Exception, len(pids))
  68. for i, pid := range pids {
  69. err := syscall.Kill(pid, syscall.SIGCONT)
  70. if err != nil {
  71. errors[i] = &exception{err, nil}
  72. }
  73. }
  74. for i, pid := range pids {
  75. if errors[i] != nil {
  76. continue
  77. }
  78. var ws syscall.WaitStatus
  79. _, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil)
  80. if err != nil {
  81. errors[i] = &exception{err, nil}
  82. } else {
  83. // TODO find command name
  84. errors[i] = &exception{NewExternalCmdExit(
  85. "[pid "+strconv.Itoa(pid)+"]", ws, pid), nil}
  86. }
  87. }
  88. return MakePipelineError(errors)
  89. }