feat!: add start function to jobs

the commit itself adds a few things but the
main purpose is to facilitate a lua side start
function that can restart the job

there is a breaking change in the hilbish.job.add
function; it is now required to provide an extra
table for arguments, since the first cmd
table isnt really what's actually ran
extended-job-api
TorchedSammy 2022-05-13 19:46:25 -04:00
parent 827c25fb57
commit c78d7f5627
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
2 changed files with 128 additions and 41 deletions

70
exec.go
View File

@ -239,7 +239,7 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
stmtStr := buf.String()
buf.Reset()
jobs.add(stmtStr)
jobs.add(stmtStr, []string{}, "")
}
interp.ExecHandler(execHandle(bg))(runner)
@ -357,13 +357,15 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
Stderr: hc.Stderr,
}
err = cmd.Start()
var j *job
if bg {
j = jobs.getLatest()
j.setHandle(cmd.Process)
j.start(cmd.Process.Pid)
j.setHandle(&cmd)
err = j.start()
} else {
err = cmd.Start()
}
if err == nil {
if done := ctx.Done(); done != nil {
go func() {
@ -388,35 +390,8 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
err = cmd.Wait()
}
var exit uint8
switch x := err.(type) {
case *exec.ExitError:
// started, but errored - default to 1 if OS
// doesn't have exit statuses
if status, ok := x.Sys().(syscall.WaitStatus); ok {
if status.Signaled() {
if ctx.Err() != nil {
return ctx.Err()
}
exit = uint8(128 + status.Signal())
goto end
}
exit = uint8(status.ExitStatus())
goto end
}
exit = 1
goto end
case *exec.Error:
// did not start
fmt.Fprintf(hc.Stderr, "%v\n", err)
exit = 127
goto end
case nil:
goto end
default:
return err
}
end:
exit := handleExecErr(err)
if bg {
j.exitCode = int(exit)
j.finish()
@ -425,6 +400,35 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
}
}
func handleExecErr(err error) (exit uint8) {
ctx := context.TODO()
switch x := err.(type) {
case *exec.ExitError:
// started, but errored - default to 1 if OS
// doesn't have exit statuses
if status, ok := x.Sys().(syscall.WaitStatus); ok {
if status.Signaled() {
if ctx.Err() != nil {
return
}
exit = uint8(128 + status.Signal())
return
}
exit = uint8(status.ExitStatus())
return
}
exit = 1
return
case *exec.Error:
// did not start
//fmt.Fprintf(hc.Stderr, "%v\n", err)
exit = 127
default: return
}
return
}
func lookpath(file string) error { // custom lookpath function so we know if a command is found *and* is executable
var skip []string
if runtime.GOOS == "windows" {

99
job.go
View File

@ -3,10 +3,12 @@ package main
import (
"sync"
"os"
"os/exec"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/iolib"
)
var jobs *jobHandler
@ -17,18 +19,51 @@ type job struct {
id int
pid int
exitCode int
proc *os.Process
once bool
args []string
// save path for a few reasons, one being security (lmao) while the other
// would just be so itll be the same binary command always (path changes)
path string
handle *exec.Cmd
stdin *iolib.File
stdout *iolib.File
stderr *iolib.File
}
func (j *job) start(pid int) {
j.pid = pid
func (j *job) start() error {
if j.handle == nil || j.once {
// cmd cant be reused so make a new one
cmd := exec.Cmd{
Path: j.path,
Args: j.args,
Stdin: j.getStdio("in"),
Stdout: j.getStdio("out"),
Stderr: j.getStdio("err"),
}
j.setHandle(&cmd)
}
if !j.once {
j.once = true
}
err := j.handle.Start()
proc := j.getProc()
j.pid = proc.Pid
j.running = true
hooks.Em.Emit("job.start", j.lua())
return err
}
func (j *job) stop() {
// finish will be called in exec handle
j.proc.Kill()
proc := j.getProc()
if proc != nil {
proc.Kill()
}
}
func (j *job) finish() {
@ -36,13 +71,38 @@ func (j *job) finish() {
hooks.Em.Emit("job.done", j.lua())
}
func (j *job) setHandle(handle *os.Process) {
j.proc = handle
func (j *job) setHandle(handle *exec.Cmd) {
j.handle = handle
j.args = handle.Args
j.path = handle.Path
}
func (j *job) getProc() *os.Process {
handle := j.handle
if handle != nil {
return handle.Process
}
return nil
}
func (j *job) getStdio(typ string) *os.File {
// TODO: make this use std io/out/err values from job struct,
// which are lua files
var stdio *os.File
switch typ {
case "in": stdio = os.Stdin
case "out": stdio = os.Stdout
case "err": stdio = os.Stderr
}
return stdio
}
func (j *job) lua() rt.Value {
jobFuncs := map[string]util.LuaExport{
"stop": {j.luaStop, 0, false},
"start": {j.luaStart, 0, false},
}
luaJob := rt.NewTable()
util.SetExports(l, luaJob, jobFuncs)
@ -56,6 +116,17 @@ func (j *job) lua() rt.Value {
return rt.TableValue(luaJob)
}
func (j *job) luaStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if !j.running {
err := j.start()
exit := handleExecErr(err)
j.exitCode = int(exit)
j.finish()
}
return c.Next(), nil
}
func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if j.running {
j.stop()
@ -79,7 +150,7 @@ func newJobHandler() *jobHandler {
}
}
func (j *jobHandler) add(cmd string) *job {
func (j *jobHandler) add(cmd string, args []string, path string) *job {
j.mu.Lock()
defer j.mu.Unlock()
@ -88,6 +159,7 @@ func (j *jobHandler) add(cmd string) *job {
cmd: cmd,
running: false,
id: j.latestID,
args: args,
}
j.jobs[j.latestID] = jb
@ -145,8 +217,19 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err != nil {
return nil, err
}
largs, err := c.TableArg(1)
if err != nil {
return nil, err
}
jb := j.add(cmd)
var args []string
util.ForEach(largs, func(k rt.Value, v rt.Value) {
if v.Type() == rt.StringType {
args = append(args, v.AsString())
}
})
// TODO: change to lookpath for args[0]
jb := j.add(cmd, args, args[0])
return c.PushingNext1(t.Runtime, jb.lua()), nil
}