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