os: process already finished when sending syscall.SIGCONT (possible bug?) - go

When executing a process and sending signals to it using: Process.Signal I notice that after sending the second signal syscall.SIGCONT I got a: os: process already finished but if using syscall.Kill everything works as expected.
For demonstrative purposes I have created this naive example:
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
exit := make(chan error, 1)
go run(exit)
for {
select {
case <-exit:
println("fin, restarting")
run(exit)
default:
time.Sleep(time.Second)
println("running...")
}
}
}
func run(ch chan<- error) {
cmd := exec.Command("sleep", "3")
if err := cmd.Start(); err != nil {
print(err.Error())
os.Exit(1)
}
fmt.Printf("Pid: %d\n", cmd.Process.Pid)
go func() {
ch <- cmd.Wait()
}()
time.Sleep(2 * time.Second)
fmt.Printf("%v\n", cmd.Process.Signal(syscall.SIGSTOP))
time.Sleep(2 * time.Second)
// Using this will return an os: process already finished
fmt.Printf("%v\n", cmd.Process.Signal(syscall.SIGCONT))
// This works as expected
//fmt.Printf("%v\n", syscall.Kill(cmd.Process.Pid, syscall.SIGCONT))
}
So basically if using:
cmd.Process.Signal(syscall.SIGCONT)
os: process already finished is returned
But when using:
syscall.Kill(cmd.Process.Pid, syscall.SIGCONT)
It works as expected.
Could this be a bug on os.exec or it the expected behavior?
UPDATE: seems to be happening only on mac os X

This issue seems to be only happening on Mac OS X, tested on "Sierra" and "El Capitan" https://go-review.googlesource.com/#/c/37610/
So, for now, to keep things cross-platform better user:
syscall.Kill(cmd.Process.Pid, syscall.SIGCONT))
Example code to test if your system has this issue:
package main
import (
"fmt"
"log"
"os/exec"
"syscall"
"unsafe"
)
func main() {
cmd := exec.Command("sleep", "10")
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// signal when wait4 will return immediately
go func() {
var siginfo [128]byte
psig := &siginfo[0]
_, _, e := syscall.Syscall6(syscall.SYS_WAITID, 1, uintptr(cmd.Process.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
fmt.Println("WAITID RETURNED -- this shouldn't happen:", e)
}()
err := cmd.Process.Signal(syscall.SIGSTOP)
if err != nil {
log.Fatal(err)
}
cmd.Wait()
}

Related

How can I get a pid of the foreground app in memory safe way?

I want to get a pid of the foreground app. Please do not recommend robotgo, I already tried and know that it works but causes memory leak as I commented on issue. Like the code mentioned there I want to get a pid in a loop so it should be memory safe.
If I understand your question correctly, I think the following program should help. I'm using the mitchellh/go-ps package.
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
ps "github.com/mitchellh/go-ps"
)
func foreground(pid int) (bool, error) {
out, err := exec.Command("/bin/ps", "-o", "stat=", "-p", strconv.Itoa(int(pid))).Output()
if err != nil {
return false, err
}
return strings.IndexByte(string(out), '+') != -1, nil
}
func getProcs() error {
procs, err := ps.Processes()
if err != nil {
return err
}
for _, p := range procs {
ok, err := foreground(p.Pid())
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
continue
}
if ok {
fmt.Fprintf(os.Stdout, "pid: %d, process: %s\n", p.Pid(), p.Executable())
}
}
return nil
}
func scheduler(tick chan<- struct{}, every time.Duration) {
tick <- struct{}{}
for range time.Tick(every * time.Second) {
tick <- struct{}{}
}
}
func main() {
dur := flag.Duration("every", 10, "interval (in seconds)")
flag.Parse()
if *dur <= 0 {
fmt.Fprintf(os.Stderr, "interval should be more than 0")
os.Exit(1)
}
tick := make(chan struct{})
go scheduler(tick, *dur)
for range tick {
if err := getProcs(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
}
Update:
I've updated the answer. Now you could print the processes that are running in the foreground. Due to unavailability of procfs in darwin based OS's, we are depending on ps. So, using ps -o stat= -p $PID gives us a + sign if the process is running on foreground. So, the theory behind foreground process is that the process's group id == controlling tty process's group id.
package main
import (
"flag"
"fmt"
"os"
"time"
ps "github.com/shirou/gopsutil/process"
)
func getProcs() error {
procs, err := ps.Processes()
if err != nil {
return err
}
for _, p := range procs {
name, _ := p.Name()
foreground, _ := p.Foreground()
fmt.Fprintf(os.Stdout, "pid: %d,\tprocess: %s ,\tForeground: %v\n", p.Pid, name, foreground)
}
return nil
}
func main() {
for range time.Tick(1 * time.Second) {
if err := getProcs(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
}
It is based on shmsr's answer but a different library that provides more information for process
Go Playground

How to create a .lock file in golang and remove it before exiting?

I'm trying to prevent a program from opening another instance if it's already open, to do this I create a file with .lock extension and remove it when exiting the program. However everything except the remove works.
package main
import (
"os"
"os/signal"
"fmt"
)
func main() {
var test string
exitsig := make(chan os.Signal, 1)
signal.Notify(exitsig, os.Interrupt)
var (
lockstate bool = false
)
if _, err := os.Stat("ms.lock"); err == nil {
return
} else if os.IsNotExist(err) {
var file, err = os.Create("ms.lock")
if err != nil {
return
}
file.Close()
lockstate = true
}
go func() {
<- exitsig
fmt.Println("Error removing file")
fmt.Scanf("%s", &test)
if lockstate {
var err = os.Remove("ms.lock")
if err != nil {
fmt.Println("Error removing file")
fmt.Scanf("%s", &test)
}
}
os.Exit(0)
}()
}
I've tried exiting by ctrl+c, exiting by pressing the close button on the top right corner of the window but it never sends a signal, the os.Interrupt signal is never caught. What is the reason for this?
Also, I need the signal to be non-platform specific, so it should work on both windows and unix based systems.
I think it's because your main function exists soon after launching the goroutine. If the main function ends, all running goroutines will die too.
Here's code that works for me:
package main
import (
"fmt"
"os"
"os/signal"
"sync"
)
func main() {
exitsig := make(chan os.Signal, 1)
signal.Notify(exitsig, os.Interrupt)
var (
lockstate bool = false
)
if _, err := os.Stat("ms.lock"); err == nil {
return
} else if os.IsNotExist(err) {
var file, err = os.Create("ms.lock")
if err != nil {
return
}
file.Close()
lockstate = true
}
go func() {
<-exitsig
if lockstate {
var err = os.Remove("ms.lock")
if err != nil {
fmt.Println("Error removing file: ", err)
}
}
os.Exit(0)
}()
wg := &sync.WaitGroup{}
wg.Add(1)
wg.Wait()
}
I added the waitgroup to wait in the main thread. Works fine on MacOS - creates ms.lock file and waits. Killing it with Cmd + C removes the file.
Should work anywhere as long as the signal fires.

Continuously execute tshark from Go script

I am trying to execute tskarh from golang script using the example from
https://tutorialedge.net/golang/executing-system-commands-with-golang/
The script works fine, but i don't receive any kind of output
What i want to get is the following:
Continuously run the script,
capture some packets,
extract some fields values,
and assign to variables
Any help please ?
https://pastebin.com/PeAz7vh9
package main
import (
"fmt"
"os/exec"
"runtime"
)
func execute() {
// here we perform the pwd command.
// we can store the output of this in our out variable
// and catch any errors in err
out, err := exec.Command("tshark", "-i", "em1").CombinedOutput()
// if there is an error with our execution
// handle it here
if err != nil {
fmt.Printf("%s", err)
}
fmt.Println("Command Successfully Executed")
// as the out variable defined above is of type []byte we need to convert
// this to a string or else we will see garbage printed out in our console
// this is how we convert it to a string
output := string(out[:])
// once we have converted it to a string we can then output it.
fmt.Println(output)
}
func main() {
fmt.Println("Simple Shell")
fmt.Println("---------------------")
if runtime.GOOS == "windows" {
fmt.Println("Can't Execute this on a windows machine")
} else {
execute()
}
}
I have no idea of tshark, but here is a code that will work continously, you need os.Interrupt, and select.
package main
import (
"os"
"os/exec"
"os/signal"
)
func main() {
out := exec.Command("ping", "8.8.8.8")
f1, _ := os.OpenFile("./outfile.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
f2, _ := os.OpenFile("./errfile.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
out.Stdout = f1
out.Stderr = f2
defer func() {
f1.Close()
f2.Close()
}()
err := out.Run()
if err != nil {
panic(err)
}
var ctrlcInt chan os.Signal
ctrlcInt = make(chan os.Signal, 1)
signal.Notify(ctrlcInt, os.Interrupt)
for {
select {
case <-ctrlcInt:
break
default:
continue
}
}
return
}
this code pings 8.8.8.8 and writes out put to outfile.txt, it will exit when you press ctrl+c. If there is error it will write to errfile.txt. You can tail the files and see the output. Hope this helps.

How to connect xml decoder to stdoutPipe() of go exec

I'm having some trouble linking things up here.
What am I doing incorrectly?
package main
import (
"encoding/xml"
"fmt"
"log"
"os/exec"
)
func checkError(err error) {
if err != nil {
log.Fatalf("Error: %s", err)
}
}
func metrics() {
cmd := exec.Command(
"nvidia-smi",
"--query",
"--xml-format")
out, err := cmd.StdoutPipe()
checkError(err)
cmd.Start()
defer cmd.Wait()
go func() {
var data interface{}
dec := xml.NewDecoder(out)
dec.Decode(&data)
fmt.Printf("Data: %+v\n", data)
}()
//go io.Copy(os.Stdout, out)
}
func main() {
metrics()
}
Result after running program is:
Data:
Things seem to be "linked" correctly.
Problem is likely to be here:
var data interface{}
You then do:
dec.Decode(&data)
But that won't work.
You need to pass in a struct that can actually be used to decode the fields in the XML that the nvidia-smi command returns.
Find below a modified example (replacing your nvidia-smi for an echo command to make it return a sample XML).
You should adjust the struct to be able to map to the actual XML you'll receive.
By the way:
You should check the error returned by decode just in case
I don't understand why you are decoding in a separate goroutine. I left it like that in the modified example, but it would work if you do it right in the same goroutine as well.
Example:
package main
import (
"log"
"os/exec"
"fmt"
"encoding/xml"
)
func checkError(err error) {
if err != nil {
log.Fatalf("Error: %s", err)
}
}
type Result struct {
Value int `xml:"value"`
}
func metrics() {
cmd := exec.Command(
"echo", "-n",
`<result><value>1</value></result>`)
out, err := cmd.StdoutPipe()
checkError(err)
cmd.Start()
defer cmd.Wait()
go func() {
var data Result
dec := xml.NewDecoder(out)
err = dec.Decode(&data)
checkError(err)
fmt.Printf("Data: %+v\n", data)
}()
//go io.Copy(os.Stdout, out)
}
func main() {
metrics()
}

Print the console log to the screen of a webpage continuously using Go Routines

I got the go routine below to work but the problem is that it prints to the console instead of to the screen. My idea is to have a running log of what commands or output is happening in a script show on a webpage where it can be watched in real time. Using fmt.Fprint doesn't do the trick. All that happens is that my webpage will never fully load. Help please?
Running external python in Golang, Catching continuous exec.Command Stdout
go code
package main
import (
"log"
"net/http"
"time"
"os/exec"
"io"
"bufio"
"fmt"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
s := r.PathPrefix("/api/").Subrouter()
s.HandleFunc("/export", export).Methods("GET")
http.Handle("/", r)
log.Panic(http.ListenAndServe(":80", nil))
}
func export(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("python", "game.py")
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
panic(err)
}
err = cmd.Start()
if err != nil {
panic(err)
}
go copyOutput(stdout)
go copyOutput(stderr)
cmd.Wait()
}
func copyOutput(r io.Reader, w http.ResponseWriter) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
fmt.Fprint(w, scanner.Text()) //line I expect to print to the screen, but doesn't
}
}
python script
import time
import sys
while True:
print "Hello"
sys.stdout.flush()
time.sleep(1)
There's a lot more to the site so I know the route is configured correctly because printing to the screen works when I'm not using the go routine'
UPDATE:
Here is my new update function which prints to the screen, but only after the entire script has ran, not as it goes
func export(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("python", "game.py")
cmd.Stdout = w
cmd.Start()
cmd.Wait()
}
I believe I may still need a go routine in order to get it to print as I go, but putting cmd.Start and/or cmd.Wait in one doesn't work
UPDATE:
So even with everything given, I have not been able to get having the outputs show on a browser as they are ran working. It simply locks up the browser, even with the headers and flush. I will hopefully have time to give a complete, working answer to this but for now, the code above prints the code to the browser correctly after it has ran. I found a repo that I think may be what I'm looking for and maybe it will help others who come across this question as well.
https://github.com/yudai/gotty
This is a very basic (naive) example but how can give you an idea of how to stream data continuously:
https://play.golang.org/p/vtXPEHSv-Sg
The code for game.py is:
import time
import sys
while True:
print("Hello")
sys.stdout.flush()
time.sleep(1)
The web app code:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"github.com/nbari/violetear"
)
func stream(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("python", "game.py")
rPipe, wPipe, err := os.Pipe()
if err != nil {
log.Fatal(err)
}
cmd.Stdout = wPipe
cmd.Stderr = wPipe
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
go writeOutput(w, rPipe)
cmd.Wait()
wPipe.Close()
}
func writeOutput(w http.ResponseWriter, input io.ReadCloser) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported", http.StatusInternalServerError)
return
}
// Important to make it work in browsers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
in := bufio.NewScanner(input)
for in.Scan() {
fmt.Fprintf(w, "data: %s\n", in.Text())
flusher.Flush()
}
input.Close()
}
func main() {
router := violetear.New()
router.HandleFunc("/", stream, "GET")
log.Fatal(http.ListenAndServe(":8080", router))
}
The key part here is the use of http.Flusher and some headers to make it work within a browser:
w.Header().Set("Content-Type", "text/event-stream")
Note the problem with this code is that once a request arrives it will exec the command that loops forever, so the wPipe.Close() will never be called
cmd.Wait()
wPipe.Close()
To be more verbose you could print the output the terminal beside the browser:
for in.Scan() {
data := in.Text()
log.Printf("data: %s\n", data)
fmt.Fprintf(w, "data: %s\n", data)
flusher.Flush()
}
If you have more than one request you will notice it will write faster in the terminal, not bad but you will also notice that if the client closed the connection/browser you will still see data going out.
A better way could execute the command within a context, from the example: https://golang.org/pkg/os/exec/#CommandContext
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
// This will fail after 100 milliseconds. The 5 second sleep
// will be interrupted.
}
Also take a look at the context (https://stackoverflow.com/a/44146619/1135424) not replaces http.CloseNotifier so could be usefull for terminate the process once the client close browser, disconetcs.
At the end depends on your needs but hope can give you an idea about how to stream data in an easy way by using the http.Flusher interface.
Just for fun here is an example using the context:
https://play.golang.org/p/V69BuDUceBA
Still very basic, but in this case if client closes the browser the program also terminates, as an exercice could be nice to improve it an share back ;-), notice the use of CommandContext and ctx.Done()
package main
import (
"bufio"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"github.com/nbari/violetear"
)
func stream(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ch := make(chan struct{})
cmd := exec.CommandContext(ctx, "python", "game.py")
rPipe, wPipe, err := os.Pipe()
if err != nil {
log.Fatal(err)
}
cmd.Stdout = wPipe
cmd.Stderr = wPipe
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
go writeOutput(w, rPipe)
go func(ch chan struct{}) {
cmd.Wait()
wPipe.Close()
ch <- struct{}{}
}(ch)
select {
case <-ch:
case <-ctx.Done():
err := ctx.Err()
log.Printf("Client disconnected: %s\n", err)
}
}
func writeOutput(w http.ResponseWriter, input io.ReadCloser) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported", http.StatusInternalServerError)
return
}
// Important to make it work in browsers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
in := bufio.NewScanner(input)
for in.Scan() {
data := in.Text()
log.Printf("data: %s\n", data)
fmt.Fprintf(w, "data: %s\n", data)
flusher.Flush()
}
input.Close()
}
func main() {
router := violetear.New()
router.HandleFunc("/", stream, "GET")
log.Fatal(http.ListenAndServe(":8080", router))
}

Resources