fg tasks
This commit is contained in:
parent
08bb7073cd
commit
e098a8812c
2 changed files with 131 additions and 106 deletions
|
|
@ -78,6 +78,9 @@ func main() {
|
|||
ast.SyncTablesWithOSEnviron = true
|
||||
ast.ExecWhenFuncUndef = true
|
||||
|
||||
stdlib.InitShellFeatures()
|
||||
defer stdlib.TeardownShell()
|
||||
|
||||
vars, funcs := config.InitFromConfig(".shsrc")
|
||||
debug_t := ast.GetVar("SH_DEBUG_MODE", vars)
|
||||
if debug_t != nil {
|
||||
|
|
@ -125,9 +128,6 @@ func main() {
|
|||
defer histFile.Close()
|
||||
}
|
||||
|
||||
stdlib.InitShellFeatures()
|
||||
defer stdlib.TeardownShell()
|
||||
|
||||
for {
|
||||
setLogLvl(vars)
|
||||
var prePrompt string
|
||||
|
|
|
|||
231
stdlib/shell.go
231
stdlib/shell.go
|
|
@ -19,18 +19,20 @@ package stdlib
|
|||
|
||||
import (
|
||||
"os"
|
||||
"io"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"context"
|
||||
"syscall"
|
||||
"strconv"
|
||||
"os/signal"
|
||||
"golang.org/x/sys/unix"
|
||||
"gitlab.com/whom/shs/ast"
|
||||
"gitlab.com/whom/shs/log"
|
||||
)
|
||||
|
||||
func tcsetpgrp(fd int, pgrp int) error {
|
||||
return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pgrp)
|
||||
}
|
||||
|
||||
var sigChan chan os.Signal
|
||||
var waitChan chan error
|
||||
|
||||
|
|
@ -40,7 +42,7 @@ var JobMap = map[int]Proc{}
|
|||
|
||||
/* id of shell process group
|
||||
*/
|
||||
var pgid int
|
||||
var pgid, pid int
|
||||
|
||||
/* Holds an os/exec Cmd object and its context cancel function
|
||||
*/
|
||||
|
|
@ -64,7 +66,7 @@ func signalHandler() {
|
|||
switch sig {
|
||||
case syscall.SIGINT:
|
||||
log.Log(log.DEBUG,
|
||||
"caught SIGINT!",
|
||||
"caught SIGINT",
|
||||
"jobctl")
|
||||
}
|
||||
}
|
||||
|
|
@ -88,26 +90,60 @@ func waitHandler() {
|
|||
|
||||
LastExitCode = exit
|
||||
}
|
||||
|
||||
log.Log(log.DEBUG,
|
||||
"handled sigchld!",
|
||||
"jobctl")
|
||||
}
|
||||
}
|
||||
|
||||
// for some reason not implemented in stdlib
|
||||
func tcsetpgrp(fd int, pgrp int) error {
|
||||
return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pgrp)
|
||||
}
|
||||
|
||||
// wrapper for convenience
|
||||
func setSigState() {
|
||||
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN, syscall.SIGTSTP)
|
||||
signal.Notify(sigChan, syscall.SIGINT)
|
||||
}
|
||||
|
||||
/* Sets pgid
|
||||
* Installs a sigchld handler
|
||||
* returns true if success, false on error
|
||||
*/
|
||||
func InitShellFeatures() bool {
|
||||
// TODO: adjust, make configurable, i dunno
|
||||
// TODO: adjust capacity, make configurable maybe?
|
||||
sigChan = make(chan os.Signal, 5)
|
||||
waitChan = make(chan error, 5)
|
||||
|
||||
go signalHandler()
|
||||
go waitHandler()
|
||||
|
||||
pid := os.Getpid()
|
||||
pgid = syscall.Getpgrp()
|
||||
if pid != pgid {
|
||||
syscall.Setpgid(0, 0)
|
||||
setSigState()
|
||||
pid = os.Getpid()
|
||||
var errr error
|
||||
pgid, errr = syscall.Getpgid(pid)
|
||||
if errr != nil {
|
||||
log.Log(log.ERR,
|
||||
"Failure to get pgid: " + errr.Error(),
|
||||
"jobctl")
|
||||
return false
|
||||
}
|
||||
|
||||
termPgrp, err := unix.IoctlGetInt(0, unix.TIOCGPGRP)
|
||||
if err != nil {
|
||||
log.Log(log.ERR,
|
||||
"Failure to get pgrp: " + err.Error(),
|
||||
"jobctl")
|
||||
return false
|
||||
}
|
||||
|
||||
if pgid != termPgrp {
|
||||
syscall.Kill(-termPgrp, unix.SIGTTIN)
|
||||
}
|
||||
|
||||
syscall.Setpgid(0, 0)
|
||||
tcsetpgrp(0, pid)
|
||||
|
||||
return true
|
||||
|
|
@ -125,26 +161,48 @@ func TeardownShell() {
|
|||
* uses os/exec Cmd object. sets SysProcAttr.Foreground
|
||||
* calls Cmd.Start()
|
||||
*/
|
||||
func LaunchProcess(path string, args []string, background bool) {
|
||||
func LaunchProcess(
|
||||
path string,
|
||||
args []string,
|
||||
background bool,
|
||||
stdin io.Reader,
|
||||
stdout io.Writer,
|
||||
stderr io.Writer ) {
|
||||
c, cancel := context.WithCancel(context.Background())
|
||||
cmd := exec.CommandContext(c, path, args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Foreground: !background, Pgid: pgid}
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Foreground: !background, Setpgid: true}
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if stdin == nil {
|
||||
stdin = os.Stdin
|
||||
}
|
||||
|
||||
if stdout == nil {
|
||||
stdout = os.Stdout
|
||||
}
|
||||
|
||||
if stderr == nil {
|
||||
stderr = os.Stderr
|
||||
}
|
||||
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
|
||||
cmd.Start()
|
||||
pid := cmd.Process.Pid
|
||||
|
||||
// TODO: check for clobber?
|
||||
// unlikely, but PIDs do reset after a while
|
||||
JobMap[pid] = Proc{ Ctl: cmd, Cancel: cancel }
|
||||
go func(){
|
||||
waitChan <- cmd.Wait()
|
||||
}()
|
||||
|
||||
if background {
|
||||
cmd.Process.Signal(syscall.SIGTSTP)
|
||||
go func(){
|
||||
waitChan <- cmd.Wait()
|
||||
tcsetpgrp(0, pgid)
|
||||
}()
|
||||
} else {
|
||||
cmd.Wait()
|
||||
tcsetpgrp(0, pgid)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +239,7 @@ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|||
args = append(args, i.Value())
|
||||
}
|
||||
|
||||
LaunchProcess(path, args, false)
|
||||
LaunchProcess(path, args, false, nil, nil, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +249,7 @@ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|||
* Example: (bg vim file.txt)
|
||||
*/
|
||||
func Bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||
in = in.Eval(ft, vt, true)
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -216,36 +275,51 @@ func Bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|||
args = append(args, i.Value())
|
||||
}
|
||||
|
||||
LaunchProcess(path, args, true)
|
||||
LaunchProcess(path, args, true, nil, nil, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
/* brings last BG'ed process into the foreground
|
||||
/* takes one argument: pid of process to foreground
|
||||
* returns nil
|
||||
*
|
||||
* Example:
|
||||
* (bg vim file.txt)
|
||||
* (fg)
|
||||
* ( <call to ps or jobs> )
|
||||
* (fg <pid>)
|
||||
*/
|
||||
func Fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||
if len(bgProcs) < 1 {
|
||||
if len(JobMap) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := bgProcs[0]
|
||||
bgProcs = bgProcs[1:]
|
||||
in = in.Eval(ft, vt, false)
|
||||
if in.Tag != ast.NUMBER && in.Tag != ast.STRING {
|
||||
log.Log(log.ERR,
|
||||
"must supply a number or string to fg",
|
||||
"fg")
|
||||
return nil
|
||||
}
|
||||
|
||||
signalChan := make(chan os.Signal, 2)
|
||||
signal.Notify(signalChan, sigs...)
|
||||
go func() {
|
||||
sig := <-signalChan
|
||||
cmd.Process.Signal(sig)
|
||||
}()
|
||||
pid, err := strconv.ParseFloat(in.Value(), 64)
|
||||
if err != nil {
|
||||
log.Log(log.ERR,
|
||||
"value supplied to fg could not be cast to float",
|
||||
"fg")
|
||||
return nil
|
||||
}
|
||||
|
||||
ipid := int(pid)
|
||||
proc, ok := JobMap[ipid]
|
||||
if !ok {
|
||||
log.Log(log.ERR,
|
||||
"Process not found, was it started by this shell?",
|
||||
"fg")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := proc.Ctl
|
||||
cmd.Process.Signal(syscall.SIGCONT)
|
||||
err := cmd.Wait()
|
||||
close(signalChan)
|
||||
signal.Reset(sigs...)
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
LastExitCode = exitError.ExitCode()
|
||||
|
|
@ -258,8 +332,7 @@ func Fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|||
}
|
||||
|
||||
/* Takes 0 args
|
||||
* returns a string containing info about current jobs
|
||||
* returns total jobs as well as their PIDs and place in the bg queue
|
||||
* returns a list of PIDs
|
||||
*
|
||||
* Example:
|
||||
* (bg ping google.com)
|
||||
|
|
@ -271,22 +344,18 @@ func Jobs(in *ast.Token, vt ast.VarTable, fg ast.FuncTable) *ast.Token {
|
|||
Tag: ast.LIST,
|
||||
}
|
||||
|
||||
_inner := &ast.Token{
|
||||
Tag: ast.STRING,
|
||||
}
|
||||
|
||||
ret.Direct(_inner)
|
||||
_inner.Set(fmt.Sprintf("Total: %d", len(bgProcs)))
|
||||
|
||||
var _inner *ast.Token
|
||||
iter := &_inner
|
||||
for i := 0; i < len(bgProcs); i += 1 {
|
||||
(*iter).Next = &ast.Token{
|
||||
for k := range JobMap {
|
||||
(*iter) = &ast.Token{
|
||||
Tag: ast.STRING,
|
||||
}
|
||||
(*iter).Next.Set(fmt.Sprintf("[%d]: %d", i, bgProcs[i].Process.Pid))
|
||||
|
||||
(*iter).Set(fmt.Sprintf("%d", k))
|
||||
iter = &(*iter).Next
|
||||
}
|
||||
|
||||
ret.Direct(_inner)
|
||||
return ret
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +376,7 @@ func ReadCmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|||
return nil
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
out := new(bytes.Buffer)
|
||||
path, err := exec.LookPath(in.Value())
|
||||
if err != nil {
|
||||
log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call")
|
||||
|
|
@ -324,34 +393,7 @@ func ReadCmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|||
args = append(args, i.Value())
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if len(args) > 0 {
|
||||
cmd = exec.Command(path, args...)
|
||||
} else {
|
||||
cmd = exec.Command(path)
|
||||
}
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
signalChan := make(chan os.Signal, 2)
|
||||
signal.Notify(signalChan, sigs...)
|
||||
go func() {
|
||||
sig := <-signalChan
|
||||
cmd.Process.Signal(sig)
|
||||
}()
|
||||
|
||||
err = cmd.Run()
|
||||
close(signalChan)
|
||||
signal.Reset(sigs...)
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
LastExitCode = exitError.ExitCode()
|
||||
} else {
|
||||
log.Log(log.ERR, "Execution step returned error: " + err.Error(), "$")
|
||||
}
|
||||
}
|
||||
|
||||
LaunchProcess(path, args, false, nil, out, nil)
|
||||
output := out.String()
|
||||
olen := len(output)
|
||||
if olen > 0 && output[olen - 1] == '\n' {
|
||||
|
|
@ -396,33 +438,16 @@ func Kill(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|||
return nil
|
||||
}
|
||||
|
||||
found := false
|
||||
newBgProcs := []*exec.Cmd{}
|
||||
for _, i := range bgProcs {
|
||||
if i.Process.Pid != int(pid) {
|
||||
newBgProcs = append(newBgProcs, i)
|
||||
} else {
|
||||
found = true
|
||||
err = i.Process.Kill()
|
||||
if err != nil {
|
||||
log.Log(log.ERR, fmt.Sprintf("error killing process %d: %s",
|
||||
int(pid), err.Error()), "kill")
|
||||
newBgProcs = append(newBgProcs, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bgProcs = newBgProcs
|
||||
|
||||
if !found {
|
||||
// docs say no error on unix systems
|
||||
proc, _ := os.FindProcess(int(pid))
|
||||
err = proc.Kill()
|
||||
if err != nil {
|
||||
log.Log(log.ERR, fmt.Sprintf("error killing process %d: %s",
|
||||
int(pid), err.Error()), "kill")
|
||||
}
|
||||
proc, ok := JobMap[int(pid)]
|
||||
if !ok {
|
||||
log.Log(log.ERR,
|
||||
"Couldnt find process " + in.Value() + ", was it started by this shell?",
|
||||
"kill")
|
||||
return nil
|
||||
}
|
||||
|
||||
// if this doesnt work do proc.Ctl.Kill()
|
||||
proc.Cancel()
|
||||
delete(JobMap, int(pid))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue