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