From e098a8812c6f21bd4d085eb1c600ad53f57bbec9 Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 22 Jul 2020 21:56:31 -0700 Subject: [PATCH] fg tasks --- cmd/shs.go | 6 +- stdlib/shell.go | 231 +++++++++++++++++++++++++++--------------------- 2 files changed, 131 insertions(+), 106 deletions(-) diff --git a/cmd/shs.go b/cmd/shs.go index cd2087a..739b917 100644 --- a/cmd/shs.go +++ b/cmd/shs.go @@ -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 diff --git a/stdlib/shell.go b/stdlib/shell.go index 83f7c13..4f8ccf2 100644 --- a/stdlib/shell.go +++ b/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) + * ( ) + * (fg ) */ 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 }