/* SHS: Syntactically Homogeneous Shell * Copyright (C) 2019 Aidan Hahn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package stdlib import ( "os" "fmt" "bytes" "strconv" "os/exec" "syscall" "os/signal" "gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/log" ) var bgProcs = make([]*exec.Cmd, 0) var LastExitCode int var sigs = []os.Signal{ os.Interrupt, syscall.SIGTERM, syscall.SIGTSTP, syscall.SIGTTIN, syscall.SIGTTOU, syscall.SIGCONT, } func call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { in = in.Eval(ft, vt, true) if in == nil { return nil } if in.Tag == ast.LIST { log.Log(log.ERR, "couldnt exec, target bin is a list", "call") return nil } path, err := exec.LookPath(in.Value()) if err != nil { log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call") return nil } args := []string{} for i := in.Next; i != nil; i = i.Next { if i.Tag == ast.LIST { log.Log(log.ERR, "Couldnt exec " + path + ", element in arguments is a list", "call") return nil } args = append(args, i.Value()) } var cmd *exec.Cmd if len(args) > 0 { cmd = exec.Command(path, args...) } else { cmd = exec.Command(path) } cmd.Env = os.Environ() cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr 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 unparsable error: " + err.Error(), "call") } } return nil } func bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { if in == nil { return nil } if in.Tag == ast.LIST { log.Log(log.ERR, "couldnt exec, target bin is a list", "call") return nil } path, err := exec.LookPath(in.Value()) if err != nil { log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call") return nil } args := []string{} for i := in.Next; i != nil; i = i.Next { if i.Tag == ast.LIST { log.Log(log.ERR, "Couldnt exec " + path + ", element in arguments is a list", "call") return nil } args = append(args, i.Value()) } var cmd *exec.Cmd if len(args) > 0 { cmd = exec.Command(path, args...) } else { cmd = exec.Command(path) } cmd.Stderr = os.Stderr bgProcs = append(bgProcs, cmd) cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr cmd.Start() cmd.Process.Signal(syscall.SIGTSTP) return nil } func fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { if len(bgProcs) < 1 { return nil } cmd := bgProcs[0] bgProcs = bgProcs[1:] signalChan := make(chan os.Signal, 2) signal.Notify(signalChan, sigs...) go func() { sig := <-signalChan cmd.Process.Signal(sig) }() cmd.Process.Signal(syscall.SIGCONT) err := cmd.Wait() 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(), "call") } } return nil } func jobs(in *ast.Token, vt ast.VarTable, fg ast.FuncTable) *ast.Token { ret := &ast.Token{ Tag: ast.LIST, } _inner := &ast.Token{ Tag: ast.STRING, } ret.Direct(_inner) _inner.Set(fmt.Sprintf("Total: %d", len(bgProcs))) iter := &_inner for i := 0; i < len(bgProcs); i += 1 { (*iter).Next = &ast.Token{ Tag: ast.STRING, } (*iter).Next.Set(fmt.Sprintf("[%d]: %d", i, bgProcs[i].Process.Pid)) iter = &(*iter).Next } return ret } func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { in = in.Eval(ft, vt, true) if in == nil { return nil } if in.Tag == ast.LIST { log.Log(log.ERR, "couldnt exec, target bin is a list", "call") return nil } var out bytes.Buffer path, err := exec.LookPath(in.Value()) if err != nil { log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call") return nil } args := []string{} for i := in.Next; i != nil; i = i.Next { if i.Tag == ast.LIST { log.Log(log.ERR, "Couldnt exec " + path + ", element in arguments is a list", "call") return nil } 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(), "$") } } output := out.String() olen := len(output) if olen > 0 && output[olen - 1] == '\n' { output = output[:olen - 1] } ret := &ast.Token{Tag: ast.STRING} ret.Set(output) return ret } func get_exit(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { ret := &ast.Token{Tag: ast.NUMBER} ret.Set(fmt.Sprintf("%d", LastExitCode)) return ret } func kill(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { in = in.Eval(ft, vt, true) if in.Tag == ast.LIST { log.Log(log.ERR, "non-number argument to kill function", "kill") return nil } pid, err := strconv.ParseInt(in.Value(), 10, 64) if err != nil { log.Log(log.ERR, "error parsing arg to kill: " + err.Error(), "kill") 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") } } return nil }