2022-03-19 17:10:50 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-05-21 15:37:19 +00:00
|
|
|
"errors"
|
2022-05-17 21:43:42 +00:00
|
|
|
"io"
|
2022-03-20 23:10:12 +00:00
|
|
|
"os"
|
2022-05-13 23:46:25 +00:00
|
|
|
"os/exec"
|
2022-05-17 21:43:42 +00:00
|
|
|
"sync"
|
2022-05-21 15:37:19 +00:00
|
|
|
"syscall"
|
2022-03-19 17:10:50 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
"hilbish/util"
|
|
|
|
|
|
|
|
rt "github.com/arnodel/golua/runtime"
|
2022-05-13 23:46:25 +00:00
|
|
|
"github.com/arnodel/golua/lib/iolib"
|
2022-03-19 17:10:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var jobs *jobHandler
|
|
|
|
|
|
|
|
type job struct {
|
|
|
|
cmd string
|
|
|
|
running bool
|
|
|
|
id int
|
|
|
|
pid int
|
|
|
|
exitCode int
|
2022-05-13 23:46:25 +00:00
|
|
|
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
|
2022-05-17 21:43:42 +00:00
|
|
|
stdin io.Reader
|
|
|
|
stdout io.Writer
|
|
|
|
stderr io.Writer
|
2022-03-19 17:10:50 +00:00
|
|
|
}
|
|
|
|
|
2022-05-13 23:46:25 +00:00
|
|
|
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,
|
2022-05-17 21:43:42 +00:00
|
|
|
Stdin: j.stdin,
|
|
|
|
Stdout: j.stdout,
|
|
|
|
Stderr: j.stderr,
|
2022-05-13 23:46:25 +00:00
|
|
|
}
|
|
|
|
j.setHandle(&cmd)
|
|
|
|
}
|
2022-05-21 10:03:53 +00:00
|
|
|
j.handle.SysProcAttr = bgProcAttr
|
2022-05-13 23:46:25 +00:00
|
|
|
|
|
|
|
if !j.once {
|
|
|
|
j.once = true
|
|
|
|
}
|
|
|
|
|
|
|
|
err := j.handle.Start()
|
|
|
|
proc := j.getProc()
|
|
|
|
|
|
|
|
j.pid = proc.Pid
|
2022-03-19 17:10:50 +00:00
|
|
|
j.running = true
|
2022-05-13 23:46:25 +00:00
|
|
|
|
2022-03-19 17:10:50 +00:00
|
|
|
hooks.Em.Emit("job.start", j.lua())
|
2022-05-13 23:46:25 +00:00
|
|
|
|
|
|
|
return err
|
2022-03-19 17:10:50 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 23:10:12 +00:00
|
|
|
func (j *job) stop() {
|
|
|
|
// finish will be called in exec handle
|
2022-05-13 23:46:25 +00:00
|
|
|
proc := j.getProc()
|
|
|
|
if proc != nil {
|
|
|
|
proc.Kill()
|
|
|
|
}
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-03-19 17:10:50 +00:00
|
|
|
func (j *job) finish() {
|
|
|
|
j.running = false
|
|
|
|
hooks.Em.Emit("job.done", j.lua())
|
|
|
|
}
|
|
|
|
|
2022-05-21 15:37:19 +00:00
|
|
|
func (j *job) wait() {
|
|
|
|
j.handle.Wait()
|
|
|
|
}
|
|
|
|
|
2022-05-13 23:46:25 +00:00
|
|
|
func (j *job) setHandle(handle *exec.Cmd) {
|
|
|
|
j.handle = handle
|
|
|
|
j.args = handle.Args
|
|
|
|
j.path = handle.Path
|
2022-05-17 21:43:42 +00:00
|
|
|
j.stdin = handle.Stdin
|
|
|
|
j.stdout = handle.Stdout
|
|
|
|
j.stderr = handle.Stderr
|
2022-05-13 23:46:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (j *job) getProc() *os.Process {
|
|
|
|
handle := j.handle
|
|
|
|
if handle != nil {
|
|
|
|
return handle.Process
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-17 21:43:42 +00:00
|
|
|
func (j *job) setStdio(typ string, f *iolib.File) {
|
2022-05-13 23:46:25 +00:00
|
|
|
switch typ {
|
2022-05-17 21:43:42 +00:00
|
|
|
case "in": j.stdin = f.File
|
|
|
|
case "out": j.stdout = f.File
|
|
|
|
case "err": j.stderr = f.File
|
2022-05-13 23:46:25 +00:00
|
|
|
}
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (j *job) lua() rt.Value {
|
|
|
|
jobFuncs := map[string]util.LuaExport{
|
|
|
|
"stop": {j.luaStop, 0, false},
|
2022-05-13 23:46:25 +00:00
|
|
|
"start": {j.luaStart, 0, false},
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
luaJob := rt.NewTable()
|
|
|
|
util.SetExports(l, luaJob, jobFuncs)
|
2022-03-19 17:10:50 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
luaJob.Set(rt.StringValue("cmd"), rt.StringValue(j.cmd))
|
|
|
|
luaJob.Set(rt.StringValue("running"), rt.BoolValue(j.running))
|
|
|
|
luaJob.Set(rt.StringValue("id"), rt.IntValue(int64(j.id)))
|
|
|
|
luaJob.Set(rt.StringValue("pid"), rt.IntValue(int64(j.pid)))
|
|
|
|
luaJob.Set(rt.StringValue("exitCode"), rt.IntValue(int64(j.exitCode)))
|
2022-03-19 17:10:50 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return rt.TableValue(luaJob)
|
2022-03-19 17:10:50 +00:00
|
|
|
}
|
|
|
|
|
2022-05-13 23:46:25 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-03-20 23:10:12 +00:00
|
|
|
if j.running {
|
|
|
|
j.stop()
|
2022-05-13 02:27:49 +00:00
|
|
|
j.finish()
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-03-19 17:10:50 +00:00
|
|
|
type jobHandler struct {
|
|
|
|
jobs map[int]*job
|
|
|
|
latestID int
|
|
|
|
mu *sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func newJobHandler() *jobHandler {
|
|
|
|
return &jobHandler{
|
|
|
|
jobs: make(map[int]*job),
|
|
|
|
latestID: 0,
|
|
|
|
mu: &sync.RWMutex{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-13 23:46:25 +00:00
|
|
|
func (j *jobHandler) add(cmd string, args []string, path string) *job {
|
2022-03-19 17:10:50 +00:00
|
|
|
j.mu.Lock()
|
|
|
|
defer j.mu.Unlock()
|
|
|
|
|
|
|
|
j.latestID++
|
2022-05-13 21:08:15 +00:00
|
|
|
jb := &job{
|
2022-03-19 17:10:50 +00:00
|
|
|
cmd: cmd,
|
|
|
|
running: false,
|
|
|
|
id: j.latestID,
|
2022-05-13 23:46:25 +00:00
|
|
|
args: args,
|
2022-05-17 22:05:16 +00:00
|
|
|
path: path,
|
2022-05-17 21:43:42 +00:00
|
|
|
stdin: os.Stdin,
|
|
|
|
stdout: os.Stdout,
|
|
|
|
stderr: os.Stderr,
|
2022-03-19 17:10:50 +00:00
|
|
|
}
|
2022-05-13 21:08:15 +00:00
|
|
|
j.jobs[j.latestID] = jb
|
2022-05-17 22:08:27 +00:00
|
|
|
hooks.Em.Emit("job.add", jb.lua())
|
2022-05-13 21:08:15 +00:00
|
|
|
|
|
|
|
return jb
|
2022-03-19 17:10:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (j *jobHandler) getLatest() *job {
|
|
|
|
j.mu.RLock()
|
|
|
|
defer j.mu.RUnlock()
|
|
|
|
|
|
|
|
return j.jobs[j.latestID]
|
|
|
|
}
|
2022-03-20 23:10:12 +00:00
|
|
|
|
2022-05-21 15:37:19 +00:00
|
|
|
func (j *jobHandler) disown(id int) error {
|
|
|
|
j.mu.RLock()
|
|
|
|
if j.jobs[id] == nil {
|
|
|
|
return errors.New("job doesnt exist")
|
|
|
|
}
|
|
|
|
j.mu.RUnlock()
|
|
|
|
|
|
|
|
j.mu.Lock()
|
|
|
|
delete(j.jobs, id)
|
|
|
|
j.mu.Unlock()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (j *jobHandler) stopAll() {
|
|
|
|
j.mu.RLock()
|
|
|
|
defer j.mu.RUnlock()
|
|
|
|
|
|
|
|
for _, jb := range j.jobs {
|
|
|
|
// on exit, unix shell should send sighup to all jobs
|
|
|
|
if jb.running {
|
|
|
|
proc := jb.getProc()
|
|
|
|
proc.Signal(syscall.SIGHUP)
|
|
|
|
jb.wait() // waits for program to exit due to sighup
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
|
|
|
|
jobFuncs := map[string]util.LuaExport{
|
|
|
|
"all": {j.luaAllJobs, 0, false},
|
|
|
|
"get": {j.luaGetJob, 1, false},
|
2022-05-17 22:00:28 +00:00
|
|
|
"add": {j.luaAddJob, 2, false},
|
2022-05-21 15:37:19 +00:00
|
|
|
"disown": {j.luaDisownJob, 1, false},
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
luaJob := rt.NewTable()
|
|
|
|
util.SetExports(rtm, luaJob, jobFuncs)
|
2022-03-20 23:10:12 +00:00
|
|
|
|
|
|
|
return luaJob
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-03-20 23:10:12 +00:00
|
|
|
j.mu.RLock()
|
|
|
|
defer j.mu.RUnlock()
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
jobID, err := c.IntArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
job := j.jobs[int(jobID)]
|
|
|
|
if job == nil {
|
|
|
|
return c.Next(), nil
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.PushingNext1(t.Runtime, job.lua()), nil
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-05-13 21:08:15 +00:00
|
|
|
func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-05-17 22:00:28 +00:00
|
|
|
if err := c.CheckNArgs(2); err != nil {
|
2022-05-13 21:08:15 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cmd, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-05-13 23:46:25 +00:00
|
|
|
largs, err := c.TableArg(1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-05-13 21:08:15 +00:00
|
|
|
|
2022-05-13 23:46:25 +00:00
|
|
|
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])
|
2022-05-13 21:08:15 +00:00
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, jb.lua()), nil
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-03-20 23:10:12 +00:00
|
|
|
j.mu.RLock()
|
|
|
|
defer j.mu.RUnlock()
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
jobTbl := rt.NewTable()
|
2022-03-20 23:10:12 +00:00
|
|
|
for id, job := range j.jobs {
|
2022-04-04 10:40:02 +00:00
|
|
|
jobTbl.Set(rt.IntValue(int64(id)), job.lua())
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil
|
2022-03-20 23:10:12 +00:00
|
|
|
}
|
2022-05-21 15:37:19 +00:00
|
|
|
|
|
|
|
func (j *jobHandler) luaDisownJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
jobID, err := c.IntArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = j.disown(int(jobID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.Next(), nil
|
|
|
|
}
|