Merge branch 'dev' into 'master'

Proper Job Control

Closes #9 and #1

See merge request whom/shs!9
This commit is contained in:
Aidan Hahn 2020-07-24 02:59:08 +00:00
commit ed20c03363
14 changed files with 332 additions and 158 deletions

View file

@ -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.

View file

@ -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

View file

@ -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
View 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()
}

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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

Binary file not shown.

View file

@ -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

View file

@ -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
} }

View file

@ -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 { // 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
} }

View file

@ -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{

View file

@ -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)
} }
} }