123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- //go:build !windows && !plan9
- package eval
- import (
- "errors"
- "os"
- "os/exec"
- "strconv"
- "syscall"
- "src.elv.sh/pkg/env"
- "src.elv.sh/pkg/eval/errs"
- "src.elv.sh/pkg/eval/vals"
- "src.elv.sh/pkg/sys/eunix"
- )
- // ErrNotInSameProcessGroup is thrown when the process IDs passed to fg are not
- // in the same process group.
- var ErrNotInSameProcessGroup = errors.New("not in the same process group")
- //elvdoc:fn exec
- //
- // ```elvish
- // exec $command? $args...
- // ```
- //
- // Replace the Elvish process with an external `$command`, defaulting to
- // `elvish`, passing the given arguments. This decrements `$E:SHLVL` before
- // starting the new process.
- //
- // This command always raises an exception on Windows with the message "not
- // supported on Windows".
- // Reference to syscall.Exec. Can be overridden in tests.
- var syscallExec = syscall.Exec
- func execFn(fm *Frame, args ...interface{}) error {
- var argstrings []string
- if len(args) == 0 {
- argstrings = []string{"elvish"}
- } else {
- argstrings = make([]string, len(args))
- for i, a := range args {
- argstrings[i] = vals.ToString(a)
- }
- }
- var err error
- argstrings[0], err = exec.LookPath(argstrings[0])
- if err != nil {
- return err
- }
- preExit(fm)
- decSHLVL()
- return syscallExec(argstrings[0], argstrings, os.Environ())
- }
- // Decrements $E:SHLVL. Called from execFn to ensure that $E:SHLVL remains the
- // same in the new command.
- func decSHLVL() {
- i, err := strconv.Atoi(os.Getenv(env.SHLVL))
- if err != nil {
- return
- }
- os.Setenv(env.SHLVL, strconv.Itoa(i-1))
- }
- func fg(pids ...int) error {
- if len(pids) == 0 {
- return errs.ArityMismatch{What: "arguments", ValidLow: 1, ValidHigh: -1, Actual: len(pids)}
- }
- var thepgid int
- for i, pid := range pids {
- pgid, err := syscall.Getpgid(pid)
- if err != nil {
- return err
- }
- if i == 0 {
- thepgid = pgid
- } else if pgid != thepgid {
- return ErrNotInSameProcessGroup
- }
- }
- err := eunix.Tcsetpgrp(0, thepgid)
- if err != nil {
- return err
- }
- errors := make([]Exception, len(pids))
- for i, pid := range pids {
- err := syscall.Kill(pid, syscall.SIGCONT)
- if err != nil {
- errors[i] = &exception{err, nil}
- }
- }
- for i, pid := range pids {
- if errors[i] != nil {
- continue
- }
- var ws syscall.WaitStatus
- _, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil)
- if err != nil {
- errors[i] = &exception{err, nil}
- } else {
- // TODO find command name
- errors[i] = &exception{NewExternalCmdExit(
- "[pid "+strconv.Itoa(pid)+"]", ws, pid), nil}
- }
- }
- return MakePipelineError(errors)
- }
|