github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_cmd_unix.go (about)

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