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  }