Merge branch 'dev' into 'master'
Proper Job Control Closes #9 and #1 See merge request whom/shs!9
This commit is contained in:
commit
ed20c03363
14 changed files with 332 additions and 158 deletions
11
Readme.md
11
Readme.md
|
|
@ -2,7 +2,7 @@
|
||||||
Syntactically Homogeneous Shell
|
Syntactically Homogeneous Shell
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users.
|
This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users. It can be used for both system administration (as one might use bash or zsh) as well as for the creation of simple programs.
|
||||||
|
|
||||||
## Basic Syntax
|
## Basic Syntax
|
||||||
When in doubt the `print_ast` utility can be used to examine the output of the Lex+Parse process. Here you can spot any bugs regarding syntax.
|
When in doubt the `print_ast` utility can be used to examine the output of the Lex+Parse process. Here you can spot any bugs regarding syntax.
|
||||||
|
|
@ -94,9 +94,16 @@ Here is an example of a shs configuration file:
|
||||||
(func _SH_PROMPT () (concat (?) ($ basename ($ pwd)) "\n"))
|
(func _SH_PROMPT () (concat (?) ($ basename ($ pwd)) "\n"))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## The Docs
|
||||||
|
- [Documentation on language interpreter](https://godoc.org/gitlab.com/whom/shs/ast)
|
||||||
|
- [Documentation on logging module](https://godoc.org/gitlab.com/whom/shs/log)
|
||||||
|
- [Documentation on configuration module](https://godoc.org/gitlab.com/whom/shs/config)
|
||||||
|
- [Documentation on utility functions](https://godoc.org/gitlab.com/whom/shs/util)
|
||||||
|
- [Documentation on standard library](https://godoc.org/gitlab.com/whom/shs/stdlib)
|
||||||
|
- [Guide to standard library](https://gitlab.com/whom/shs/-/blob/master/stdlib/Readme.md)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
- Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository.
|
- Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository.
|
||||||
- Consider reading the [STDLIB Readme](https://git.callpipe.com/aidan/shs/-/blob/master/stdlib/Readme.md) for more information on how to extend this project.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Copyright (C) 2019 Aidan Hahn.
|
Copyright (C) 2019 Aidan Hahn.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@
|
||||||
|
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import "gitlab.com/whom/shs/log"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
)
|
||||||
|
|
||||||
/* expected function header for any stdlib function
|
/* expected function header for any stdlib function
|
||||||
*/
|
*/
|
||||||
|
|
@ -63,6 +66,10 @@ func (f Function) ParseFunction(args *Token) bool {
|
||||||
log.Log(log.ERR,
|
log.Log(log.ERR,
|
||||||
"Incorrect number of arguments",
|
"Incorrect number of arguments",
|
||||||
"eval")
|
"eval")
|
||||||
|
log.Log(log.DEBUG,
|
||||||
|
fmt.Sprintf("Function %s expects %d arguments. You've provided %d arguments.",
|
||||||
|
f.Name, f.Args, f.Args - i),
|
||||||
|
"eval")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +96,7 @@ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token {
|
||||||
func GetFunction(arg string, table FuncTable) *Function {
|
func GetFunction(arg string, table FuncTable) *Function {
|
||||||
target, ok := (*table)[arg]
|
target, ok := (*table)[arg]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Log(log.DEBUG,
|
log.Log(log.INFO,
|
||||||
"function " + arg + " not found",
|
"function " + arg + " not found",
|
||||||
"ftable")
|
"ftable")
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
14
cmd/shs.go
14
cmd/shs.go
|
|
@ -21,10 +21,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"github.com/peterh/liner"
|
"github.com/candid82/liner"
|
||||||
"gitlab.com/whom/shs/ast"
|
"gitlab.com/whom/shs/ast"
|
||||||
"gitlab.com/whom/shs/log"
|
"gitlab.com/whom/shs/log"
|
||||||
"gitlab.com/whom/shs/util"
|
"gitlab.com/whom/shs/util"
|
||||||
|
"gitlab.com/whom/shs/stdlib"
|
||||||
"gitlab.com/whom/shs/config"
|
"gitlab.com/whom/shs/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -77,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 {
|
||||||
|
|
@ -105,6 +109,12 @@ func main() {
|
||||||
line := liner.NewLiner()
|
line := liner.NewLiner()
|
||||||
defer line.Close()
|
defer line.Close()
|
||||||
|
|
||||||
|
if !liner.TerminalSupported() {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"Terminal unsupported, continuing in dummy mode!",
|
||||||
|
"init")
|
||||||
|
}
|
||||||
|
|
||||||
line.SetCtrlCAborts(true)
|
line.SetCtrlCAborts(true)
|
||||||
line.SetCompleter(func(line string) (c []string) {
|
line.SetCompleter(func(line string) (c []string) {
|
||||||
return util.ShellCompleter(line, vars, funcs)
|
return util.ShellCompleter(line, vars, funcs)
|
||||||
|
|
@ -126,6 +136,8 @@ func main() {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
setLogLvl(vars)
|
setLogLvl(vars)
|
||||||
|
stdlib.CheckBGProcs()
|
||||||
|
|
||||||
var prePrompt string
|
var prePrompt string
|
||||||
if dyn_prompt != nil {
|
if dyn_prompt != nil {
|
||||||
p_tok := dyn_prompt.CallFunction(nil, vars, funcs)
|
p_tok := dyn_prompt.CallFunction(nil, vars, funcs)
|
||||||
|
|
|
||||||
36
cmd/test_shell.go
Normal file
36
cmd/test_shell.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
"gitlab.com/whom/shs/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetLogLvl(3)
|
||||||
|
stdlib.InitShellFeatures()
|
||||||
|
stdlib.LaunchProcess("/usr/bin/vim", []string{"test.txt"}, false)
|
||||||
|
|
||||||
|
fmt.Println("process started, now sleeping for 10 sec")
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
stdlib.TeardownShell()
|
||||||
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ func InitFromConfig(configFile string) (ast.VarTable, ast.FuncTable) {
|
||||||
|
|
||||||
util.LoadScript(configFile, vars, funcs)
|
util.LoadScript(configFile, vars, funcs)
|
||||||
|
|
||||||
log.Log(log.DEBUG,
|
log.Log(log.INFO,
|
||||||
"config file fully evaluated",
|
"config file fully evaluated",
|
||||||
"config")
|
"config")
|
||||||
return vars, funcs
|
return vars, funcs
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -3,6 +3,8 @@ module gitlab.com/whom/shs
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/candid82/liner v1.4.0 // indirect
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||||
github.com/peterh/liner v1.2.0 // indirect
|
github.com/peterh/liner v1.2.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666
|
||||||
)
|
)
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -1,6 +1,10 @@
|
||||||
|
github.com/candid82/liner v1.4.0 h1:nUhs4pv/cnpnBERwJHmqmgargZTWnPbDJ67HtQcfSTo=
|
||||||
|
github.com/candid82/liner v1.4.0/go.mod h1:shD5EWTOYasmaGjMfuaB82N9YxGMIAEoXjQEH6RoGvo=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/peterh/liner v1.2.0 h1:w/UPXyl5GfahFxcTOz2j9wCIHNI+pUPr2laqpojKNCg=
|
github.com/peterh/liner v1.2.0 h1:w/UPXyl5GfahFxcTOz2j9wCIHNI+pUPr2laqpojKNCg=
|
||||||
github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
|
github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8=
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
|
||||||
|
|
@ -51,5 +51,5 @@ func Log(lvl int, msg, context string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[" + context + "] " + msg)
|
fmt.Println("[" + context + "]\033[3m " + msg + "\033[0m ")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
resources/icon/icon.kra
Normal file
BIN
resources/icon/icon.kra
Normal file
Binary file not shown.
|
|
@ -45,8 +45,8 @@ func ShsProgn(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
* (if (eq (number "3") 3) (print "test passed") (print "test failed"))
|
* (if (eq (number "3") 3) (print "test passed") (print "test failed"))
|
||||||
*/
|
*/
|
||||||
func ShsIf(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
func ShsIf(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
cond := in
|
cond := in.Copy()
|
||||||
t := cond.Next
|
t := cond.Next.Copy()
|
||||||
f := t.Next
|
f := t.Next
|
||||||
cond.Next = nil
|
cond.Next = nil
|
||||||
t.Next = nil
|
t.Next = nil
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
*/
|
*/
|
||||||
func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token {
|
func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token {
|
||||||
if input.Tag != ast.LIST {
|
if input.Tag != ast.LIST {
|
||||||
log.Log(log.DEBUG, "expand called on not a list", "expand")
|
log.Log(log.INFO, "expand called on not a list", "expand")
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,30 +19,205 @@ package stdlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"io"
|
||||||
"fmt"
|
"fmt"
|
||||||
"bytes"
|
"bytes"
|
||||||
"strconv"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"context"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"strconv"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
"gitlab.com/whom/shs/ast"
|
"gitlab.com/whom/shs/ast"
|
||||||
"gitlab.com/whom/shs/log"
|
"gitlab.com/whom/shs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bgProcs = make([]*exec.Cmd, 0)
|
var sigChan chan os.Signal
|
||||||
var sigs = []os.Signal{
|
var waitChan chan error
|
||||||
os.Interrupt,
|
|
||||||
syscall.SIGTERM,
|
/* mapping of pid to Proc
|
||||||
syscall.SIGTSTP,
|
*/
|
||||||
syscall.SIGTTIN,
|
var JobMap = map[int]Proc{}
|
||||||
syscall.SIGTTOU,
|
|
||||||
syscall.SIGCONT,
|
/* id of shell process group
|
||||||
|
*/
|
||||||
|
var pgid, pid int
|
||||||
|
|
||||||
|
/* cancel func for current running process
|
||||||
|
*/
|
||||||
|
var CurCancel *context.CancelFunc
|
||||||
|
|
||||||
|
|
||||||
|
/* Holds an os/exec Cmd object and its context cancel function
|
||||||
|
*/
|
||||||
|
type Proc struct {
|
||||||
|
Ctl *exec.Cmd
|
||||||
|
Cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
var catchMe = []os.Signal{
|
||||||
|
syscall.SIGINT,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Exit code of last run process
|
/* Exit code of last run process
|
||||||
*/
|
*/
|
||||||
var LastExitCode int
|
var LastExitCode int
|
||||||
|
|
||||||
|
// Job control handlers
|
||||||
|
func signalHandler() {
|
||||||
|
for {
|
||||||
|
<- sigChan
|
||||||
|
log.Log(log.DEBUG,
|
||||||
|
"caught SIGINT",
|
||||||
|
"jobctl")
|
||||||
|
|
||||||
|
if CurCancel != nil {
|
||||||
|
(*CurCancel)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, syscall.SIGQUIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets pgid
|
||||||
|
* Installs a sigchld handler
|
||||||
|
* returns true if success, false on error
|
||||||
|
*/
|
||||||
|
func InitShellFeatures() bool {
|
||||||
|
// TODO: adjust capacity, make configurable maybe?
|
||||||
|
sigChan = make(chan os.Signal, 5)
|
||||||
|
waitChan = make(chan error, 5)
|
||||||
|
|
||||||
|
go signalHandler()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tears down session
|
||||||
|
*/
|
||||||
|
func TeardownShell() {
|
||||||
|
close(sigChan)
|
||||||
|
close(waitChan)
|
||||||
|
for k := range JobMap {
|
||||||
|
JobMap[k].Cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check background processes states
|
||||||
|
* If any have finished, reap them
|
||||||
|
*/
|
||||||
|
func CheckBGProcs() {
|
||||||
|
for key, proc := range JobMap {
|
||||||
|
maybeFilePath := fmt.Sprintf("/proc/%d", key)
|
||||||
|
_, err := os.Stat(maybeFilePath)
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if err := proc.Ctl.Process.Release(); err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
fmt.Sprintf("Failed to release exited process %d: %s",
|
||||||
|
key, err.Error()),
|
||||||
|
"jobctl")
|
||||||
|
} else {
|
||||||
|
// not actually an error, just abusing default visibility
|
||||||
|
log.Log(log.ERR,
|
||||||
|
fmt.Sprintf("Child [%d] exited", key),
|
||||||
|
"jobctl")
|
||||||
|
delete(JobMap, key)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
log.Log(log.DEBUG,
|
||||||
|
"Error checking if process exists in proc: " + err.Error(),
|
||||||
|
"jobctl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Makes and stores a new process in the job control
|
||||||
|
* uses os/exec Cmd object. sets SysProcAttr.Foreground
|
||||||
|
* calls Cmd.Start()
|
||||||
|
*/
|
||||||
|
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{Setpgid: true}
|
||||||
|
|
||||||
|
if !background {
|
||||||
|
cmd.SysProcAttr.Foreground = true
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
cpid := cmd.Process.Pid
|
||||||
|
|
||||||
|
// TODO: check for clobber?
|
||||||
|
// unlikely, but PIDs do reset after a while
|
||||||
|
JobMap[cpid] = Proc{ Ctl: cmd, Cancel: cancel }
|
||||||
|
CurCancel = &cancel
|
||||||
|
|
||||||
|
if !background {
|
||||||
|
cmd.Wait()
|
||||||
|
tcsetpgrp(0, pgid)
|
||||||
|
delete(JobMap, cpid)
|
||||||
|
CurCancel = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Takes n arguments (list of tokens generated by lexing a shell command)
|
/* Takes n arguments (list of tokens generated by lexing a shell command)
|
||||||
* Evaluates arguments, but does not err on undefined symbols (note the last arg to Eval(...))
|
* Evaluates arguments, but does not err on undefined symbols (note the last arg to Eval(...))
|
||||||
* Executes shell command and returns nil
|
* Executes shell command and returns nil
|
||||||
|
|
@ -76,35 +251,7 @@ func Call(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, nil, nil)
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,6 +261,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
|
||||||
}
|
}
|
||||||
|
|
@ -139,64 +287,61 @@ func Bgcall(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, true, nil, nil, nil)
|
||||||
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
|
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...)
|
|
||||||
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 err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
log.Log(log.ERR,
|
||||||
LastExitCode = exitError.ExitCode()
|
"value supplied to fg could not be cast to float",
|
||||||
} else {
|
"fg")
|
||||||
log.Log(log.ERR, "Execution step returned error: " + err.Error(), "call")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tcsetpgrp(0, ipid); err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"Error foregrounding process: " + err.Error(),
|
||||||
|
"fg")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
proc.Ctl.Wait()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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)
|
||||||
|
|
@ -208,22 +353,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,7 +385,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")
|
||||||
|
|
@ -261,34 +402,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' {
|
||||||
|
|
@ -333,33 +447,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 {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this doesnt work do proc.Ctl.Kill()
|
||||||
|
proc.Cancel()
|
||||||
|
delete(JobMap, int(pid))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -238,7 +238,7 @@ func GenFuncTable() ast.FuncTable {
|
||||||
Function: Fg,
|
Function: Fg,
|
||||||
Name: "foreground",
|
Name: "foreground",
|
||||||
TimesCalled: 0,
|
TimesCalled: 0,
|
||||||
Args: 0,
|
Args: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
"$": &ast.Function{
|
"$": &ast.Function{
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func ShellCompleter(line string, vt ast.VarTable, ft ast.FuncTable) []string {
|
||||||
|
|
||||||
fobjs, err := ioutil.ReadDir(dir)
|
fobjs, err := ioutil.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.DEBUG,
|
log.Log(log.INFO,
|
||||||
"couldnt read dir " + dir + ": " + err.Error(),
|
"couldnt read dir " + dir + ": " + err.Error(),
|
||||||
"complete")
|
"complete")
|
||||||
if path {
|
if path {
|
||||||
|
|
@ -59,7 +59,16 @@ func ShellCompleter(line string, vt ast.VarTable, ft ast.FuncTable) []string {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, f := range fobjs {
|
for _, f := range fobjs {
|
||||||
compSource = append(compSource, dir + "/" + f.Name())
|
fileTok := f.Name()
|
||||||
|
if dir != "." {
|
||||||
|
fileTok = dir + "/" + fileTok
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
fileTok = fileTok + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
compSource = append(compSource, fileTok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue