For fun and to better learn Go, I'm trying to re-implement antigen in Go.
Problem is: source is a shell built-in function, so I can't call it with os/exec Command function, because it expects an executable in PATH.
How can I do this? And, is it possible to make a source from inside a go program affect the user shell?
You can write the command directly in the terminal device. But, to do that, first you need to know which device is using the user. A script that executes your program can be a solution.
#!/bin/bash
echo Running from foo script, pid = $$
go run foo.go `tty`
Then, the program has to write the commands to the terminal device.
package main
import (
"C"
"fmt"
"os"
"syscall"
"unsafe"
)
func main() {
// Get tty path
if len(os.Args) < 2 {
fmt.Printf("no tty path\n")
os.Exit(1)
}
ttyPath := os.Args[1]
// Open tty
tty, err := os.Open(ttyPath)
if err != nil {
fmt.Printf("error opening tty: %s\n", err.Error())
os.Exit(2)
}
defer tty.Close()
// Write a command
cmd := "echo Hello from go, pid = $$\n"
cmdstr := C.CString(cmd)
cmdaddr := uintptr(unsafe.Pointer(cmdstr))
for i := range []byte(cmd) {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCSTI, cmdaddr+uintptr(i))
if uintptr(err) != 0 {
fmt.Printf("syscall error: %s\n", err.Error())
os.Exit(3)
}
}
}
Here is an example output:
$ echo $$
70318
$ ./foo
Running from foo script, pid = 83035
echo Hello from go, pid = $$
$ echo Hello from go, pid = $$
Hello from go, pid = 70318
Note that I am executing the script with ./ not source, so the PID of the script differs. But later, the command executed by the go program has the same PID.
Related
Executing the following program, out is an empty slice of type []uint8.
package main
import (
"context"
"log"
"os/exec"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", "-c", "python3", "-c", "print('hello')")
out, _ := cmd.Output()
log.Println(out)
}
However, if I run without bash -c, I get the expected output.
This is a trivial example where bash -c isn't needed, but in the real world, my app is invoking a python script which imports several packages, and without bash -c, I get "module not found" errors from python.
What am I doing wrong here? How can I capture the stdout contents?
bash -c should be followed by one string argument with the command to execute, then bash will do the argument processing.
cmd := exec.CommandContext(ctx, "bash", "-c", "python3 -c 'print(\"hello\")'")
I want to escape a restricted shell spawning a bash shell via Go. In other words, I want to do this but using Go:
python -c 'import pty; pty.spawn("/bin/bash")'
I am totally new to Go. I have tried this (following the answer in this question Go: How to spawn a bash shell) but nothing happens:
package main
import "os"
import "os/exec"
func main() {
shell := exec.Command("/bin/bash")
shell.Stdout = os.Stdout
shell.Stdin = os.Stdin
shell.Stderr = os.Stderr
shell.Run()
}
Also if I add the fmt.Println("hello") line at the end of the main function nothing is printed
UPDATE
Maybe I did not expalined well. What I am trying to achieve it's to spawn a shell gotten a restricted shell. This is what I did:
Listener:
nc.traditional -l -p 8080 -e /bin/bash
Connects to listener: And I exec the code here
nc.traditional localhost 8080 -v
Your program works fine for me. I put some error checking in and an extra print statement which should make what is happening clearer. You are getting an interactive shell, it just looks exactly like your previous shell.
$ go run Go/shell.go
$ # whoa another shell
$ exit
exit
exiting
$ # back again
$
Here is the revised program
package main
import (
"fmt"
"log"
"os"
"os/exec"
)
func main() {
shell := exec.Command("/bin/bash")
shell.Stdout = os.Stdout
shell.Stdin = os.Stdin
shell.Stderr = os.Stderr
err := shell.Run()
if err != nil {
log.Fatalf("command failed: %v", err)
}
fmt.Printf("exiting\n")
}
i noticed that subprocesses created using Start() will be terminated after program exit, for example:
package main
import "os/exec"
func main() {
cmd := exec.Command("sh", "test.sh")
cmd.Start()
}
when main() exits, test.sh will stop running
The subprocess should continue to run after your process ends, as long as it ends cleanly, which won't happen if you hit ^C.
What you can do is intercept the signals sent to your process so you can end cleanly.
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan,
syscall.SIGINT,
syscall.SIGKILL,
syscall.SIGTERM,
syscall.SIGQUIT)
go func() {
s := <-sigchan
// do anything you need to end program cleanly
}()
A subprocess (if not waited on within the go program) will continue to run once the go program has finished (unless the subprocess naturally finishes before the parent go program).
The problem the original poster is likely encountering is that they are probably terminating their go program early (e.g. using <Ctrl-c>), and because the go program is not exiting cleanly the subprocess it spawned is also terminated.
Below is a reduced test case that helps validate this behaviour...
First I create a bash shell script I want to run (e.g. test.sh, don't forget to chmod +x ./test.sh so the script is considered 'executable'). The script is very simple. It sleeps for 10 seconds and then either creates a new file called testfile (if it doesn't exist) or if the file already exists it will update the 'last modified' timestamp. This is important because this is how I confirm the bash script is still running once my go program finishes (which I expect to finish long before the bash script finishes due to the 10 second sleep).
#!/usr/local/bin/bash
sleep 10
touch testfile
Next, I have a simple go program, which spawns a subprocess that runs the bash script above but importantly doesn't wait for it to complete. You'll see I've also added a 2 second sleep to my go program which gives me some time to press <Ctrl-c>. Now, even though I have a 2 second sleep, this program (if left to run without me pressing <Ctrl-c>) will finish before the subprocess bash script does (which is sleeping for 10 seconds):
package main
import (
"fmt"
"log"
"os/exec"
"time"
)
func main() {
cmd := exec.Command("./test.sh")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
time.Sleep(2 * time.Second)
fmt.Println("program finished, but what about the subprocess?")
}
If I run the go program and just let it finish naturally, I can ls -l testfile and check the timestamp on it. I'll then wait 10 seconds and run the ls -l testfile again and I will see the timestamp update (which shows the subprocess finished successfully).
Now if I was to re-run the go program and this time press <Ctrl-c> before the program finishes (this is why I add the 2 second sleep), then not only will the go program exit early, but the subprocess will be terminated also. So I can wait 10 seconds or 10 hours or longer, doesn't matter. The timestamp on the testfile will not update, proving the subprocess was terminated.
Try modding you program a to use Run instead of start. In that way the Go program will wait for the sh script to finish before exiting.
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "test.sh")
err := cmd.Run()
if err != nil {
log.Fatalln(err)
}
}
Likewise, you could always use a wait group but I think that's overkill here.
You could also just a go routine with or without a wait group. Depends on if you want go to wait for the program the sh program to complete
package main
import (
"os/exec"
)
func runOffMainProgram() {
cmd := exec.Command("sh", "test.sh")
cmd.Start()
}
func main() {
// This will start a go routine, but without a waitgroup this program will exit as soon as it runs
// regardless the sh program will be running in the background. Until the sh program completes
go runOffMainProgram()
}
The accepted answer is vague about where the signal should be handled. I think some more sophisticated techniques must be used to prevent sending interrupts to children, if at all possible.
TLDR;
So the only way to deal with ctrl-c is to anticipate the SIGINT and process that signal in the children.
I did some experimentation of my own.
go build -o ctrl-c ctrl-c.go
If the program is sent to the background, The only way to kill the main process is with kill -9 (SIGKILL).
SIGTERM (15) will not do.
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill $cmd
[1] 1165918
1165918
bashed 1165926
bashed 1165927
bashed 1165928
main()
go SIGN 23 urgent I/O condition
go SIGN 23 urgent I/O condition
main()
kill 1165918
go SIGN 15 terminated
main()
$ main()
main()
main()
main()
main()
main() done.
Bash _ 1165926 EXITs
Bash q 1165927 EXITs
Bash c 1165928 EXITs
[1]+ Done ./ctrl-c
SIGINT (2) will not do.
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill -INT $cmd
[1] 1167675
1167675
bashed 1167683
bashed 1167684
bashed 1167685
main()
main()
kill 1167675
go SIGN 2 interrupt
main()
balmora: ~/src/my/go/doodles/sub-process [master]
$ main()
main()
main()
main()
main()
main() done.
Bash _ 1167683 EXITs
Bash q 1167684 EXITs
Bash c 1167685 EXITs
SIGKILL kills the main process but not the bash sub-commands.
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill -KILL $cmd
[1] 1170721
1170721
bashed 1170729
bashed 1170730
bashed 1170731
main()
main()
kill 1170721
[1]+ Killed ./ctrl-c
Bash _ 1170729 EXITs
Bash q 1170730 EXITs
Bash c 1170731 EXITs
However, if the go binary is running in the foreground then only children who do deal with SIGINT will be kept running. This feels like almost the opposite of the above findings
$ ./ctrl-c
1186531
bashed 1186538
bashed 1186539
bashed 1186540
main()
main()
main()
main()
main()
main()
^C
Bash c 1186540 INTs quit
Bash q 1186539 INTs ignored
Bash c 1186540 EXITs
Bash _ 1186538 INTs ignored
go SIGN 2 interrupt
go SIGN 17 child exited
6q ELAPSED 2
Bash q 1186539 EXITs
6_ ELAPSED 2
Bash _ 1186538 EXITs
go SIGN 17 child exited
main()
main()
main() done.
Anyway, the takeaway for me is that ctrl+c is forwarded to children when Cmd.Start() is used. The behavior is the same if Cmd.Run() is used, but Cmd.Run() will wait before each sub-command exits. Running the Cmd in a go routine (go func(){}()) does not change anything. If the sub-commands are started "in parallel" as a go-routine or with Cmd.Start(), the the interrupt signal will reach all of them at the same time.
To keep the sub-commands running on an interactive terminal after an interrupt, I think the sub-commands have to handle the signal and ignore it.
The code I experimented with:
package main
import (
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
func signs(s ...os.Signal) chan os.Signal {
signals := make(chan os.Signal, 1)
signal.Notify(signals, s...)
signal.Notify(signals,
os.Interrupt, syscall.SIGINT, syscall.SIGQUIT, // keyboard
syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM, // os termination
syscall.SIGUSR1, syscall.SIGUSR2, // user
syscall.SIGPIPE, syscall.SIGCHLD, syscall.SIGSEGV, // os other
)
return signals
}
func interpret(signals chan os.Signal) chan os.Signal {
go func() {
for ;; {
select {
case sign := <-signals:
elog("go SIGN %#v %s", sign, sign)
}
}
}()
return signals
}
func bash(script string) {
cmd := exec.Command("/bin/bash", "-c", script )
cmd.Stdout = os.Stderr
err := cmd.Start()
//err := cmd.Run()
if err != nil {
log.Fatal(err)
}
elog("bashed %d", cmd.Process.Pid)
}
func main() {
fmt.Println(os.Getpid())
signals := interpret(signs())
signals = signals
//go bash(`
bash(`
trap ' echo Bash _ $$ INTs ignored; ' SIGINT
trap ' echo Bash _ $$ QUITs ignored; ' SIGQUIT
trap ' echo Bash _ $$ EXITs' EXIT
sleep 6;
echo 6_ $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n _; sleep 0.1; done; echo
`)
// go bash(`
bash(`
trap ' echo Bash q $$ INTs ignored; ' SIGINT
trap ' echo Bash q $$ QUITs; exit ' SIGQUIT
trap ' echo Bash q $$ EXITs; ' EXIT
sleep 6;
echo 6q $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n q; sleep 0.1; done; echo
`)
//go bash(`
bash(`
trap ' echo Bash c $$ INTs quit; exit ' SIGINT
trap ' echo Bash c $$ QUITs ignored; ' SIGQUIT
trap ' echo Bash c $$ EXITs' EXIT
sleep 6;
echo 6c $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n c; sleep 0.1; done; echo
`)
go func() {
for ;; {
time.Sleep(time.Millisecond * 333)
elog("main()")
}
}()
time.Sleep(3 * time.Second)
elog("main() done.")
}
func echo(a ...interface{}) {
_, err := fmt.Println(a...)
if err != nil {
fmt.Println("ERR ", err.Error())
}
}
func elog(form string, arg ...interface{}) {
println(fmt.Sprintf(form, arg...))
}
Situation:
I want to run a command that puts itself into the background. If it makes it more possible, then I'll run the command in foreground and bring it into the background by myself.
Question:
When the process runs in background: how can I get it's pid using Go?
I tried the following:
cmd := exec.Command("ssh", "-i", keyFile, "-o", "ExitOnForwardFailure yes", "-fqnNTL", fmt.Sprintf("%d:127.0.0.1:%d", port, port), fmt.Sprintf("%s#%s", serverUser, serverIP))
cmd.Start()
pid := cmd.Process.Pid
cmd.Wait()
This returns instantly and leaves ssh running in the background. But it's pid is not the pid of the running ssh process. Moreover, it's the pid of the parent ssh process before it forked and backgrounded itself.
You don't need anything special, just don't tell ssh to background itself and don't Wait() for it. Example:
$ cat script.sh
#!/bin/sh
sleep 1
echo "I'm the script with pid $$"
for i in 1 2 3; do
sleep 1
echo "Still running $$"
done
$ cat proc.go
package main
import (
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("./script.sh")
cmd.Stdout = os.Stdout
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
log.Printf("Just ran subprocess %d, exiting\n", cmd.Process.Pid)
}
$ go run proc.go
2016/09/15 17:01:03 Just ran subprocess 3794, exiting
$ I'm the script with pid 3794
Still running 3794
Still running 3794
Still running 3794
#Mostafa Hussein, can use goroutine waiting, manage process
function main()
cmd := exec.Command( "shell.sh" )
err := cmd.Start()
if err != nil {
return err
}
pid := cmd.Process.Pid
// use goroutine waiting, manage process
// this is important, otherwise the process becomes in S mode
go func() {
err = cmd.Wait()
fmt.Printf("Command finished with error: %v", err)
}()
return nil
}
I've run into an issue with the os/exec library. I want to run a shell and pass it multiple commands to run, but it's failing when I do. Here's my test code:
package main
import (
"fmt"
"os/exec"
)
func main() {
fmt.Printf("-- Test 1 --\n`")
command1 := fmt.Sprintf("\"%s\"", "pwd") // this one succeeds
fmt.Printf("Running: %s\n", command1)
cmd1 := exec.Command("/bin/sh", "-c", command1)
output1,err1 := cmd1.CombinedOutput()
if err1 != nil {
fmt.Printf("error: %v\n", err1)
return
}
fmt.Printf(string(output1))
fmt.Printf("-- Test 2 --\n")
command2 := fmt.Sprintf("\"%s\"", "pwd && pwd") // this one fails
fmt.Printf("Running: %s\n", command2)
cmd2 := exec.Command("/bin/sh", "-c", command2)
output2,err2 := cmd2.CombinedOutput()
if err2 != nil {
fmt.Printf("error: %v\n", err2)
return
}
fmt.Printf(string(output2))
}
When running this I get an error 127 on the second example. It seems like it's looking for a literal "pwd && pwd" command instead of evaluating it as a script.
If I do the same thing from the command line it works just fine.
$ /bin/sh -c "pwd && pwd"
I'm using Go 1.4 on OS X 10.10.2.
the quotes are for your shell where you typed the command line, they should not be included when programatically launching an app
just make this change and it will work:
command2 := "pwd && pwd" // you don't want the extra quotes