github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/external_cmd.go (about) 1 package eval 2 3 import ( 4 "errors" 5 "os" 6 "os/exec" 7 "syscall" 8 9 "src.elv.sh/pkg/eval/vals" 10 "src.elv.sh/pkg/fsutil" 11 "src.elv.sh/pkg/parse" 12 "src.elv.sh/pkg/persistent/hash" 13 ) 14 15 var ( 16 // ErrExternalCmdOpts is thrown when an external command is passed Elvish 17 // options. 18 // 19 // TODO: Catch this kind of errors at compilation time. 20 ErrExternalCmdOpts = errors.New("external commands don't accept elvish options") 21 // ErrImplicitCdNoArg is thrown when an implicit cd form is passed arguments. 22 ErrImplicitCdNoArg = errors.New("implicit cd accepts no arguments") 23 ) 24 25 // externalCmd is an external command. 26 type externalCmd struct { 27 Name string 28 } 29 30 // NewExternalCmd returns a callable that executes the named external command. 31 // 32 // An external command converts all arguments to strings, and does not accept 33 // any option. 34 func NewExternalCmd(name string) Callable { 35 return externalCmd{name} 36 } 37 38 func (e externalCmd) Kind() string { 39 return "fn" 40 } 41 42 func (e externalCmd) Equal(a interface{}) bool { 43 return e == a 44 } 45 46 func (e externalCmd) Hash() uint32 { 47 return hash.String(e.Name) 48 } 49 50 func (e externalCmd) Repr(int) string { 51 return "<external " + parse.Quote(e.Name) + ">" 52 } 53 54 // Call calls an external command. 55 func (e externalCmd) Call(fm *Frame, argVals []interface{}, opts map[string]interface{}) error { 56 if len(opts) > 0 { 57 return ErrExternalCmdOpts 58 } 59 if fsutil.DontSearch(e.Name) { 60 stat, err := os.Stat(e.Name) 61 if err == nil && stat.IsDir() { 62 // implicit cd 63 if len(argVals) > 0 { 64 return ErrImplicitCdNoArg 65 } 66 return fm.Evaler.Chdir(e.Name) 67 } 68 } 69 70 files := make([]*os.File, len(fm.ports)) 71 for i, port := range fm.ports { 72 if port != nil { 73 files[i] = port.File 74 } 75 } 76 77 args := make([]string, len(argVals)+1) 78 for i, a := range argVals { 79 // TODO: Maybe we should enforce string arguments instead of coercing 80 // all args to strings. 81 args[i+1] = vals.ToString(a) 82 } 83 84 path, err := exec.LookPath(e.Name) 85 if err != nil { 86 return err 87 } 88 89 args[0] = path 90 91 sys := makeSysProcAttr(fm.background) 92 proc, err := os.StartProcess(path, args, &os.ProcAttr{Files: files, Sys: sys}) 93 if err != nil { 94 return err 95 } 96 97 state, err := proc.Wait() 98 if err != nil { 99 // This should be a can't happen situation. Nonetheless, treat it as a 100 // soft error rather than panicking since the Go documentation is not 101 // explicit that this can only happen if we make a mistake. Such as 102 // calling `Wait` twice on a particular process object. 103 return err 104 } 105 return NewExternalCmdExit(e.Name, state.Sys().(syscall.WaitStatus), proc.Pid) 106 }