├── .gitignore ├── LICENSE ├── README.md ├── cmds └── kexec │ └── kexec.go ├── examples ├── example.go └── flask_main.py ├── kexec.go ├── kexec_posix.go ├── kexec_test.go ├── kexec_windows.go └── tests ├── Procfile ├── flask_main.py ├── main.go └── tests /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 codeskyblue 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kexec 2 | [![GoDoc](https://godoc.org/github.com/codeskyblue/kexec?status.svg)](https://godoc.org/github.com/codeskyblue/kexec) 3 | 4 | This is a golang lib, add a `Terminate` command to exec. 5 | 6 | Tested on _windows, linux, darwin._ 7 | 8 | This lib has been used in [fswatch](https://github.com/codeskyblue/fswatch). 9 | 10 | ## Usage 11 | 12 | ``` 13 | go get -v github.com/codeskyblue/kexec 14 | ``` 15 | 16 | 17 | example1: 18 | 19 | ```go 20 | package main 21 | 22 | import "github.com/codeskyblue/kexec" 23 | 24 | func main(){ 25 | p := kexec.Command("python", "flask_main.py") 26 | p.Start() 27 | p.Terminate(syscall.SIGINT) 28 | } 29 | ``` 30 | 31 | example2: see more [examples](examples) 32 | 33 | ```go 34 | package main 35 | 36 | import ( 37 | "github.com/codeskyblue/kexec" 38 | ) 39 | 40 | func main() { 41 | // In unix will call: bash -c "python flask_main.py" 42 | // In windows will call: cmd /c "python flask_main.py" 43 | p := kexec.CommandString("python flask_main.py") 44 | p.Stdout = os.Stdout 45 | p.Stderr = os.Stderr 46 | p.Start() 47 | p.Terminate(syscall.SIGKILL) 48 | } 49 | ``` 50 | 51 | example3: 52 | 53 | ```go 54 | package main 55 | 56 | import "github.com/codeskyblue/kexec" 57 | 58 | func main() { 59 | p := kexec.Command("whoami") 60 | p.SetUser("codeskyblue") // Only works on darwin and linux 61 | p.Run() 62 | } 63 | ``` 64 | 65 | ## Command line usage 66 | ``` 67 | $ go get -v github.com/codeskyblue/kexec/cmds/kexec 68 | $ kexec python main.py 69 | # Ctrl+C 70 | python is terminating ... 71 | ``` 72 | 73 | ## PS 74 | This lib also support you call `Wait()` twice, which is not support by `os/exec` 75 | 76 | ## LICENSE 77 | [MIT](LICENSE) 78 | -------------------------------------------------------------------------------- /cmds/kexec/kexec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Tornado ignore signal CTRL-C, so I write this program 3 | */ 4 | package main 5 | 6 | import ( 7 | "log" 8 | "os" 9 | "os/signal" 10 | "strings" 11 | "syscall" 12 | 13 | "github.com/codeskyblue/kexec" 14 | ) 15 | 16 | func main() { 17 | cmd := kexec.CommandString(strings.Join(os.Args[1:], " ")) //(os.Args[1], os.Args[1:]...) 18 | cmd.Stdout = os.Stdout 19 | cmd.Stderr = os.Stderr 20 | cmd.Stdin = os.Stdin 21 | if err := cmd.Start(); err != nil { 22 | log.Fatal(err) 23 | } 24 | sigC := make(chan os.Signal, 1) 25 | signal.Notify(sigC, os.Interrupt) 26 | 27 | go func() { 28 | for _ = range sigC { 29 | log.Println("Signal interrupt catched. terminate") 30 | cmd.Terminate(syscall.SIGKILL) 31 | } 32 | }() 33 | 34 | if err := cmd.Wait(); err != nil { 35 | log.Fatal(err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "syscall" 6 | "time" 7 | 8 | "github.com/codeskyblue/kexec" 9 | ) 10 | 11 | func main() { 12 | p := kexec.CommandString("python flask_main.py") 13 | p.Start() 14 | time.Sleep(3 * time.Second) 15 | err := p.Terminate(syscall.SIGKILL) 16 | if err != nil { 17 | log.Println(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/flask_main.py: -------------------------------------------------------------------------------- 1 | import flask 2 | 3 | app = flask.Flask(__name__) 4 | 5 | if __name__ == '__main__': 6 | app.run(port=46732, debug=True) 7 | -------------------------------------------------------------------------------- /kexec.go: -------------------------------------------------------------------------------- 1 | package kexec 2 | 3 | import ( 4 | "errors" 5 | "os/exec" 6 | "sync" 7 | ) 8 | 9 | type KCommand struct { 10 | *exec.Cmd 11 | 12 | errCs []chan error 13 | err error 14 | finished bool 15 | once sync.Once 16 | mu sync.Mutex 17 | } 18 | 19 | func (c *KCommand) Run() error { 20 | if err := c.Start(); err != nil { 21 | return err 22 | } 23 | return c.Wait() 24 | } 25 | 26 | // This Wait wraps exec.Wait, but support multi call 27 | func (k *KCommand) Wait() error { 28 | if k.Process == nil { 29 | return errors.New("exec: not started") 30 | } 31 | k.once.Do(func() { 32 | if k.errCs == nil { 33 | k.errCs = make([]chan error, 0) 34 | } 35 | go func() { 36 | k.err = k.Cmd.Wait() 37 | k.mu.Lock() 38 | k.finished = true 39 | for _, errC := range k.errCs { 40 | errC <- k.err 41 | } 42 | k.mu.Unlock() 43 | }() 44 | }) 45 | k.mu.Lock() 46 | if k.finished { 47 | k.mu.Unlock() 48 | return k.err 49 | } 50 | errC := make(chan error, 1) 51 | k.errCs = append(k.errCs, errC) 52 | k.mu.Unlock() 53 | return <-errC 54 | } 55 | -------------------------------------------------------------------------------- /kexec_posix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package kexec 4 | 5 | import ( 6 | "os" 7 | "os/exec" 8 | "os/user" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | func setupCmd(cmd *exec.Cmd) { 14 | cmd.SysProcAttr = &syscall.SysProcAttr{} 15 | cmd.SysProcAttr.Setsid = true 16 | } 17 | 18 | func Command(name string, arg ...string) *KCommand { 19 | cmd := exec.Command(name, arg...) 20 | setupCmd(cmd) 21 | return &KCommand{ 22 | Cmd: cmd, 23 | } 24 | } 25 | 26 | func CommandString(command string) *KCommand { 27 | cmd := exec.Command("/bin/bash", "-c", command) 28 | setupCmd(cmd) 29 | //cmd.Stdout = os.Stdout 30 | //cmd.Stderr = os.Stderr 31 | return &KCommand{ 32 | Cmd: cmd, 33 | } 34 | } 35 | 36 | func (p *KCommand) Terminate(sig os.Signal) (err error) { 37 | if p.Process == nil { 38 | return 39 | } 40 | // find pgid, ref: http://unix.stackexchange.com/questions/14815/process-descendants 41 | group, err := os.FindProcess(-1 * p.Process.Pid) 42 | //log.Println(group) 43 | if err == nil { 44 | err = group.Signal(sig) 45 | } 46 | return err 47 | } 48 | 49 | // Ref: http://stackoverflow.com/questions/21705950/running-external-commands-through-os-exec-under-another-user 50 | func (k *KCommand) SetUser(name string) (err error) { 51 | u, err := user.Lookup(name) 52 | if err != nil { 53 | return err 54 | } 55 | uid, err := strconv.Atoi(u.Uid) 56 | if err != nil { 57 | return err 58 | } 59 | gid, err := strconv.Atoi(u.Gid) 60 | if err != nil { 61 | return err 62 | } 63 | if k.SysProcAttr == nil { 64 | k.SysProcAttr = &syscall.SysProcAttr{} 65 | } 66 | k.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /kexec_test.go: -------------------------------------------------------------------------------- 1 | package kexec 2 | 3 | import ( 4 | "os" 5 | "os/user" 6 | "syscall" 7 | "testing" 8 | "time" 9 | 10 | . "github.com/smartystreets/goconvey/convey" 11 | ) 12 | 13 | func TestCommand(t *testing.T) { 14 | Convey("1 should equal 1", t, func() { 15 | So(1, ShouldEqual, 1) 16 | }) 17 | 18 | Convey("kexec should work as normal os/exec", t, func() { 19 | cmd := Command("echo", "-n", "123") 20 | data, err := cmd.Output() 21 | So(err, ShouldBeNil) 22 | So(string(data), ShouldEqual, "123") 23 | }) 24 | 25 | Convey("the terminate should kill proc", t, func() { 26 | cmd := CommandString("sleep 51") 27 | cmd.Stdout = os.Stdout 28 | cmd.Stderr = os.Stderr 29 | cmd.Start() 30 | time.Sleep(time.Millisecond * 50) 31 | cmd.Terminate(syscall.SIGINT) 32 | err := cmd.Wait() 33 | So(err, ShouldNotBeNil) 34 | //So(err.Error(), ShouldEqual, "signal: interrupt") 35 | }) 36 | 37 | Convey("Should ok with call Wait twice", t, func() { 38 | cmd := CommandString("not-exists-command-xxl213 true") 39 | var err error 40 | err = cmd.Start() 41 | So(err, ShouldBeNil) 42 | 43 | err1 := cmd.Wait() 44 | So(err1, ShouldNotBeNil) 45 | err2 := cmd.Wait() 46 | So(err1, ShouldEqual, err2) 47 | }) 48 | 49 | Convey("Set user works", t, func() { 50 | u, err := user.Current() 51 | So(err, ShouldBeNil) 52 | // Set user must be root 53 | if u.Uid != "0" { 54 | return 55 | } 56 | 57 | cmd := Command("whoami") 58 | err = cmd.SetUser("qard2") 59 | So(err, ShouldBeNil) 60 | 61 | output, err := cmd.Output() 62 | So(err, ShouldBeNil) 63 | So(string(output), ShouldEqual, "qard2\n") 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /kexec_windows.go: -------------------------------------------------------------------------------- 1 | package kexec 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/exec" 7 | "strconv" 8 | ) 9 | 10 | func Command(name string, arg ...string) *KCommand { 11 | return &KCommand{ 12 | Cmd: exec.Command(name, arg...), 13 | } 14 | } 15 | 16 | func CommandString(command string) *KCommand { 17 | cmd := exec.Command("cmd", "/c", command) 18 | //cmd.Stdout = os.Stdout 19 | //cmd.Stderr = os.Stderr 20 | return &KCommand{ 21 | Cmd: cmd, 22 | } 23 | } 24 | 25 | func (p *KCommand) Terminate(sig os.Signal) (err error) { 26 | if p.Process == nil { 27 | return nil 28 | } 29 | pid := p.Process.Pid 30 | c := exec.Command("taskkill", "/t", "/f", "/pid", strconv.Itoa(pid)) 31 | c.Stdout = os.Stdout 32 | c.Stderr = os.Stderr 33 | return c.Run() 34 | } 35 | 36 | // SetUser not support on windws 37 | func (k *KCommand) SetUser(name string) (err error) { 38 | log.Printf("Can not set user(%s) on windows", name) 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /tests/Procfile: -------------------------------------------------------------------------------- 1 | web: python flask_main.py 2 | -------------------------------------------------------------------------------- /tests/flask_main.py: -------------------------------------------------------------------------------- 1 | import flask 2 | 3 | 4 | app = flask.Flask(__name__) 5 | 6 | @app.route('/') 7 | def homepage(): 8 | return 'Home' 9 | 10 | if __name__ == '__main__': 11 | app.run(debug=True, host='0.0.0.0') -------------------------------------------------------------------------------- /tests/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "kproc" 6 | "log" 7 | "os/exec" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | p := kproc.ProcString("python flask_main.py") 14 | p.Start() 15 | time.Sleep(10 * time.Second) 16 | err := p.Terminate(syscall.SIGKILL) 17 | if err != nil { 18 | log.Println(err) 19 | } 20 | out, _ := exec.Command("lsof", "-i:5000").CombinedOutput() 21 | fmt.Println(string(out)) 22 | } 23 | -------------------------------------------------------------------------------- /tests/tests: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeskyblue/kexec/5a4bed90d99a42c283dafbc64d94a6a8f372f949/tests/tests --------------------------------------------------------------------------------