github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/builtin_fn_cmd_unix.go (about)

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