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