github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/exec.go (about) 1 package lang 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "reflect" 8 "strings" 9 "syscall" 10 11 "github.com/lmorg/murex/builtins/pipes/null" 12 "github.com/lmorg/murex/builtins/pipes/term" 13 "github.com/lmorg/murex/debug" 14 "github.com/lmorg/murex/lang/types" 15 "github.com/lmorg/murex/utils/consts" 16 ) 17 18 const ( 19 envMethodTrue = consts.EnvMethod + "=" + consts.EnvTrue 20 envMethodFalse = consts.EnvMethod + "=" + consts.EnvFalse 21 22 envBackgroundTrue = consts.EnvBackground + "=" + consts.EnvTrue 23 envBackgroundFalse = consts.EnvBackground + "=" + consts.EnvFalse 24 25 envDataType = consts.EnvDataType + "=" 26 ) 27 28 var ( 29 envMurexPid = fmt.Sprintf("%s=%d", consts.EnvMurexPid, os.Getpid()) 30 termOut = reflect.TypeOf(new(term.Out)).String() 31 ) 32 33 // External executes an external process. 34 func External(p *Process) error { 35 if err := execute(p); err != nil { 36 _, cmd := p.Exec.Get() 37 if cmd != nil { 38 p.ExitNum = cmd.ProcessState.ExitCode() 39 } else { 40 p.ExitNum = 1 41 } 42 return err 43 44 } 45 return nil 46 } 47 48 func execute(p *Process) error { 49 exeName, parameters, err := getCmdTokens(p) 50 if err != nil { 51 return err 52 } 53 cmd := exec.Command(exeName, parameters...) 54 55 if p.HasCancelled() { 56 return nil 57 } 58 59 //ctxCancel := p.Kill 60 p.Kill = func() { 61 if !debug.Enabled { 62 defer func() { recover() }() // I don't care about errors in this instance since we are just killing the proc anyway 63 } 64 65 //ctxCancel() 66 err := cmd.Process.Signal(syscall.SIGTERM) 67 if err != nil { 68 if err.Error() == os.ErrProcessDone.Error() { 69 return 70 } 71 name, _ := p.Args() 72 os.Stderr.WriteString( 73 fmt.Sprintf("\nError sending SIGTERM to `%s`: %s\n", name, err.Error())) 74 } 75 } 76 77 // *** 78 // Define STANDARD IN (fd 0) 79 // *** 80 81 switch { 82 case p.IsMethod: 83 cmd.Stdin = p.Stdin 84 if p.Background.Get() { 85 cmd.Env = append(os.Environ(), envMurexPid, envMethodTrue, envBackgroundTrue, envDataType+p.Stdin.GetDataType()) 86 } else { 87 cmd.Env = append(os.Environ(), envMurexPid, envMethodTrue, envBackgroundFalse, envDataType+p.Stdin.GetDataType()) 88 } 89 case p.Background.Get(): 90 cmd.Stdin = new(null.Null) 91 cmd.Env = append(os.Environ(), envMurexPid, envMethodFalse, envBackgroundTrue, envDataType+p.Stdin.GetDataType()) 92 default: 93 cmd.Stdin = os.Stdin 94 cmd.Env = append(os.Environ(), envMurexPid, envMethodFalse, envBackgroundFalse, envDataType+p.Stdin.GetDataType()) 95 } 96 cmd.Env = append(cmd.Env, p.Exec.Env...) 97 98 // *** 99 // Define STANDARD OUT (fd 1) 100 // *** 101 102 if p.Stdout.IsTTY() { 103 // If Stdout is a TTY then set the appropriate syscalls to allow the calling program to own the TTY.... 104 //osSyscalls(cmd, int(p.ttyout.Fd())) 105 //osSyscalls(cmd, int(os.Stdin.Fd())) 106 //if reflect.TypeOf(p.Stdout).String() == termOut { 107 // osSyscalls(cmd, int(p.ttyout.Fd())) 108 // cmd.Stdout = p.ttyout 109 //} else { 110 osSyscalls(cmd, int(p.Stdout.File().Fd())) 111 cmd.Stdout = p.Stdout.File() 112 //} 113 } else { 114 // ....otherwise we just treat the program as a regular piped util 115 cmd.Stdout = p.Stdout 116 } 117 118 // *** 119 // Define STANDARD ERR (fd 2) 120 // *** 121 122 // Pipe STDERR irrespective of whether the exec process is outputting to a TTY or not. 123 // The reason for this is so that we can do some post-processing on the error stream (namely add colour to it), 124 // however this might cause some bugs. If so please raise on github: https://github.com/lmorg/murex 125 // In the meantime, you can force exec processes to write STDERR to the TTY via the `config` command in the shell: 126 // 127 // config set proc force-tty true 128 if p.Stderr.IsTTY() && forceTTY(p) { 129 //cmd.Stderr = tty.Stderr 130 cmd.Stderr = p.Stderr.File() 131 } else { 132 cmd.Stderr = p.Stderr 133 } 134 135 // *** 136 // Define MUREX DATA TYPE (fd 3) 137 // *** 138 139 /*var failedPipe bool 140 mxdtR, mxdtW, err := os.Pipe() 141 if err != nil { 142 tty.Stderr.WriteString("unable to create murex data type output file for external process: " + err.Error() + "\n") 143 failedPipe = true 144 mxdtR = new(os.File) 145 mxdtW = new(os.File) 146 147 } else { 148 cmd.ExtraFiles = []*os.File{mxdtW} 149 }*/ 150 151 // *** 152 // Start process 153 // *** 154 155 if err := cmd.Start(); err != nil { 156 //if !strings.HasPrefix(err.Error(), "signal:") { 157 //mxdtW.Close() 158 //mxdtR.Close() 159 return err 160 //} 161 } 162 163 // *** 164 // Get murex data type 165 // *** 166 167 /*go func() { 168 if failedPipe { 169 p.Stdout.SetDataType(types.Generic) 170 return 171 } 172 173 var dt string 174 175 scanner := bufio.NewScanner(mxdtR) 176 scanner.Split(bufio.ScanLines) 177 for scanner.Scan() { 178 dt = scanner.Text() 179 break 180 } 181 182 if scanner.Err() != nil || dt == "" { 183 dt = types.Generic 184 } 185 186 p.Stdout.SetDataType(dt) 187 mxdtR.Close() 188 }()*/ 189 190 ///////// 191 192 p.Exec.Set(cmd.Process.Pid, cmd) 193 194 /*if err := mxdtW.Close(); err != nil { 195 tty.Stderr.WriteString("error closing murex data type output file write pipe:" + err.Error() + "\n") 196 }*/ 197 198 if err := cmd.Wait(); err != nil { 199 if !strings.HasPrefix(err.Error(), "signal:") { 200 //mxdtR.Close() 201 return err 202 } 203 } 204 205 //mxdtR.Close() 206 return nil 207 } 208 209 func forceTTY(p *Process) bool { 210 v, err := p.Config.Get("proc", "force-tty", types.Boolean) 211 if err != nil { 212 return false 213 } 214 return v.(bool) 215 }