github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_cmd_unix.go (about) 1 //go:build !windows && !plan9 2 // +build !windows,!plan9 3 4 package eval 5 6 import ( 7 "errors" 8 "os" 9 "os/exec" 10 "strconv" 11 "syscall" 12 13 "github.com/markusbkk/elvish/pkg/env" 14 "github.com/markusbkk/elvish/pkg/eval/errs" 15 "github.com/markusbkk/elvish/pkg/eval/vals" 16 "github.com/markusbkk/elvish/pkg/sys/eunix" 17 ) 18 19 // ErrNotInSameProcessGroup is thrown when the process IDs passed to fg are not 20 // in the same process group. 21 var ErrNotInSameProcessGroup = errors.New("not in the same process group") 22 23 //elvdoc:fn exec 24 // 25 // ```elvish 26 // exec $command? $args... 27 // ``` 28 // 29 // Replace the Elvish process with an external `$command`, defaulting to 30 // `elvish`, passing the given arguments. This decrements `$E:SHLVL` before 31 // starting the new process. 32 // 33 // This command always raises an exception on Windows with the message "not 34 // supported on Windows". 35 36 // Reference to syscall.Exec. Can be overridden in tests. 37 var syscallExec = syscall.Exec 38 39 func execFn(fm *Frame, args ...interface{}) error { 40 var argstrings []string 41 if len(args) == 0 { 42 argstrings = []string{"elvish"} 43 } else { 44 argstrings = make([]string, len(args)) 45 for i, a := range args { 46 argstrings[i] = vals.ToString(a) 47 } 48 } 49 50 var err error 51 argstrings[0], err = exec.LookPath(argstrings[0]) 52 if err != nil { 53 return err 54 } 55 56 preExit(fm) 57 decSHLVL() 58 59 return syscallExec(argstrings[0], argstrings, os.Environ()) 60 } 61 62 // Decrements $E:SHLVL. Called from execFn to ensure that $E:SHLVL remains the 63 // same in the new command. 64 func decSHLVL() { 65 i, err := strconv.Atoi(os.Getenv(env.SHLVL)) 66 if err != nil { 67 return 68 } 69 os.Setenv(env.SHLVL, strconv.Itoa(i-1)) 70 } 71 72 func fg(pids ...int) error { 73 if len(pids) == 0 { 74 return errs.ArityMismatch{What: "arguments", ValidLow: 1, ValidHigh: -1, Actual: len(pids)} 75 } 76 var thepgid int 77 for i, pid := range pids { 78 pgid, err := syscall.Getpgid(pid) 79 if err != nil { 80 return err 81 } 82 if i == 0 { 83 thepgid = pgid 84 } else if pgid != thepgid { 85 return ErrNotInSameProcessGroup 86 } 87 } 88 89 err := eunix.Tcsetpgrp(0, thepgid) 90 if err != nil { 91 return err 92 } 93 94 errors := make([]Exception, len(pids)) 95 96 for i, pid := range pids { 97 err := syscall.Kill(pid, syscall.SIGCONT) 98 if err != nil { 99 errors[i] = &exception{err, nil} 100 } 101 } 102 103 for i, pid := range pids { 104 if errors[i] != nil { 105 continue 106 } 107 var ws syscall.WaitStatus 108 _, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil) 109 if err != nil { 110 errors[i] = &exception{err, nil} 111 } else { 112 // TODO find command name 113 errors[i] = &exception{NewExternalCmdExit( 114 "[pid "+strconv.Itoa(pid)+"]", ws, pid), nil} 115 } 116 } 117 118 return MakePipelineError(errors) 119 }