src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/builtin_fn_cmd_unix.go (about)

     1  //go:build unix
     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/errs"
    14  	"src.elv.sh/pkg/eval/vals"
    15  	"src.elv.sh/pkg/sys/eunix"
    16  )
    17  
    18  // ErrNotInSameProcessGroup is thrown when the process IDs passed to fg are not
    19  // in the same process group.
    20  var ErrNotInSameProcessGroup = errors.New("not in the same process group")
    21  
    22  // Reference to syscall.Exec. Can be overridden in tests.
    23  var syscallExec = syscall.Exec
    24  
    25  func execFn(fm *Frame, args ...any) error {
    26  	var argstrings []string
    27  	if len(args) == 0 {
    28  		argstrings = []string{"elvish"}
    29  	} else {
    30  		argstrings = make([]string, len(args))
    31  		for i, a := range args {
    32  			argstrings[i] = vals.ToString(a)
    33  		}
    34  	}
    35  
    36  	var err error
    37  	argstrings[0], err = exec.LookPath(argstrings[0])
    38  	if err != nil {
    39  		return err
    40  	}
    41  
    42  	fm.Evaler.PreExit()
    43  	decSHLVL()
    44  
    45  	return syscallExec(argstrings[0], argstrings, os.Environ())
    46  }
    47  
    48  // Decrements $E:SHLVL. Called from execFn to ensure that $E:SHLVL remains the
    49  // same in the new command.
    50  func decSHLVL() {
    51  	i, err := strconv.Atoi(os.Getenv(env.SHLVL))
    52  	if err != nil {
    53  		return
    54  	}
    55  	os.Setenv(env.SHLVL, strconv.Itoa(i-1))
    56  }
    57  
    58  func fg(pids ...int) error {
    59  	if len(pids) == 0 {
    60  		return errs.ArityMismatch{What: "arguments", ValidLow: 1, ValidHigh: -1, Actual: len(pids)}
    61  	}
    62  	var thepgid int
    63  	for i, pid := range pids {
    64  		pgid, err := syscall.Getpgid(pid)
    65  		if err != nil {
    66  			return err
    67  		}
    68  		if i == 0 {
    69  			thepgid = pgid
    70  		} else if pgid != thepgid {
    71  			return ErrNotInSameProcessGroup
    72  		}
    73  	}
    74  
    75  	err := eunix.Tcsetpgrp(0, thepgid)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	errors := make([]Exception, len(pids))
    81  
    82  	for i, pid := range pids {
    83  		err := syscall.Kill(pid, syscall.SIGCONT)
    84  		if err != nil {
    85  			errors[i] = &exception{err, nil}
    86  		}
    87  	}
    88  
    89  	for i, pid := range pids {
    90  		if errors[i] != nil {
    91  			continue
    92  		}
    93  		var ws syscall.WaitStatus
    94  		_, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil)
    95  		if err != nil {
    96  			errors[i] = &exception{err, nil}
    97  		} else {
    98  			// TODO find command name
    99  			errors[i] = &exception{NewExternalCmdExit(
   100  				"[pid "+strconv.Itoa(pid)+"]", ws, pid), nil}
   101  		}
   102  	}
   103  
   104  	return MakePipelineError(errors)
   105  }